agentic-flow-kit 0.2.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/bin/flow-kit.mjs +4 -0
  4. package/modules/gates/dotnet-ca/README.md +24 -0
  5. package/modules/gates/dotnet-ca/check-architecture.sh +128 -0
  6. package/modules/gates/dotnet-ca/local-pre-commit.line +1 -0
  7. package/modules/hosts/omp/agents/implementer.md +12 -0
  8. package/modules/hosts/omp/agents/independent-reviewer.md +11 -0
  9. package/modules/hosts/omp/agents/ui-verifier.md +12 -0
  10. package/modules/hosts/opencode/agents/implementer.md +14 -0
  11. package/modules/hosts/opencode/agents/independent-reviewer.md +10 -0
  12. package/modules/hosts/opencode/agents/ui-verifier.md +10 -0
  13. package/modules/hosts/opencode/commands/build.md +13 -0
  14. package/modules/hosts/opencode/commands/deploy.md +12 -0
  15. package/modules/hosts/opencode/commands/design.md +12 -0
  16. package/modules/hosts/opencode/commands/maintain.md +12 -0
  17. package/modules/hosts/opencode/commands/new-task.md +12 -0
  18. package/modules/hosts/opencode/commands/plan.md +12 -0
  19. package/modules/hosts/opencode/commands/review.md +12 -0
  20. package/modules/hosts/opencode/commands/test.md +14 -0
  21. package/modules/hosts/trae/agents/implementer.md +9 -0
  22. package/modules/hosts/trae/agents/independent-reviewer.md +8 -0
  23. package/modules/hosts/trae/agents/ui-verifier.md +12 -0
  24. package/modules/hosts/trae/commands/wf-build.md +14 -0
  25. package/modules/hosts/trae/commands/wf-deploy.md +13 -0
  26. package/modules/hosts/trae/commands/wf-design.md +13 -0
  27. package/modules/hosts/trae/commands/wf-maintain.md +13 -0
  28. package/modules/hosts/trae/commands/wf-new-task.md +13 -0
  29. package/modules/hosts/trae/commands/wf-plan.md +13 -0
  30. package/modules/hosts/trae/commands/wf-review.md +13 -0
  31. package/modules/hosts/trae/commands/wf-test.md +14 -0
  32. package/modules/hosts/trae/hooks/post-edit-check.cjs +87 -0
  33. package/modules/hosts/trae/hooks/pre-shell-check.cjs +151 -0
  34. package/modules/hosts/trae/hooks/resolve-shell.cjs +21 -0
  35. package/modules/hosts/trae/hooks.json +29 -0
  36. package/modules/hosts/trae/rules/beautify-input-chinese.md +6 -0
  37. package/modules/hosts/trae/rules/git-commit-message.md +10 -0
  38. package/modules/hosts/trae/rules/project-workflow.md +14 -0
  39. package/modules/hosts/zcode/agents/implementer.md +11 -0
  40. package/modules/hosts/zcode/agents/independent-reviewer.md +10 -0
  41. package/modules/hosts/zcode/agents/ui-verifier.md +10 -0
  42. package/package.json +34 -0
  43. package/src/add-gate.mjs +83 -0
  44. package/src/add-host.mjs +63 -0
  45. package/src/cli.mjs +81 -0
  46. package/src/doctor.mjs +150 -0
  47. package/src/init.mjs +190 -0
  48. package/src/profiles.mjs +101 -0
  49. package/src/render.mjs +68 -0
  50. package/src/run-tests.mjs +35 -0
  51. package/src/sync.mjs +141 -0
  52. package/src/sync.test.mjs +207 -0
  53. package/templates/.gitattributes +6 -0
  54. package/templates/AGENTS.md +52 -0
  55. package/templates/_agents/board/index.html +382 -0
  56. package/templates/_agents/board/marked.min.js +6 -0
  57. package/templates/_agents/cache/kb-index.json +1 -0
  58. package/templates/_agents/commands/build.md +74 -0
  59. package/templates/_agents/commands/deploy.md +65 -0
  60. package/templates/_agents/commands/design.md +54 -0
  61. package/templates/_agents/commands/maintain.md +91 -0
  62. package/templates/_agents/commands/new-task.md +73 -0
  63. package/templates/_agents/commands/plan.md +46 -0
  64. package/templates/_agents/commands/review.md +59 -0
  65. package/templates/_agents/commands/test.md +80 -0
  66. package/templates/_agents/hooks/check-pairing-incremental.sh +29 -0
  67. package/templates/_agents/hooks/check-wiki-ledger.sh +18 -0
  68. package/templates/_agents/hooks/commit-check.cjs +140 -0
  69. package/templates/_agents/hooks/local-pre-commit +6 -0
  70. package/templates/_agents/notes/runtime-env.md +18 -0
  71. package/templates/_agents/roles/implementer.md +46 -0
  72. package/templates/_agents/roles/independent-reviewer.md +43 -0
  73. package/templates/_agents/roles/ui-verifier.md +42 -0
  74. package/templates/_agents/rule-budgets.txt +8 -0
  75. package/templates/_agents/scripts/agg-delegations.cjs +175 -0
  76. package/templates/_agents/scripts/check-loop.sh +803 -0
  77. package/templates/_agents/scripts/check-loop.test.sh +1042 -0
  78. package/templates/_agents/scripts/ensure-board.ps1 +48 -0
  79. package/templates/_agents/scripts/gen-wiki-board.mjs +197 -0
  80. package/templates/_agents/scripts/gen-wiki-board.test.mjs +136 -0
  81. package/templates/_agents/scripts/gen-workflow-index.mjs +190 -0
  82. package/templates/_agents/scripts/gen-workflow-index.test.mjs +113 -0
  83. package/templates/_agents/scripts/gen-workflow-metrics.mjs +172 -0
  84. package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +101 -0
  85. package/templates/_agents/scripts/kb-cache-evict.mjs +50 -0
  86. package/templates/_agents/scripts/kb-cache-evict.test.mjs +104 -0
  87. package/templates/_agents/scripts/kb-search.mjs +307 -0
  88. package/templates/_agents/scripts/kb-search.test.mjs +153 -0
  89. package/templates/_agents/scripts/rule-budget.sh +68 -0
  90. package/templates/_agents/scripts/verify-wiki-consistency.mjs +192 -0
  91. package/templates/_agents/scripts/wiki-search.mjs +19 -0
  92. package/templates/_agents/scripts/workflow-board-server.mjs +244 -0
  93. package/templates/_agents/skills/verify-ui/SKILL.md +24 -0
  94. package/templates/_agents/skills/wiki/SKILL.md +104 -0
  95. package/templates/_agents/workflow-modules.txt +11 -0
  96. package/templates/_githooks/commit-msg +15 -0
  97. package/templates/_githooks/post-commit +10 -0
  98. package/templates/_githooks/pre-commit +58 -0
  99. package/templates/_githooks/pre-merge-commit +24 -0
  100. package/templates/_githooks/pre-push +32 -0
  101. package/templates/wiki/INDEX.md +23 -0
  102. package/templates/wiki/drafts-archive/README.md +3 -0
  103. package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +56 -0
  104. package/templates/workflow/README.md +52 -0
  105. package/templates/workflow/delegations.md +28 -0
  106. package/templates/workflow/incidents/_TEMPLATE.md +42 -0
  107. package/templates/workflow/intents/_TEMPLATE.md +40 -0
  108. package/templates/workflow/papercuts.md +7 -0
  109. package/templates/workflow/plans/_TEMPLATE.md +38 -0
  110. package/templates/workflow/regression-checklist.md +22 -0
  111. package/templates/workflow/specs/_TEMPLATE.md +41 -0
@@ -0,0 +1,382 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Workflow 看板 — AI 执行流程</title>
6
+ <script src="/marked.min.js"></script>
7
+ <style>
8
+ :root {
9
+ --bg: #14171c; --panel: #1c2027; --card: #232833; --card-hover: #2b3140;
10
+ --border: #2e3542; --text: #d7dce4; --dim: #8b93a1;
11
+ --intent: #4f8ef7; --incident: #e0674f; --spec: #9a6ff0; --plan: #2fa87c;
12
+ --ok: #3fbf7f; --warn: #d9a53a; --bad: #e0674f;
13
+ }
14
+ * { box-sizing: border-box; margin: 0; }
15
+ body { background: var(--bg); color: var(--text); font: 14px/1.6 "Segoe UI", "Microsoft YaHei", sans-serif; }
16
+ header { display: flex; align-items: center; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--border); }
17
+ header h1 { font-size: 16px; font-weight: 600; }
18
+ .chip { font-size: 12px; color: var(--dim); background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 1px 9px; }
19
+ #sse { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--dim); margin-left: auto; }
20
+ #sse i { width: 8px; height: 8px; border-radius: 50%; background: var(--bad); }
21
+ #sse.on i { background: var(--ok); }
22
+ #board { display: flex; gap: 10px; padding: 14px 18px; overflow-x: auto; height: calc(100vh - 53px); }
23
+ .col { flex: 1 1 0; min-width: 215px; display: flex; flex-direction: column; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; }
24
+ .col h2 { font-size: 13px; font-weight: 600; padding: 9px 12px; border-bottom: 1px solid var(--border); color: var(--dim); }
25
+ .col h2 b { color: var(--text); margin-right: 4px; }
26
+ .col .list { flex: 1; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 7px; }
27
+ .card { background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border); border-radius: 6px; padding: 8px 10px; cursor: pointer; }
28
+ .card:hover { background: var(--card-hover); border-color: var(--dim); }
29
+ .row { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
30
+ .type { font-size: 10px; font-weight: 700; letter-spacing: .5px; color: #fff; border-radius: 3px; padding: 0 5px; }
31
+ .lv { font-size: 10px; font-weight: 700; border: 1px solid var(--dim); color: var(--dim); border-radius: 3px; padding: 0 4px; }
32
+ .lv.hi { border-color: var(--warn); color: var(--warn); }
33
+ .date { font-size: 11px; color: var(--dim); margin-left: auto; }
34
+ .acpt { font-size: 10px; border: 1px solid var(--border); color: var(--dim); border-radius: 3px; padding: 0 4px; }
35
+ .acpt.full { color: var(--ok); border-color: var(--ok); }
36
+ .age { font-size: 10px; font-weight: 700; border-radius: 3px; padding: 0 4px; }
37
+ .age.a1 { color: var(--warn); border: 1px solid var(--warn); }
38
+ .age.a2 { color: #fff; background: var(--bad); }
39
+ .card.age1 { outline: 1px solid var(--warn); outline-offset: -1px; }
40
+ .card.age2 { outline: 1px solid var(--bad); outline-offset: -1px; }
41
+ .alrt { color: var(--bad); font-size: 11px; cursor: help; }
42
+ #q { background: var(--card); border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 3px 10px; font-size: 12px; width: 180px; }
43
+ #q:focus { outline: none; border-color: var(--intent); }
44
+ .hold { font-size: 10px; font-weight: 700; color: var(--bad); border: 1px solid var(--bad); border-radius: 3px; padding: 0 4px; margin-right: 5px; }
45
+ .ct { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: 1px; }
46
+ #toast { position: fixed; top: 54px; right: 18px; background: var(--card); border: 1px solid var(--ok); color: var(--text); border-radius: 6px; padding: 6px 14px; font-size: 12.5px; z-index: 99; opacity: 0; transition: opacity .2s; pointer-events: none; }
47
+ #toast.show { opacity: 1; }
48
+ .card .title { font-size: 12.5px; line-height: 1.45; }
49
+ /* 任务卡:作者 + 展开的文档清单 */
50
+ .auth { font-size: 11px; color: var(--dim); background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 0 7px; }
51
+ .type.mini { font-size: 9px; padding: 0 4px; }
52
+ .docs { display: none; border-top: 1px dashed var(--border); margin-top: 6px; padding-top: 4px; flex-direction: column; gap: 2px; }
53
+ .card.open .docs { display: flex; }
54
+ .doc-item { display: flex; align-items: center; gap: 6px; padding: 3px 4px; border-radius: 4px; cursor: pointer; font-size: 12px; }
55
+ .doc-item:hover { background: var(--card-hover); }
56
+ .doc-item .doc-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
57
+ .doc-item .chip { font-size: 10px; padding: 0 5px; flex-shrink: 0; }
58
+ .empty { color: var(--dim); font-size: 12px; text-align: center; padding: 18px 0; }
59
+ /* 抽屉 */
60
+ #drawer { position: fixed; top: 0; right: -780px; width: min(760px, 92vw); height: 100vh; background: var(--panel); border-left: 1px solid var(--border); transition: right .18s ease; display: flex; flex-direction: column; box-shadow: -8px 0 24px rgba(0,0,0,.4); z-index: 10; }
61
+ #drawer.open { right: 0; }
62
+ #d-head { padding: 14px 18px 8px; }
63
+ #d-title { font-size: 16px; font-weight: 600; margin: 6px 0 8px; }
64
+ #d-close { float: right; background: none; border: none; color: var(--dim); font-size: 20px; cursor: pointer; }
65
+ #d-close:hover { color: var(--text); }
66
+ #d-related { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
67
+ #d-related a { font-size: 11px; color: var(--intent); background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 1px 9px; cursor: pointer; text-decoration: none; }
68
+ #d-related a:hover { border-color: var(--intent); }
69
+ #d-tabs { display: flex; gap: 2px; padding: 0 18px; border-bottom: 1px solid var(--border); }
70
+ #d-tabs button { background: none; border: none; border-bottom: 2px solid transparent; color: var(--dim); font-size: 13px; padding: 7px 12px; cursor: pointer; }
71
+ #d-tabs button.on { color: var(--text); border-bottom-color: var(--intent); }
72
+ #d-body { flex: 1; overflow-y: auto; padding: 16px 20px 40px; }
73
+ #sse.poll i { background: var(--warn); }
74
+ /* md 渲染 */
75
+ .md h1, .md h2, .md h3 { margin: 18px 0 8px; line-height: 1.35; } .md h1 { font-size: 19px; } .md h2 { font-size: 16px; } .md h3 { font-size: 14.5px; }
76
+ .md p, .md ul, .md ol { margin: 6px 0; } .md li { margin: 2px 0; }
77
+ .md code { background: var(--card); border-radius: 3px; padding: 1px 5px; font-size: 12.5px; }
78
+ .md pre { background: var(--card); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; overflow-x: auto; margin: 8px 0; } .md pre code { background: none; padding: 0; }
79
+ .md table { border-collapse: collapse; margin: 8px 0; font-size: 12.5px; } .md th, .md td { border: 1px solid var(--border); padding: 4px 10px; } .md th { background: var(--card); }
80
+ .md blockquote { border-left: 3px solid var(--border); color: var(--dim); padding-left: 12px; margin: 8px 0; }
81
+ .md a { color: var(--intent); }
82
+ .md hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; }
83
+ /* 历史时间线 */
84
+ .tl { position: relative; padding-left: 22px; }
85
+ .tl::before { content: ""; position: absolute; left: 6px; top: 6px; bottom: 6px; width: 1px; background: var(--border); }
86
+ .tl-item { position: relative; padding: 4px 0 10px; }
87
+ .tl-item::before { content: ""; position: absolute; left: -20px; top: 11px; width: 9px; height: 9px; border-radius: 50%; background: var(--panel); border: 2px solid var(--intent); }
88
+ .tl-sub { color: var(--text); font-size: 13px; }
89
+ .tl-meta { color: var(--dim); font-size: 12px; }
90
+ .tl-meta code { cursor: pointer; } .tl-meta code:hover { color: var(--text); }
91
+ .none { color: var(--dim); font-size: 13px; }
92
+ </style>
93
+ </head>
94
+ <body>
95
+ <header>
96
+ <h1>Workflow 看板</h1>
97
+ <span class="chip" id="c-intents"></span><span class="chip" id="c-incidents"></span>
98
+ <span class="chip" id="c-plans"></span><span class="chip" id="c-specs"></span>
99
+ <span class="chip" id="c-alerts" style="display:none"></span>
100
+ <input id="q" placeholder="过滤 标题 / slug / 文件…" spellcheck="false">
101
+ <span id="sse"><i></i><span>连接断开</span></span>
102
+ </header>
103
+ <div id="board"></div>
104
+
105
+ <div id="drawer">
106
+ <div id="d-head">
107
+ <button id="d-close" title="关闭 (Esc)">×</button>
108
+ <div id="d-badges"></div>
109
+ <div id="d-title"></div>
110
+ <div id="d-related"></div>
111
+ </div>
112
+ <div id="d-tabs">
113
+ <button data-tab="doc" class="on">文档</button>
114
+ <button data-tab="hist">修改历史</button>
115
+ </div>
116
+ <div id="d-body"></div>
117
+ </div>
118
+ <div id="toast"></div>
119
+
120
+ <script>
121
+ const COLS = [
122
+ { key: 'draft', name: '起草', statuses: ['draft'] },
123
+ { key: 'active', name: '进行中', statuses: ['approved', 'open'] },
124
+ { key: 'done', name: '完成', statuses: ['done', 'fixed'] },
125
+ { key: 'closed', name: '关闭', statuses: ['closed'] },
126
+ { key: 'gone', name: '废弃', statuses: ['superseded', 'cancelled'] },
127
+ ];
128
+ const TYPE_LABEL = { intents: 'INTENT', incidents: 'INCIDENT', plans: 'PLAN', specs: 'SPEC' };
129
+ const TYPE_COLOR = { intents: 'var(--intent)', incidents: 'var(--incident)', plans: 'var(--plan)', specs: 'var(--spec)' };
130
+
131
+ let CARDS = []; // 最近一次 /api/board 的卡片
132
+ let CURRENT = null; // 抽屉当前打开的卡片
133
+ let TAB = 'doc';
134
+ let retryTimer = null; // /api/board 失败后的重试定时器
135
+ let ROOT = ''; // 仓库根(/api/board 返回,内链 vscode 协议用)
136
+ let Q = ''; // 搜索过滤关键字
137
+
138
+ const $ = (s) => document.querySelector(s);
139
+ const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
140
+ // —— 精细化徽标:验收进度 / 配对断裂 ⚠ / 进行中老化 ——
141
+ const ACTIVE_STATUSES = ['draft', 'approved', 'open'];
142
+ const ageClass = (c) => {
143
+ if (!ACTIVE_STATUSES.includes(c.status)) return '';
144
+ const d = new Date(c.date);
145
+ if (isNaN(d)) return '';
146
+ const days = Math.floor((Date.now() - d.getTime()) / 864e5);
147
+ return days >= 14 ? 'age2' : days >= 7 ? 'age1' : '';
148
+ };
149
+ const ageBadge = (c) => {
150
+ const cls = ageClass(c);
151
+ if (!cls) return '';
152
+ const days = Math.floor((Date.now() - new Date(c.date).getTime()) / 864e5);
153
+ return `<span class="age ${cls === 'age2' ? 'a2' : 'a1'}" title="进行中已 ${days} 天">${days}天</span>`;
154
+ };
155
+ const acceptBadge = (c) =>
156
+ !c.accept ? '' :
157
+ `<span class="acpt ${c.accept.done === c.accept.total ? 'full' : ''}" title="验收标准勾验 ${c.accept.done}/${c.accept.total}">☑${c.accept.done}/${c.accept.total}</span>`;
158
+ const alertBadge = (c) =>
159
+ !c.alerts?.length ? '' :
160
+ `<span class="alrt" title="${esc(c.alerts.join(';'))}">⚠${c.alerts.length > 1 ? c.alerts.length : ''}</span>`;
161
+
162
+ const badge = (c) =>
163
+ (TYPE_LABEL[c.type]
164
+ ? `<span class="type" style="background:${TYPE_COLOR[c.type]}">${TYPE_LABEL[c.type]}</span>`
165
+ : `<span class="type" style="background:var(--dim)">MD</span>`) +
166
+ (c.level ? `<span class="lv ${['L2','L3'].includes(c.level) ? 'hi' : ''}">${esc(c.level)}</span>` : '') +
167
+ acceptBadge(c) + alertBadge(c) + ageBadge(c) +
168
+ `<span class="date">${esc(c.date)}</span>`;
169
+
170
+ // —— 任务族聚合:同 slug 一卡;head 取入口文档(intent 优先、其次 incident),孤儿用首文档 ——
171
+ const TYPE_ORDER = { intents: 0, incidents: 1, plans: 2, specs: 3 };
172
+ const TYPE_ABBR = { intents: 'IN', incidents: 'IC', plans: 'PL', specs: 'SP' };
173
+ const EXPANDED = new Set(); // 展开中的任务 slug(SSE 刷新后保持)
174
+ const familyOf = (cards) => {
175
+ const bySlug = new Map();
176
+ for (const c of cards) {
177
+ if (!bySlug.has(c.slug)) bySlug.set(c.slug, []);
178
+ bySlug.get(c.slug).push(c);
179
+ }
180
+ const fams = [];
181
+ for (const docs of bySlug.values()) {
182
+ const head = docs.find((d) => d.type === 'intents') || docs.find((d) => d.type === 'incidents') || docs[0];
183
+ docs.sort((a, b) => (TYPE_ORDER[a.type] - TYPE_ORDER[b.type]) || a.date.localeCompare(b.date));
184
+ fams.push({
185
+ slug: docs[0].slug, head, docs,
186
+ alerts: docs.flatMap((d) => d.alerts || []),
187
+ author: head.author || docs.map((d) => d.author).find(Boolean) || '',
188
+ });
189
+ }
190
+ return fams.sort((a, b) => b.head.date.localeCompare(a.head.date));
191
+ };
192
+ const famTypeChips = (fam) =>
193
+ [...new Set(fam.docs.map((d) => d.type))].map((t) =>
194
+ `<span class="type mini" style="background:${TYPE_COLOR[t]}" title="${TYPE_LABEL[t]}">${TYPE_ABBR[t]}</span>`).join('');
195
+
196
+ async function refreshBoard() {
197
+ let data;
198
+ try {
199
+ const resp = await fetch('/api/board');
200
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
201
+ data = await resp.json();
202
+ } catch (e) {
203
+ // 服务不可达:头部提示 + 3s 重试(避免静默保留旧内容而不提示)
204
+ const chip = $('#c-alerts');
205
+ chip.style.display = ''; chip.textContent = '看板服务不可达,重试中'; chip.style.color = 'var(--bad)';
206
+ clearTimeout(retryTimer);
207
+ retryTimer = setTimeout(refreshBoard, 3000);
208
+ return false;
209
+ }
210
+ CARDS = data.cards;
211
+ ROOT = data.root || ROOT;
212
+ CURRENT = CARDS.find((c) => c.file === CURRENT?.file) ?? CURRENT; // 重定位抽屉卡片,避免刷新后徽标/状态陈旧
213
+ for (const t of Object.keys(TYPE_LABEL)) $(`#c-${t}`).textContent = `${TYPE_LABEL[t]} ${data.counts[t] ?? 0}`;
214
+ const groups = Object.fromEntries(COLS.map((c) => [c.key, []]));
215
+ const rest = [];
216
+ const q = Q.trim().toLowerCase();
217
+ // 过滤在族级做:任一成员命中即整卡保留;有过滤词时全部展开便于定位命中文档
218
+ const fams = familyOf(CARDS).filter((f) =>
219
+ !q || f.docs.some((d) => (d.title + ' ' + d.name + ' ' + d.file).toLowerCase().includes(q)));
220
+ for (const fam of fams) {
221
+ const col = COLS.find((c) => c.statuses.includes(fam.head.status));
222
+ (col ? groups[col.key] : rest).push(fam);
223
+ }
224
+ const nAlerts = CARDS.filter((c) => c.alerts?.length).length;
225
+ const ca = $('#c-alerts');
226
+ ca.style.display = nAlerts ? '' : 'none';
227
+ if (nAlerts) { ca.textContent = `⚠ 断档 ${nAlerts}`; ca.style.color = 'var(--bad)'; }
228
+ const cardHtml = (fam, extra = '') => {
229
+ const h = fam.head;
230
+ return `<div class="card ${ageClass(h)}${EXPANDED.has(fam.slug) || q ? ' open' : ''}" data-slug="${esc(fam.slug)}" style="border-left-color:${TYPE_COLOR[h.type]}">` +
231
+ `<div class="row">${famTypeChips(fam)}` +
232
+ (h.level ? `<span class="lv ${['L2','L3'].includes(h.level) ? 'hi' : ''}">${esc(h.level)}</span>` : '') +
233
+ acceptBadge(h) + alertBadge(fam) + ageBadge(h) + extra +
234
+ (fam.author ? `<span class="auth" title="作者(git 最早提交)">${esc(fam.author)}</span>` : '') +
235
+ `<span class="date">${esc(h.date)}</span></div>` +
236
+ `<div class="title">${h.status === 'open' ? '<span class="hold">挂起</span>' : ''}${esc(h.title)}</div>` +
237
+ `<div class="docs">${fam.docs.map((d) =>
238
+ `<div class="doc-item" data-file="${esc(d.file)}" title="打开文档">` +
239
+ `<span class="type" style="background:${TYPE_COLOR[d.type]}">${TYPE_LABEL[d.type]}</span>` +
240
+ `<span class="chip">${esc(d.status)}</span>${alertBadge(d)}` +
241
+ `<span class="doc-title">${esc(d.title)}</span></div>`).join('')}</div></div>`;
242
+ };
243
+ let html = COLS.map((c) => {
244
+ const items = groups[c.key].map((fam) => cardHtml(fam)).join('');
245
+ return `<div class="col"><h2><b>${c.name}</b>${groups[c.key].length}</h2><div class="list">${
246
+ items || '<div class="empty">—</div>'}</div></div>`;
247
+ }).join('');
248
+ if (rest.length) {
249
+ html += `<div class="col"><h2><b>其他</b>${rest.length}</h2><div class="list">${rest.map((fam) =>
250
+ cardHtml(fam, `<span class="chip">${esc(fam.head.status)}</span>`)).join('')}</div></div>`;
251
+ }
252
+ $('#board').innerHTML = html;
253
+ return true;
254
+ }
255
+
256
+ function renderDrawer() {
257
+ if (!CURRENT) return;
258
+ $('#d-badges').innerHTML = badge(CURRENT) +
259
+ (CURRENT.author ? ` <span class="chip" title="git 最早提交">✎ ${esc(CURRENT.author)}</span>` : '') +
260
+ ` <span class="chip">${esc(CURRENT.status)}</span>`;
261
+ $('#d-title').textContent = CURRENT.title;
262
+ const related = CARDS.filter((c) => c.slug === CURRENT.slug && c.file !== CURRENT.file);
263
+ $('#d-related').innerHTML = related.map((c) =>
264
+ `<a data-file="${esc(c.file)}">${TYPE_LABEL[c.type]}${c.status !== CURRENT.status ? ' · ' + esc(c.status) : ''}</a>`).join('');
265
+ document.querySelectorAll('#d-tabs button').forEach((b) => b.classList.toggle('on', b.dataset.tab === TAB));
266
+ if (TAB === 'doc') loadDoc(); else loadHist();
267
+ }
268
+
269
+ async function loadDoc() {
270
+ $('#d-body').innerHTML = '<div class="none">加载中…</div>';
271
+ const { text, error } = await (await fetch(`/api/doc?file=${encodeURIComponent(CURRENT.file)}`)).json();
272
+ if (error) { $('#d-body').innerHTML = `<div class="none">${esc(error)}</div>`; return; }
273
+ // 裸 .md 相对路径引用 → 可点链接(workflow 文档互引多为纯文本路径而非 md 链接语法;
274
+ // 已有 ](path) 链接语法因前缀为 ] 不会被重复包裹)
275
+ const linked = text.replace(
276
+ /(^|[\s((【>])((?:\.\.\/|\.\/)?(?:intents|incidents|plans|specs)\/[A-Za-z0-9_\-./]+\.md)/g,
277
+ '$1[$2]($2)'
278
+ );
279
+ $('#d-body').innerHTML = `<div class="md">${marked.parse(linked)}</div>`;
280
+ }
281
+
282
+ // —— commit 类型色点(Conventional Commits 首词)——
283
+ const CT_COLOR = { feat: 'var(--ok)', fix: 'var(--bad)', docs: 'var(--dim)', test: 'var(--spec)', refactor: 'var(--intent)', chore: 'var(--warn)', style: 'var(--spec)', perf: 'var(--warn)' };
284
+ const ctDot = (subject) => {
285
+ const t = (subject.match(/^(\w+)[(:]/) || [])[1];
286
+ return `<span class="ct" style="background:${CT_COLOR[t] || 'var(--dim)'}" title="${t || 'commit'}"></span>`;
287
+ };
288
+
289
+ async function loadHist() {
290
+ $('#d-body').innerHTML = '<div class="none">加载中…</div>';
291
+ const { commits } = await (await fetch(`/api/history?file=${encodeURIComponent(CURRENT.file)}`)).json();
292
+ $('#d-body').innerHTML = commits.length
293
+ ? `<div class="tl">${commits.map((c) =>
294
+ `<div class="tl-item"><div class="tl-sub">${ctDot(c.subject)}${esc(c.subject)}</div>` +
295
+ `<div class="tl-meta">${esc(c.date)} · <code class="hash" data-h="${esc(c.hash)}" title="点击复制完整 hash">${esc(c.hash.slice(0, 9))}</code></div></div>`).join('')}</div>`
296
+ : '<div class="none">暂无提交记录(文件尚未 commit;git 历史只包含已提交的修改)</div>';
297
+ }
298
+
299
+ function openCard(card) { CURRENT = card; TAB = 'doc'; $('#drawer').classList.add('open'); renderDrawer(); }
300
+
301
+ // —— toast 反馈 ——
302
+ let toastTimer = null;
303
+ function toast(msg) {
304
+ const t = $('#toast');
305
+ t.textContent = msg; t.classList.add('show');
306
+ clearTimeout(toastTimer);
307
+ toastTimer = setTimeout(() => t.classList.remove('show'), 1800);
308
+ }
309
+
310
+ // —— 抽屉内文档内链:md 相对链接 → 抽屉内打开;外链新窗口;仓库代码路径 → vscode 协议 ——
311
+ function viewFile(rel) {
312
+ const hit = CARDS.find((c) => c.file === rel);
313
+ if (hit) { openCard(hit); return; }
314
+ // 自由文档(不在看板卡范围,如 workflow/README.md、同名以外的引用):伪卡视图
315
+ CURRENT = { file: rel, name: rel, slug: rel, title: rel, type: 'link', status: '', level: '', date: '' };
316
+ TAB = 'doc';
317
+ $('#drawer').classList.add('open');
318
+ renderDrawer();
319
+ }
320
+
321
+ function onDocLink(e, a) {
322
+ const href = a.getAttribute('href');
323
+ if (!href || href.startsWith('#')) return; // 页内锚点走默认行为
324
+ if (/^https?:/i.test(href)) { e.preventDefault(); window.open(href, '_blank', 'noopener'); return; }
325
+ e.preventDefault();
326
+ const dir = CURRENT.file.includes('/') ? CURRENT.file.replace(/[^/]*$/, '') : '';
327
+ let rel;
328
+ try { rel = decodeURIComponent(new URL(href, 'http://x/' + dir).pathname.slice(1)); } catch { return; }
329
+ if (/\.md$/i.test(rel)) { viewFile(rel); return; }
330
+ if (ROOT && !/^[a-z]+:/i.test(rel)) { window.open(`vscode://file/${ROOT}/${rel}`); toast('已用编辑器打开 ' + rel); }
331
+ }
332
+
333
+ document.addEventListener('click', (e) => {
334
+ const dLink = e.target.closest('#d-body a');
335
+ if (dLink) { onDocLink(e, dLink); return; }
336
+ // 展开区文档条目 → 开抽屉(先于卡片命中,条目在卡内)
337
+ const item = e.target.closest('.doc-item');
338
+ if (item) {
339
+ const hit = CARDS.find((c) => c.file === item.dataset.file);
340
+ if (hit) openCard(hit);
341
+ return;
342
+ }
343
+ // 卡片点击 → 就地展开/收起该任务
344
+ const card = e.target.closest('.card');
345
+ if (card) {
346
+ const slug = card.dataset.slug;
347
+ if (EXPANDED.has(slug)) EXPANDED.delete(slug); else EXPANDED.add(slug);
348
+ card.classList.toggle('open');
349
+ return;
350
+ }
351
+ const rel = e.target.closest('#d-related a');
352
+ if (rel) {
353
+ const hit = CARDS.find((c) => c.file === rel.dataset.file);
354
+ if (hit) { CURRENT = hit; renderDrawer(); }
355
+ return;
356
+ }
357
+ const hash = e.target.closest('.hash');
358
+ if (hash) navigator.clipboard.writeText(hash.dataset.h)
359
+ .then(() => toast('已复制 ' + hash.dataset.h.slice(0, 9)))
360
+ .catch(() => toast('复制失败'));
361
+ });
362
+ $('#d-close').onclick = () => { $('#drawer').classList.remove('open'); };
363
+ document.addEventListener('keydown', (e) => { if (e.key === 'Escape') $('#drawer').classList.remove('open'); });
364
+ document.querySelectorAll('#d-tabs button').forEach((b) => b.onclick = () => { TAB = b.dataset.tab; renderDrawer(); });
365
+
366
+ // SSE:服务端 fs.watch 防抖后推送 changed → 看板与打开中的抽屉自动刷新
367
+ const sse = new EventSource('/api/events');
368
+ sse.addEventListener('changed', () => { refreshBoard().then((ok) => { if (ok) renderDrawer(); }); });
369
+ sse.addEventListener('watchdead', () => { $('#sse').classList.remove('on'); $('#sse').classList.add('poll'); $('#sse span:last-child').textContent = '监听失效 · 60s 轮询'; });
370
+ sse.onopen = () => { $('#sse').classList.add('on'); $('#sse').classList.remove('poll'); $('#sse span:last-child').textContent = '实时'; };
371
+ sse.onerror = () => { $('#sse').classList.remove('on'); $('#sse span:last-child').textContent = '连接断开,自动重连中'; };
372
+
373
+ // 60s 兜底轮询:SSE/watch 任何环节失效时数据仍保鲜(正常时多拉一次无害)
374
+ setInterval(() => refreshBoard(), 60000);
375
+
376
+ // 搜索过滤:本地过滤标题/slug/文件,SSE 刷新时保持关键字
377
+ $('#q').addEventListener('input', (e) => { Q = e.target.value; refreshBoard(); });
378
+
379
+ refreshBoard();
380
+ </script>
381
+ </body>
382
+ </html>
@@ -0,0 +1,6 @@
1
+ /**
2
+ * marked v12.0.2 - a markdown parser
3
+ * Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed)
4
+ * https://github.com/markedjs/marked
5
+ */
6
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s=/[&<>"']/,r=new RegExp(s.source,"g"),i=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,l=new RegExp(i.source,"g"),o={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},a=e=>o[e];function c(e,t){if(t){if(s.test(e))return e.replace(r,a)}else if(i.test(e))return e.replace(l,a);return e}const h=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function p(e){return e.replace(h,((e,t)=>"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""))}const u=/(^|[^\[])\^/g;function k(e,t){let n="string"==typeof e?e:e.source;t=t||"";const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(u,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}function g(e){try{e=encodeURI(e).replace(/%25/g,"%")}catch(e){return null}return e}const f={exec:()=>null};function d(e,t){const n=e.replace(/\|/g,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(/ \|/);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(/\\\|/g,"|");return n}function x(e,t,n){const s=e.length;if(0===s)return"";let r=0;for(;r<s;){const i=e.charAt(s-r-1);if(i!==t||n){if(i===t||!n)break;r++}else r++}return e.slice(0,s-r)}function b(e,t,n,s){const r=t.href,i=t.title?c(t.title):null,l=e[1].replace(/\\([\[\]])/g,"$1");if("!"!==e[0].charAt(0)){s.state.inLink=!0;const e={type:"link",raw:n,href:r,title:i,text:l,tokens:s.inlineTokens(l)};return s.state.inLink=!1,e}return{type:"image",raw:n,href:r,title:i,text:c(l)}}class w{options;rules;lexer;constructor(t){this.options=t||e.defaults}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:x(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t){const n=e.match(/^(\s+)(?:```)/);if(null===n)return t;const s=n[1];return t.split("\n").map((e=>{const t=e.match(/^\s+/);if(null===t)return e;const[n]=t;return n.length>=s.length?e.slice(s.length):e})).join("\n")}(e,t[3]||"");return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(/#$/.test(e)){const t=x(e,"#");this.options.pedantic?e=t.trim():t&&!/ $/.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:t[0]}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=t[0].replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,"\n $1");e=x(e.replace(/^ *>[ \t]?/gm,""),"\n");const n=this.lexer.state.top;this.lexer.state.top=!0;const s=this.lexer.blockTokens(e);return this.lexer.state.top=n,{type:"blockquote",raw:t[0],tokens:s,text:e}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim();const s=n.length>1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=new RegExp(`^( {0,3}${n})((?:[\t ][^\\n]*)?(?:\\n|$))`);let l="",o="",a=!1;for(;e;){let n=!1;if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;l=t[0],e=e.substring(l.length);let s=t[2].split("\n",1)[0].replace(/^\t+/,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=0;this.options.pedantic?(h=2,o=s.trimStart()):(h=t[2].search(/[^ ]/),h=h>4?1:h,o=s.slice(h),h+=t[1].length);let p=!1;if(!s&&/^ *$/.test(c)&&(l+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=new RegExp(`^ {0,${Math.min(3,h-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),n=new RegExp(`^ {0,${Math.min(3,h-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),r=new RegExp(`^ {0,${Math.min(3,h-1)}}(?:\`\`\`|~~~)`),i=new RegExp(`^ {0,${Math.min(3,h-1)}}#`);for(;e;){const a=e.split("\n",1)[0];if(c=a,this.options.pedantic&&(c=c.replace(/^ {1,4}(?=( {4})*[^ ])/g," ")),r.test(c))break;if(i.test(c))break;if(t.test(c))break;if(n.test(e))break;if(c.search(/[^ ]/)>=h||!c.trim())o+="\n"+c.slice(h);else{if(p)break;if(s.search(/[^ ]/)>=4)break;if(r.test(s))break;if(i.test(s))break;if(n.test(s))break;o+="\n"+c}p||c.trim()||(p=!0),l+=a+"\n",e=e.substring(a.length+1),s=c.slice(h)}}r.loose||(a?r.loose=!0:/\n *\n *$/.test(l)&&(a=!0));let u,k=null;this.options.gfm&&(k=/^\[[ xX]\] /.exec(o),k&&(u="[ ] "!==k[0],o=o.replace(/^\[[ xX]\] +/,""))),r.items.push({type:"list_item",raw:l,task:!!k,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=l}r.items[r.items.length-1].raw=l.trimEnd(),r.items[r.items.length-1].text=o.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e<r.items.length;e++)if(this.lexer.state.top=!1,r.items[e].tokens=this.lexer.blockTokens(r.items[e].text,[]),!r.loose){const t=r.items[e].tokens.filter((e=>"space"===e.type)),n=t.length>0&&t.some((e=>/\n.*\n/.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e<r.items.length;e++)r.items[e].loose=!0;return r}}html(e){const t=this.rules.block.html.exec(e);if(t){return{type:"html",block:!0,raw:t[0],pre:"pre"===t[1]||"script"===t[1]||"style"===t[1],text:t[0]}}}def(e){const t=this.rules.block.def.exec(e);if(t){const e=t[1].toLowerCase().replace(/\s+/g," "),n=t[2]?t[2].replace(/^<(.*)>$/,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",s=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:e,raw:t[0],href:n,title:s}}}table(e){const t=this.rules.block.table.exec(e);if(!t)return;if(!/[:|]/.test(t[2]))return;const n=d(t[1]),s=t[2].replace(/^\||\| *$/g,"").split("|"),r=t[3]&&t[3].trim()?t[3].replace(/\n[ \t]*$/,"").split("\n"):[],i={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(const e of s)/^ *-+: *$/.test(e)?i.align.push("right"):/^ *:-+: *$/.test(e)?i.align.push("center"):/^ *:-+ *$/.test(e)?i.align.push("left"):i.align.push(null);for(const e of n)i.header.push({text:e,tokens:this.lexer.inline(e)});for(const e of r)i.rows.push(d(e,i.header.length).map((e=>({text:e,tokens:this.lexer.inline(e)}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:c(t[1])}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&/^<a /i.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&/^<\/a>/i.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&/^</.test(e)){if(!/>$/.test(e))return;const t=x(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s<e.length;s++)if("\\"===e[s])s++;else if(e[s]===t[0])n++;else if(e[s]===t[1]&&(n--,n<0))return s;return-1}(t[2],"()");if(e>-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),/^</.test(n)&&(n=this.options.pedantic&&!/>$/.test(e)?n.slice(1):n.slice(1,-1)),b(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(/\s+/g," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return b(n,e,n[0],this.lexer)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(/[\p{L}\p{N}]/u))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(/\n/g," ");const n=/[^ ]/.test(e),s=/^ /.test(e)&&/ $/.test(e);return n&&s&&(e=e.substring(1,e.length-1)),e=c(e,!0),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=c(t[1]),n="mailto:"+e):(e=c(t[1]),n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=c(t[0]),n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=c(t[0]),n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){let e;return e=this.lexer.state.inRawBlock?t[0]:c(t[0]),{type:"text",raw:t[0],text:e}}}}const m=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,y=/(?:[*+-]|\d{1,9}[.)])/,$=k(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,y).replace(/blockCode/g,/ {4}/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),z=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,T=/(?!\s*\])(?:\\.|[^\[\]\\])+/,R=k(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/).replace("label",T).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),_=k(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,y).getRegex(),A="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",S=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,I=k("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))","i").replace("comment",S).replace("tag",A).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),E=k(z).replace("hr",m).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",A).getRegex(),q={blockquote:k(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",E).getRegex(),code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,def:R,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:m,html:I,lheading:$,list:_,newline:/^(?: *(?:\n|$))+/,paragraph:E,table:f,text:/^[^\n]+/},Z=k("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",m).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",A).getRegex(),L={...q,table:Z,paragraph:k(z).replace("hr",m).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",Z).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",A).getRegex()},P={...q,html:k("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:\"[^\"]*\"|'[^']*'|\\s[^'\"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",S).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:f,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:k(z).replace("hr",m).replace("heading"," *#{1,6} *[^\n]").replace("lheading",$).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},Q=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,v=/^( {2,}|\\)\n(?!\s*$)/,B="\\p{P}\\p{S}",C=k(/^((?![*_])[\spunctuation])/,"u").replace(/punctuation/g,B).getRegex(),M=k(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/,"u").replace(/punct/g,B).getRegex(),O=k("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])","gu").replace(/punct/g,B).getRegex(),D=k("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])","gu").replace(/punct/g,B).getRegex(),j=k(/\\([punct])/,"gu").replace(/punct/g,B).getRegex(),H=k(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),U=k(S).replace("(?:--\x3e|$)","--\x3e").getRegex(),X=k("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",U).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),F=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,N=k(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",F).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),G=k(/^!?\[(label)\]\[(ref)\]/).replace("label",F).replace("ref",T).getRegex(),J=k(/^!?\[(ref)\](?:\[\])?/).replace("ref",T).getRegex(),K={_backpedal:f,anyPunctuation:j,autolink:H,blockSkip:/\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g,br:v,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:f,emStrongLDelim:M,emStrongRDelimAst:O,emStrongRDelimUnd:D,escape:Q,link:N,nolink:J,punctuation:C,reflink:G,reflinkSearch:k("reflink|nolink(?!\\()","g").replace("reflink",G).replace("nolink",J).getRegex(),tag:X,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,url:f},V={...K,link:k(/^!?\[(label)\]\((.*?)\)/).replace("label",F).getRegex(),reflink:k(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",F).getRegex()},W={...K,escape:k(Q).replace("])","~|])").getRegex(),url:k(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},Y={...W,br:k(v).replace("{2,}","*").getRegex(),text:k(W.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},ee={normal:q,gfm:L,pedantic:P},te={normal:K,gfm:W,breaks:Y,pedantic:V};class ne{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new w,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={block:ee.normal,inline:te.normal};this.options.pedantic?(n.block=ee.pedantic,n.inline=te.pedantic):this.options.gfm&&(n.block=ee.gfm,this.options.breaks?n.inline=te.breaks:n.inline=te.gfm),this.tokenizer.rules=n}static get rules(){return{block:ee,inline:te}}static lex(e,t){return new ne(t).lex(e)}static lexInline(e,t){return new ne(t).inlineTokens(e)}lex(e){e=e.replace(/\r\n|\r/g,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e<this.inlineQueue.length;e++){const t=this.inlineQueue[e];this.inlineTokens(t.src,t.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[]){let n,s,r,i;for(e=this.options.pedantic?e.replace(/\t/g," ").replace(/^ +$/gm,""):e.replace(/^( *)(\t+)/gm,((e,t,n)=>t+" ".repeat(n.length)));e;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.space(e))e=e.substring(n.raw.length),1===n.raw.length&&t.length>0?t[t.length-1].raw+="\n":t.push(n);else if(n=this.tokenizer.code(e))e=e.substring(n.raw.length),s=t[t.length-1],!s||"paragraph"!==s.type&&"text"!==s.type?t.push(n):(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue[this.inlineQueue.length-1].src=s.text);else if(n=this.tokenizer.fences(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.heading(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.hr(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.blockquote(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.list(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.html(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.def(e))e=e.substring(n.raw.length),s=t[t.length-1],!s||"paragraph"!==s.type&&"text"!==s.type?this.tokens.links[n.tag]||(this.tokens.links[n.tag]={href:n.href,title:n.title}):(s.raw+="\n"+n.raw,s.text+="\n"+n.raw,this.inlineQueue[this.inlineQueue.length-1].src=s.text);else if(n=this.tokenizer.table(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.lheading(e))e=e.substring(n.raw.length),t.push(n);else{if(r=e,this.options.extensions&&this.options.extensions.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(n=this.tokenizer.paragraph(r)))s=t[t.length-1],i&&"paragraph"===s.type?(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=s.text):t.push(n),i=r.length!==e.length,e=e.substring(n.raw.length);else if(n=this.tokenizer.text(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===s.type?(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=s.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n,s,r,i,l,o,a=e;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(i=this.tokenizer.rules.inline.reflinkSearch.exec(a));)e.includes(i[0].slice(i[0].lastIndexOf("[")+1,-1))&&(a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(i=this.tokenizer.rules.inline.blockSkip.exec(a));)a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(i=this.tokenizer.rules.inline.anyPunctuation.exec(a));)a=a.slice(0,i.index)+"++"+a.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;e;)if(l||(o=""),l=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.escape(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.tag(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.link(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.emStrong(e,a,o))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.codespan(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.br(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.del(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.autolink(e))e=e.substring(n.raw.length),t.push(n);else if(this.state.inLink||!(n=this.tokenizer.url(e))){if(r=e,this.options.extensions&&this.options.extensions.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(n=this.tokenizer.inlineText(r))e=e.substring(n.raw.length),"_"!==n.raw.slice(-1)&&(o=n.raw.slice(-1)),l=!0,s=t[t.length-1],s&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}else e=e.substring(n.raw.length),t.push(n);return t}}class se{options;constructor(t){this.options=t||e.defaults}code(e,t,n){const s=(t||"").match(/^\S*/)?.[0];return e=e.replace(/\n$/,"")+"\n",s?'<pre><code class="language-'+c(s)+'">'+(n?e:c(e,!0))+"</code></pre>\n":"<pre><code>"+(n?e:c(e,!0))+"</code></pre>\n"}blockquote(e){return`<blockquote>\n${e}</blockquote>\n`}html(e,t){return e}heading(e,t,n){return`<h${t}>${e}</h${t}>\n`}hr(){return"<hr>\n"}list(e,t,n){const s=t?"ol":"ul";return"<"+s+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"</"+s+">\n"}listitem(e,t,n){return`<li>${e}</li>\n`}checkbox(e){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph(e){return`<p>${e}</p>\n`}table(e,t){return t&&(t=`<tbody>${t}</tbody>`),"<table>\n<thead>\n"+e+"</thead>\n"+t+"</table>\n"}tablerow(e){return`<tr>\n${e}</tr>\n`}tablecell(e,t){const n=t.header?"th":"td";return(t.align?`<${n} align="${t.align}">`:`<${n}>`)+e+`</${n}>\n`}strong(e){return`<strong>${e}</strong>`}em(e){return`<em>${e}</em>`}codespan(e){return`<code>${e}</code>`}br(){return"<br>"}del(e){return`<del>${e}</del>`}link(e,t,n){const s=g(e);if(null===s)return n;let r='<a href="'+(e=s)+'"';return t&&(r+=' title="'+t+'"'),r+=">"+n+"</a>",r}image(e,t,n){const s=g(e);if(null===s)return n;let r=`<img src="${e=s}" alt="${n}"`;return t&&(r+=` title="${t}"`),r+=">",r}text(e){return e}}class re{strong(e){return e}em(e){return e}codespan(e){return e}del(e){return e}html(e){return e}text(e){return e}link(e,t,n){return""+n}image(e,t,n){return""+n}br(){return""}}class ie{options;renderer;textRenderer;constructor(t){this.options=t||e.defaults,this.options.renderer=this.options.renderer||new se,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new re}static parse(e,t){return new ie(t).parse(e)}static parseInline(e,t){return new ie(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[r.type]){const e=r,t=this.options.extensions.renderers[e.type].call({parser:this},e);if(!1!==t||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(e.type)){n+=t||"";continue}}switch(r.type){case"space":continue;case"hr":n+=this.renderer.hr();continue;case"heading":{const e=r;n+=this.renderer.heading(this.parseInline(e.tokens),e.depth,p(this.parseInline(e.tokens,this.textRenderer)));continue}case"code":{const e=r;n+=this.renderer.code(e.text,e.lang,!!e.escaped);continue}case"table":{const e=r;let t="",s="";for(let t=0;t<e.header.length;t++)s+=this.renderer.tablecell(this.parseInline(e.header[t].tokens),{header:!0,align:e.align[t]});t+=this.renderer.tablerow(s);let i="";for(let t=0;t<e.rows.length;t++){const n=e.rows[t];s="";for(let t=0;t<n.length;t++)s+=this.renderer.tablecell(this.parseInline(n[t].tokens),{header:!1,align:e.align[t]});i+=this.renderer.tablerow(s)}n+=this.renderer.table(t,i);continue}case"blockquote":{const e=r,t=this.parse(e.tokens);n+=this.renderer.blockquote(t);continue}case"list":{const e=r,t=e.ordered,s=e.start,i=e.loose;let l="";for(let t=0;t<e.items.length;t++){const n=e.items[t],s=n.checked,r=n.task;let o="";if(n.task){const e=this.renderer.checkbox(!!s);i?n.tokens.length>0&&"paragraph"===n.tokens[0].type?(n.tokens[0].text=e+" "+n.tokens[0].text,n.tokens[0].tokens&&n.tokens[0].tokens.length>0&&"text"===n.tokens[0].tokens[0].type&&(n.tokens[0].tokens[0].text=e+" "+n.tokens[0].tokens[0].text)):n.tokens.unshift({type:"text",text:e+" "}):o+=e+" "}o+=this.parse(n.tokens,i),l+=this.renderer.listitem(o,r,!!s)}n+=this.renderer.list(l,t,s);continue}case"html":{const e=r;n+=this.renderer.html(e.text,e.block);continue}case"paragraph":{const e=r;n+=this.renderer.paragraph(this.parseInline(e.tokens));continue}case"text":{let i=r,l=i.tokens?this.parseInline(i.tokens):i.text;for(;s+1<e.length&&"text"===e[s+1].type;)i=e[++s],l+="\n"+(i.tokens?this.parseInline(i.tokens):i.text);n+=t?this.renderer.paragraph(l):l;continue}default:{const e='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}parseInline(e,t){t=t||this.renderer;let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[r.type]){const e=this.options.extensions.renderers[r.type].call({parser:this},r);if(!1!==e||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(r.type)){n+=e||"";continue}}switch(r.type){case"escape":{const e=r;n+=t.text(e.text);break}case"html":{const e=r;n+=t.html(e.text);break}case"link":{const e=r;n+=t.link(e.href,e.title,this.parseInline(e.tokens,t));break}case"image":{const e=r;n+=t.image(e.href,e.title,e.text);break}case"strong":{const e=r;n+=t.strong(this.parseInline(e.tokens,t));break}case"em":{const e=r;n+=t.em(this.parseInline(e.tokens,t));break}case"codespan":{const e=r;n+=t.codespan(e.text);break}case"br":n+=t.br();break;case"del":{const e=r;n+=t.del(this.parseInline(e.tokens,t));break}case"text":{const e=r;n+=t.text(e.text);break}default:{const e='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}}class le{options;constructor(t){this.options=t||e.defaults}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}}class oe{defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};options=this.setOptions;parse=this.#e(ne.lex,ie.parse);parseInline=this.#e(ne.lexInline,ie.parseInline);Parser=ie;Renderer=se;TextRenderer=re;Lexer=ne;Tokenizer=w;Hooks=le;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(const s of e)switch(n=n.concat(t.call(this,s)),s.type){case"table":{const e=s;for(const s of e.header)n=n.concat(this.walkTokens(s.tokens,t));for(const s of e.rows)for(const e of s)n=n.concat(this.walkTokens(e.tokens,t));break}case"list":{const e=s;n=n.concat(this.walkTokens(e.items,t));break}default:{const e=s;this.defaults.extensions?.childTokens?.[e.type]?this.defaults.extensions.childTokens[e.type].forEach((s=>{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new se(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if("options"===n)continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new w(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new le;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if("options"===n)continue;const s=n,r=e.hooks[s],i=t[s];le.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return ne.lex(e,t??this.defaults)}parser(e,t){return ie.parse(e,t??this.defaults)}#e(e,t){return(n,s)=>{const r={...s},i={...this.defaults,...r};!0===this.defaults.async&&!1===r.async&&(i.silent||console.warn("marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored."),i.async=!0);const l=this.#t(!!i.silent,!!i.async);if(null==n)return l(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof n)return l(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));if(i.hooks&&(i.hooks.options=i),i.async)return Promise.resolve(i.hooks?i.hooks.preprocess(n):n).then((t=>e(t,i))).then((e=>i.hooks?i.hooks.processAllTokens(e):e)).then((e=>i.walkTokens?Promise.all(this.walkTokens(e,i.walkTokens)).then((()=>e)):e)).then((e=>t(e,i))).then((e=>i.hooks?i.hooks.postprocess(e):e)).catch(l);try{i.hooks&&(n=i.hooks.preprocess(n));let s=e(n,i);i.hooks&&(s=i.hooks.processAllTokens(s)),i.walkTokens&&this.walkTokens(s,i.walkTokens);let r=t(s,i);return i.hooks&&(r=i.hooks.postprocess(r)),r}catch(e){return l(e)}}}#t(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="<p>An error occurred:</p><pre>"+c(n.message+"",!0)+"</pre>";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const ae=new oe;function ce(e,t){return ae.parse(e,t)}ce.options=ce.setOptions=function(e){return ae.setOptions(e),ce.defaults=ae.defaults,n(ce.defaults),ce},ce.getDefaults=t,ce.defaults=e.defaults,ce.use=function(...e){return ae.use(...e),ce.defaults=ae.defaults,n(ce.defaults),ce},ce.walkTokens=function(e,t){return ae.walkTokens(e,t)},ce.parseInline=ae.parseInline,ce.Parser=ie,ce.parser=ie.parse,ce.Renderer=se,ce.TextRenderer=re,ce.Lexer=ne,ce.lexer=ne.lex,ce.Tokenizer=w,ce.Hooks=le,ce.parse=ce;const he=ce.options,pe=ce.setOptions,ue=ce.use,ke=ce.walkTokens,ge=ce.parseInline,fe=ce,de=ie.parse,xe=ne.lex;e.Hooks=le,e.Lexer=ne,e.Marked=oe,e.Parser=ie,e.Renderer=se,e.TextRenderer=re,e.Tokenizer=w,e.getDefaults=t,e.lexer=xe,e.marked=ce,e.options=he,e.parse=fe,e.parseInline=ge,e.parser=de,e.setOptions=pe,e.use=ue,e.walkTokens=ke}));
@@ -0,0 +1 @@
1
+ {"fp":"1bc2046ba4cefb3c","files":{"C:\\Users\\Administrator\\AppData\\Local\\Temp\\kb-search-test-umvfVZ::workflow/intents/2026-09-21-bbb.md":{"st":[1790177200273.1658,270],"kind":"wf","meta":{"状态":"approved","级别":"L1","日期":"2026-09-21","模块":"pipeline"},"title":"乙需求","lines":[[1,"(frontmatter)","---"],[2,"(frontmatter)","状态: approved"],[3,"(frontmatter)","级别: L1"],[4,"(frontmatter)","日期: 2026-09-21"],[5,"(frontmatter)","模块: pipeline"],[6,"(frontmatter)","---"],[7,"(标题)","# INTENT — 乙需求"],[9,"目标","## 目标"],[10,"目标","- 统一口径:编码模板与分类层级,冲突处理见 A/B乙"],[11,"目标",""]]},"C:\\Users\\Administrator\\AppData\\Local\\Temp\\kb-search-test-umvfVZ::workflow/plans/2026-09-20-ccc.md":{"st":[1790177200273.1658,200],"kind":"wf","meta":{"状态":"approved","级别":"L1","模块":"purchase"},"title":"丙计划","lines":[[1,"(frontmatter)","---"],[2,"(frontmatter)","状态: approved"],[3,"(frontmatter)","级别: L1"],[4,"(frontmatter)","模块: purchase"],[5,"(frontmatter)","---"],[6,"(标题)","# PLAN — 丙计划"],[8,"目标","## 目标"],[9,"目标","- 只讲编码模板,不提另一个词"],[10,"目标",""]]},"C:\\Users\\Administrator\\AppData\\Local\\Temp\\kb-search-test-umvfVZ::workflow/incidents/2026-09-01-aaa.md":{"st":[1790177200272.163,270],"kind":"wf","meta":{"状态":"closed","级别":"L2","发现":"2026-09-01","模块":"material"},"title":"甲事故","lines":[[1,"(frontmatter)","---"],[2,"(frontmatter)","状态: closed"],[3,"(frontmatter)","级别: L2"],[4,"(frontmatter)","发现: 2026-09-01"],[5,"(frontmatter)","模块: material"],[6,"(frontmatter)","---"],[7,"(标题)","# INCIDENT — 甲事故"],[9,"目标","## 目标"],[10,"目标","- 统一口径:编码模板与分类层级,冲突处理见 A/B甲"],[11,"目标",""]]},"C:\\Users\\Administrator\\AppData\\Local\\Temp\\kb-search-test-umvfVZ::wiki/测试主题/说明.md":{"st":[1790177200274.1648,47],"kind":"wiki","lines":[[1,"# 说明"],[2,""],[3,"编码模板的领域知识条目。"],[4,""]]}}}
@@ -0,0 +1,74 @@
1
+ ---
2
+ description: Build 阶段:起 plan + 实现代码 + 自验(确认后才动代码)
3
+ stage: Build
4
+ triggers:
5
+ - "起个 plan"
6
+ - "spec 没问题"
7
+ - "开始写吧"
8
+ - "动手吧"
9
+ - "计划可以了"
10
+ delegation:
11
+ - role: implementer
12
+ instructions: .agents/roles/implementer.md
13
+ when: plan 与改动文件清单均已确认,且工作包边界满足角色契约
14
+ required: false
15
+ fallback: main
16
+ approval_required: true
17
+ next: .agents/commands/test.md
18
+ ---
19
+
20
+ # Build · 起 plan + 写代码
21
+
22
+ > AI 起草 plan,用户确认;进计划模式列改动,用户确认;最后才动代码。两道确认门。
23
+
24
+ ## 执行
25
+
26
+ ### 起草 plan
27
+
28
+ 1. 确认同名入口文档(approved intent 或已确认的 incident)+ spec(若 L2/L3)均已确认;否则拒绝,回上一阶段
29
+ 2. 起草 plan:
30
+ - 复制 `workflow/plans/_TEMPLATE.md` → `workflow/plans/YYYY-MM-DD-<主题>.md`(与入口文档同名)
31
+ - L2/L3 按 4 节填写:任务拆解(每个任务有判据) / 风险评估 / 执行顺序 / 遗留项
32
+ - L1 默认极简两节:改动面(逐条文件+做什么) / 验证方式(一行静态门+按需 UI 实测);仅多文件多步骤时再加任务拆解/执行顺序
33
+ - 仍须与入口文档同名落盘
34
+ 3. 停下,输出 plan 草稿全文给用户过目,一句"可以"即确认。**逐件确认**:入口文档 / spec / plan 各自的确认点不得并作一次,plan 草稿须全文过目(不接受摘要代替)
35
+ 4. 确认后 → plan 状态 draft → approved,并在 plan 内补「确认与复核」节(确认结果 + 日期)。确认环节须在文档留痕:`approved` 是确认的机器可见态,`done` 只在关单出现,禁从 `draft` 直跳 `done`
36
+
37
+ ### 进计划模式列改动
38
+
39
+ 5. **不要直接动代码**。先进「计划模式」:
40
+ - **先拉同模块历史坑**:按入口文档 `模块:` 跑 `node .agents/scripts/kb-search.mjs --scope workflow --module <token> "<关键词>"`——命中 incident 的根因 / 防复发条目列入改动自查
41
+ - 列改动文件清单(读后改 vs 新建)
42
+ - 列步骤(改 X 文件做 Y → 改 Z 文件做 W)
43
+ - 标注触达红线(对照根 `AGENTS.md`「Working rules」+ 改动域目录级 `AGENTS.md`(如有))
44
+ 6. 停下,等用户确认改动清单
45
+
46
+ ### 实现
47
+
48
+ 7. 确认后按 plan 顺序推进;每个任务对照判据自验。若任务满足 `.agents/roles/implementer.md` 的边界,主智能体可将单个工作包连同必读材料、授权文件和验收条件委派给 `implementer`;否则由主智能体执行
49
+ 8. 自验:按改动域跑 `项目构建命令` / `项目类型检查命令` / `项目测试命令`(对应编译 / 前端类型检查 / 自动化测试;静态门全集见 `test.md` §1)
50
+ 9. **项目架构红线**:数据访问模式、公共字段写入等以根与目录级 `AGENTS.md`(如有)红线为准,不自创旁路
51
+ 10. **L3 且含 schema 变更时**:schema 变更走项目约定的维护方式(人工 SQL / 迁移工具,以目录级 `AGENTS.md` 或 spec 约束为准),不得擅自引入项目未采用的迁移机制(仅运行时 / 管线类 L3 无此约束)
52
+ 11. 实现中若需偏离已确认的 plan(改公共接口 / 动 plan 未列文件 / 引新依赖 / 影响任务判据)→ 停下,向用户说明原因与影响,确认后再继续
53
+
54
+ ## 技能辅助(可选,宿主级 skills)
55
+
56
+ - plan 任务多、依赖复杂时:用 `to-tickets` 拆 tracer-bullet 任务单(每张带阻塞边);结果回填 plan「任务拆解」节,不发布外部 tracker
57
+ - 后端逻辑改动:有测试基建的用 `tdd` 测试先行(红→绿→重构);整体实现纪律可用 `implement` 驱动
58
+ - 遇合并冲突:用 `resolving-merge-conflicts` 按意图逐 hunk 处理
59
+ - 技能不改变两道确认门、红线检查与委派边界
60
+
61
+ ## 红线提示(本阶段高频踩)
62
+
63
+ - 红线(架构 / 数据 / 安全等)正文与豁免见根与目录级 `AGENTS.md`(如有)——已由 pre-commit 机器门强制的部分以钩子为准,写代码前先过一遍目录级 AGENTS
64
+
65
+ ## 子代理调用约定
66
+
67
+ - plan 起草、风险判断、改动清单与两道确认门由主智能体负责;仅两道确认后范围明确的工作包可委派 `implementer`,公共接口 / Schema / 依赖 / 安全 / 权限 / 破坏性操作不得委派。
68
+ - 子智能体返回后,主智能体须检查 diff 与授权文件范围、确认未覆盖既有修改并重跑相关验证;不支持子智能体时 `fallback: main`,验收标准不变。
69
+
70
+ ## 确认后
71
+
72
+ - plan 状态 → done(实现完成;intent / spec 关单在 test)
73
+ - commit:L1+ 入口文档 / spec / plan 随代码同一提交(Conventional Commits 中文 feat/fix/refactor/perf);docs 豁免与 bootstrap 可单独 `docs(*)`
74
+ - 进 `next: .agents/commands/test.md`
@@ -0,0 +1,65 @@
1
+ ---
2
+ description: Deploy 阶段:prod 上线清单(静态门 + 配对检查 + 回归 + DB + 授权 + 24h 观察;文档关单在 test)
3
+ stage: Deploy
4
+ triggers:
5
+ - "跑下 release"
6
+ - "可以上了"
7
+ - "过一遍 release 清单"
8
+ - "准备上线"
9
+ approval_required: true
10
+ next: .agents/commands/maintain.md(仅事故时)
11
+ ---
12
+
13
+ # Deploy · 上线清单
14
+
15
+ > 仅 prod。文档关单(验收勾验 / intent done)在 `test.md`,本阶段不重复关单。个人工作流:唯一授权人是用户本人——上线授权 = 用户一句确认 + `release/<日期>` tag 留痕,追溯靠 git,不依赖第二人审批。
16
+ > 环境定位:dev 为开发自测;staging 即人工验证环境(不设独立发布门);prod 上线必须过本清单。
17
+
18
+ ## 1. 静态门(必跑,全过方可继续)
19
+
20
+ 同 `test.md` §1(静态门全集:`项目构建命令` / `项目测试命令` / `项目类型检查命令` 与项目自有门禁;含环境注记与 Windows 取码注记),逐条跑完再继续。
21
+
22
+ ## 2. 文档闭环(必查)
23
+
24
+ - L2 / L3 变更:入口文档(`workflow/intents/` 或 `workflow/incidents/`) + `workflow/specs/` + `workflow/plans/` 三件同名配对齐全;intent/spec/plan 状态为 done(test 已关单)。若仍为 approved,先回 `test.md` 补关单再上
25
+ - 若属修复:`workflow/incidents/` 同时须满足复盘三件套完整(结构性修复 / 防复发验证 / 规范条目),状态 closed
26
+ - 新 intent 是否已立:根因属门禁缺位 / 规范未落地 / 系统性问题时强制立(即使修复已完成)
27
+
28
+ ## 3. 回归清单(必过)
29
+
30
+ - `workflow/regression-checklist.md` 中涉及模块的关键路径条目全过
31
+ - 项目自动化测试用例全过(`项目测试命令`;不得跳过失败用例)
32
+ - 历史同类 incident 的防复发验证条目(清单「防复发验证」节)逐条过
33
+
34
+ ## 4. 数据库(若涉及 schema 变更)
35
+
36
+ - 迁移 SQL 已按项目约定的 schema 变更流程验证并留档
37
+ - 实体映射与迁移 SQL 已人工核对一致
38
+ - 备份点已记录 + 回滚 SQL 就绪(L3 且含 schema 变更时强制)
39
+
40
+ ## 5. 上线授权(用户本人最后一关)
41
+
42
+ - 授权人:用户本人(个人工作流,无第二审批人);对话内一句"上"即授权
43
+ - 授权记录:打 `release/<日期>` tag + commit SHA——tag 是「prod 跑的是哪个 commit」的追溯依据,`git log` 可查
44
+ - 发布流水线(可选):若项目 CI/CD 配置了人工审批卡点,按提示在流水线平台自行批准;批准人仍是本人,不构成第二人评审
45
+ - 回滚决策人:用户本人
46
+ - 回滚预案(按 L3 类型分叉):含 schema 变更 → <备份点 + 回滚 SQL 路径>;仅运行时 / 管线(无 schema,如认证管线 / DI 注册 / 中间件) → 回退上一 `release/<日期>` tag + 配置开关回退
47
+ - 回滚 SQL 必须在 dev 演练执行过(没跑过的回滚脚本等于没有);回退版本类预案须确认上一 tag 可复现构建
48
+
49
+ ## 6. 上线后观察(用户观察,Maintain 阶段接手)
50
+
51
+ - 观察期:上线后 24h;由用户观察错误率 / 延迟 / 关键业务流(无监控时人工看)
52
+ - 闭环宣告:用户确认无异常后才算闭环;AI 不得自行宣告
53
+ - 异常回落:超阈值 → 生成 `workflow/incidents/YYYY-MM-DD-<主题>.md`,进 `maintain.md`(止血方案用户拍板,见 maintain.md §0)
54
+ - 事故响应:单用途账号 + 最小权限;不得跨智能体协作推送修复(参考 Anthropic 教训)
55
+
56
+ ## 子代理调用约定
57
+
58
+ - 上线清单、回滚方案、授权、tag 与事故决策均由主智能体处理,本阶段不委派子智能体;回滚方案(尤其含数据回滚 SQL)涉及数据安全,不得以通用实现子智能体代替主智能体判断与用户确认。
59
+
60
+ ## 确认后
61
+
62
+ - 打 `release/<日期>` tag(commit SHA 可追溯)
63
+ - 上线 + 用户观察 24h
64
+ - 用户确认无异常 → prod 闭环(文档 done 应已在 test 写好;若漏写在此补勾验收并置 done)
65
+ - 异常 → 进 `next: .agents/commands/maintain.md`