add-coder 0.3.34 → 0.3.37

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 (98) hide show
  1. package/README.en.md +101 -42
  2. package/README.md +63 -18
  3. package/dist/index.js +24 -6
  4. package/package.json +2 -2
  5. package/templates/.add-coder-src-hash.json +94 -39
  6. package/templates/adapters/claude/hooks/doc-format-guard.mjs +172 -84
  7. package/templates/adapters/claude/hooks/post-tool-use.mjs +59 -1
  8. package/templates/adapters/claude/hooks/prompt-submit.mjs +72 -0
  9. package/templates/adapters/claude/hooks/session-start.mjs +65 -1
  10. package/templates/adapters/codex/hooks/doc-format-guard.mjs +172 -84
  11. package/templates/adapters/codex/hooks/post-tool-use.mjs +59 -1
  12. package/templates/adapters/codex/hooks/prompt-submit.mjs +72 -0
  13. package/templates/adapters/codex/hooks/session-start.mjs +65 -1
  14. package/templates/adapters/qoder/hooks/doc-format-guard.mjs +172 -84
  15. package/templates/adapters/qoder/hooks/post-tool-use.mjs +59 -1
  16. package/templates/adapters/qoder/hooks/prompt-submit.mjs +72 -0
  17. package/templates/adapters/qoder/hooks/session-start.mjs +67 -1
  18. package/templates/adapters/trae/hooks/doc-format-guard.mjs +172 -84
  19. package/templates/adapters/trae/hooks/post-tool-use.mjs +59 -1
  20. package/templates/adapters/trae/hooks/prompt-submit.mjs +72 -0
  21. package/templates/adapters/trae/hooks/session-start.mjs +65 -1
  22. package/templates/adapters/vscode/hooks/doc-format-guard.mjs +172 -84
  23. package/templates/adapters/vscode/hooks/post-tool-use.mjs +59 -1
  24. package/templates/adapters/vscode/hooks/prompt-submit.mjs +72 -0
  25. package/templates/adapters/vscode/hooks/session-start.mjs +65 -1
  26. package/templates/core/governance/doc-format-guard.ts +29 -112
  27. package/templates/core/governance/post-tool-router.ts +33 -1
  28. package/templates/core/governance/prompt-router.ts +47 -0
  29. package/templates/core/governance/session-start-guard.ts +48 -1
  30. package/templates/core/prisma/add.prisma +203 -0
  31. package/templates/core/scripts/db-ensure.sh +92 -2
  32. package/templates/core/scripts/mcp-server/shared/db-types.ts +119 -0
  33. package/templates/core/scripts/mcp-server/shared/hitl-create-policy.ts +27 -0
  34. package/templates/core/scripts/mcp-server/shared/hitl-proposal-content.ts +110 -0
  35. package/templates/core/scripts/mcp-server/shared/hitl-widget-instance.ts +85 -0
  36. package/templates/core/scripts/mcp-server/shared/memory/calibration/batch-fit.ts +250 -0
  37. package/templates/core/scripts/mcp-server/shared/memory/calibration/feedback-stats.ts +101 -0
  38. package/templates/core/scripts/mcp-server/shared/memory/calibration/unit-state.ts +224 -0
  39. package/templates/core/scripts/mcp-server/shared/memory/domain/conflicts.ts +59 -0
  40. package/templates/core/scripts/mcp-server/shared/memory/domain/dedup.ts +45 -0
  41. package/templates/core/scripts/mcp-server/shared/memory/domain/errors.ts +33 -0
  42. package/templates/core/scripts/mcp-server/shared/memory/domain/handoff-digest.ts +92 -0
  43. package/templates/core/scripts/mcp-server/shared/memory/domain/metric-candidate.ts +79 -0
  44. package/templates/core/scripts/mcp-server/shared/memory/domain/scope.ts +94 -0
  45. package/templates/core/scripts/mcp-server/shared/memory/domain/secrets.ts +50 -0
  46. package/templates/core/scripts/mcp-server/shared/memory/domain/state-machine.ts +90 -0
  47. package/templates/core/scripts/mcp-server/shared/memory/embedding/index.ts +117 -0
  48. package/templates/core/scripts/mcp-server/shared/memory/embedding/local-onnx.ts +105 -0
  49. package/templates/core/scripts/mcp-server/shared/memory/embedding/openai-compatible.ts +87 -0
  50. package/templates/core/scripts/mcp-server/shared/memory/jobs/consolidation.ts +226 -0
  51. package/templates/core/scripts/mcp-server/shared/memory/jobs/evidence-collector.ts +153 -0
  52. package/templates/core/scripts/mcp-server/shared/memory/jobs/snapshot.ts +114 -0
  53. package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-recall.ts +134 -0
  54. package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-writer.ts +217 -0
  55. package/templates/core/scripts/mcp-server/shared/memory/metrics/stage-words.ts +69 -0
  56. package/templates/core/scripts/mcp-server/shared/memory/retrieval/context-builder.ts +89 -0
  57. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/pg.ts +139 -0
  58. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql +29 -0
  59. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite.ts +106 -0
  60. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fusion.ts +43 -0
  61. package/templates/core/scripts/mcp-server/shared/memory/retrieval/pipeline.ts +285 -0
  62. package/templates/core/scripts/mcp-server/shared/memory/retrieval/query-terms.ts +31 -0
  63. package/templates/core/scripts/mcp-server/shared/memory/retrieval/recall-writer.ts +87 -0
  64. package/templates/core/scripts/mcp-server/shared/memory/retrieval/reranker.ts +116 -0
  65. package/templates/core/scripts/mcp-server/shared/memory/retrieval/types.ts +52 -0
  66. package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/pgvector.ts +143 -0
  67. package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/sqlite-vec.ts +118 -0
  68. package/templates/core/scripts/mcp-server/shared/memory/switches.ts +39 -0
  69. package/templates/core/scripts/mcp-server/shared/review-files.ts +22 -0
  70. package/templates/core/scripts/mcp-server/shared/runtime-freshness.ts +235 -0
  71. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +37 -0
  72. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +40 -1
  73. package/templates/core/scripts/mcp-server/tools/hitl.ts +108 -42
  74. package/templates/core/scripts/mcp-server/tools/index.ts +7 -1
  75. package/templates/core/scripts/mcp-server/tools/memory-compat.ts +258 -0
  76. package/templates/core/scripts/mcp-server/tools/memory.ts +654 -0
  77. package/templates/core/scripts/mcp-server/tools/plan.ts +8 -3
  78. package/templates/core/scripts/mcp-server/tools/review.ts +10 -7
  79. package/templates/core/scripts/mcp-server.ts +36 -0
  80. package/templates/core/templates/checklist-template.md +13 -0
  81. package/templates/core/templates/review-implementation-template.md +24 -0
  82. package/templates/core/templates/review-template.md +16 -0
  83. package/templates/core/validation/index.ts +136 -0
  84. package/templates/core/validation/policy.ts +91 -0
  85. package/templates/core/validation/registry.ts +61 -0
  86. package/templates/core/validation/schema-validator.ts +277 -0
  87. package/templates/core/validation/validators/add-route.ts +32 -0
  88. package/templates/core/validation/validators/checklist.ts +48 -0
  89. package/templates/core/validation/validators/handoff.ts +46 -0
  90. package/templates/core/validation/validators/hitl.ts +22 -0
  91. package/templates/core/validation/validators/index.ts +52 -0
  92. package/templates/core/validation/validators/plan.ts +20 -0
  93. package/templates/core/validation/validators/report.ts +16 -0
  94. package/templates/core/validation/validators/review.ts +30 -0
  95. package/templates/core/validation/validators/spec.ts +25 -0
  96. package/templates/core/validation/validators/tasks.ts +40 -0
  97. package/templates/core/validation/validators/types.ts +32 -0
  98. package/templates/core/vocabulary/add-governance-vocabulary.md +18 -0
@@ -123,14 +123,84 @@ build_target() {
123
123
  [ -n "$tables" ] && EXCLUDE_ARGS=(--exclude "$tables")
124
124
  echo ">>> Atlas 同步(共库模式: 仅 ADD 治理表,其余 $(echo "$tables" | tr ',' '\n' | wc -l) 张表排除)..."
125
125
  fi
126
+ # Atlas 自身的版本记录 schema 不属于期望态,必须排除,否则 diff 会生成 DROP SCHEMA ... CASCADE
127
+ EXCLUDE_ARGS+=(--exclude atlas_schema_revisions)
126
128
  TARGET_URL="${TARGET_URL}?sslmode=disable"
127
129
  }
128
130
 
129
- # baseline 生成(同源:Prisma schema SQL,过滤 Prisma 7 ◇ 提示)
131
+ # ③.5 raw 对象登记(schema 表达不了的对象:trgm 索引 / 向量层;单一事实源)
132
+ # 期望态必须能表达这些对象,否则 diff 把「raw 对象」判成多余并生成 DROP(review 发现 #2)。
133
+ # 约束:Atlas dev-url 必须是干净库且具备同名扩展(否则 gin_trgm_ops / vector 类型无法解析);
134
+ # 本项目的 dev 库是一次性沙箱 → 每次 diff 前从 template1 重建(见 prepare_atlas_dev_db)。
135
+ RAW_OBJECTS_SQL="prisma/raw-objects.sql"
136
+ RAW_OBJECTS_VECTOR_SQL="prisma/raw-objects-vector.sql"
137
+
138
+ # template1 扩展引导(幂等):新建库(含 Atlas dev 沙箱库)从 template1 继承扩展——
139
+ # 缺扩展时期望态里的 gin_trgm_ops / vector 无法解析,diff 直接报错中止。
140
+ # 镜像不含扩展时静默跳过(记忆检索按 fts-only 合法降级,不硬失败)。
141
+ ensure_template1_extensions() {
142
+ local c="$1" u="$2"
143
+ podman exec "$c" true >/dev/null 2>&1 || return 0
144
+ podman exec "$c" psql -U "$u" -d template1 -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm;" >/dev/null 2>&1 || true
145
+ podman exec "$c" psql -U "$u" -d template1 -tAc "CREATE EXTENSION IF NOT EXISTS vector;" >/dev/null 2>&1 || true
146
+ }
147
+
148
+ # dev 沙箱库准备:DROP + CREATE TEMPLATE template1(template1 内已装 pg_trgm/vector)
149
+ prepare_atlas_dev_db() {
150
+ # 容器 / 超级用户 / 库名探测:provisionDevUrl 实际建的是 `{project}-add-dev`(用户 postgres、库 dev),
151
+ # 历史环境可能是 `{project}-dev` / admin / `{project}-add-dev` 库——依次探测取第一个可用,
152
+ # 否则 DROP/CREATE DATABASE 与扩展引导会静默空转(沙箱库拿不到 pg_trgm / vector)。
153
+ local c="${PROJECT_NAME:-add-project}-add-dev" u="" d="${ATLAS_DEV_DB:-}"
154
+ podman exec "$c" true >/dev/null 2>&1 || c="${PROJECT_NAME:-add-project}-dev"
155
+ podman exec "$c" true >/dev/null 2>&1 || { echo ">>> [dev-url] 容器不可达,跳过重建(依赖现有 dev 库)"; return 0; }
156
+ for cand in "${ATLAS_DEV_USER:-postgres}" postgres "${DATABASE_USER:-admin}" admin; do
157
+ if [ -n "$cand" ] && podman exec "$c" psql -U "$cand" -d postgres -tAc "SELECT 1;" >/dev/null 2>&1; then
158
+ u="$cand"
159
+ break
160
+ fi
161
+ done
162
+ if [ -z "$u" ]; then
163
+ echo ">>> [dev-url] 未能确定 $c 的超级用户,跳过重建(沿用现有 dev 库)"
164
+ return 0
165
+ fi
166
+ if [ -z "$d" ]; then
167
+ # 库名优先取 ATLAS_DEV_URL 的路径段(dev),取不到再退回 add-project-dev
168
+ d="$(printf '%s' "${ATLAS_DEV_URL:-}" | sed -E 's/[?].*$//; s#.*/##')"
169
+ [ -n "$d" ] || d="add-project-dev"
170
+ fi
171
+ ensure_template1_extensions "$c" "$u"
172
+ if [ "${ADD_DB_KEEP_DEV:-}" = "yes" ]; then
173
+ echo ">>> [dev-url] ADD_DB_KEEP_DEV=yes:沿用现有 dev 库"
174
+ return 0
175
+ fi
176
+ podman exec "$c" psql -U "$u" -d postgres -tAc "DROP DATABASE IF EXISTS \"$d\";" >/dev/null 2>&1 || true
177
+ if podman exec "$c" psql -U "$u" -d postgres -tAc "CREATE DATABASE \"$d\" TEMPLATE template1;" >/dev/null 2>&1; then
178
+ echo ">>> [dev-url] 已从 template1 重建沙箱库 $d(干净 + 扩展齐备)"
179
+ else
180
+ echo ">>> [dev-url] 重建 $d 失败,沿用现有库(若解析报错请检查 template1 扩展)"
181
+ fi
182
+ }
183
+
184
+ # 目标库是否具备 pgvector(决定是否把向量段并入期望态)
185
+ has_pgvector_in_target() {
186
+ local c="${PROJECT_NAME:-add-project}-postgres" u="${DATABASE_USER:-admin}" d="${PROJECT_NAME:-add-project}" has
187
+ has="$(podman exec "$c" psql -U "$u" -d "$d" -tAc "SELECT 1 FROM pg_available_extensions WHERE name='vector' LIMIT 1;" 2>/dev/null || true)"
188
+ [ "$has" = "1" ]
189
+ }
190
+
191
+ # ④ baseline 生成(同源:Prisma schema SQL + raw 对象登记段,过滤 Prisma 7 ◇ 提示)
130
192
  generate_baseline() {
131
193
  BASELINE_SQL="$(mktemp /tmp/atlas-target.XXXXXX.sql)"
132
194
  trap 'rm -f "$BASELINE_SQL"' EXIT
133
195
  npx prisma migrate diff --from-empty --to-schema "$SCHEMA_TARGET" --script 2>/dev/null | sed '/^◇/d' > "$BASELINE_SQL"
196
+ if [ -f "$RAW_OBJECTS_SQL" ]; then
197
+ printf '\n-- ===== raw objects registry =====\n' >> "$BASELINE_SQL"
198
+ cat "$RAW_OBJECTS_SQL" >> "$BASELINE_SQL"
199
+ fi
200
+ if has_pgvector_in_target && [ -f "$RAW_OBJECTS_VECTOR_SQL" ]; then
201
+ printf '\n-- ===== raw objects registry (vector) =====\n' >> "$BASELINE_SQL"
202
+ cat "$RAW_OBJECTS_VECTOR_SQL" >> "$BASELINE_SQL"
203
+ fi
134
204
  }
135
205
 
136
206
  # ⑤ diff 检测(SQL 语句特征判定:Atlas 无变更时输出 "Schemas are synced..." 非空,不算变更)
@@ -140,10 +210,28 @@ run_atlas_diff() {
140
210
  echo "$DIFF_SQL" | grep -qE "^(CREATE|ALTER|DROP|COMMENT|-- *(Create|Modify|Drop))"
141
211
  }
142
212
 
143
- # ⑥ apply(确认门槛:交互输出 SQL确认 → apply;拒绝则跳过)
213
+ # ⑥ apply(双门槛:DROP 守卫交互确认 → apply;任一不通过则跳过)
214
+ # DROP 守卫(Plan 轮 3 前置 / runtime review 发现 #2):破坏性语句一律拒绝——
215
+ # raw SQL 对象(如 pg_trgm GIN 索引、pgvector 列/索引)无法被 Prisma schema 表达,
216
+ # diff 会误判为「多余对象」并生成 DROP;若放行,索引会被静默删除且不报错。
217
+ # 显式放行需人工设 ADD_DB_ALLOW_DROP=yes,并在迁移评审中登记(禁止默认自动放行)。
144
218
  apply_atlas_diff() {
145
219
  echo "=== 待应用 diff SQL(前 60 行)==="
146
220
  echo "$DIFF_SQL" | head -60
221
+ local drops
222
+ drops="$(printf '%s\n' "$DIFF_SQL" | grep -nE '^DROP |DROP COLUMN|DROP CONSTRAINT|DROP INDEX|DROP TABLE|DROP TYPE|DROP SCHEMA' || true)"
223
+ if [ -n "$drops" ]; then
224
+ echo "!!! 检测到破坏性语句(DROP),已拒绝应用:"
225
+ printf '%s\n' "$drops" | sed 's/^/ /'
226
+ echo " 处置建议:"
227
+ echo " ① 若是无法被 schema 表达的 raw SQL 对象(索引/扩展/向量列)→ 核对登记清单 prisma/raw-objects.sql、prisma/raw-objects-vector.sql;"
228
+ echo " ② 确需删除时人工执行,并在迁移评审中登记(能力矩阵 §六 已登记同类缺口)。"
229
+ if [ "${ADD_DB_ALLOW_DROP:-}" != "yes" ]; then
230
+ echo " (如需在评审后放行:ADD_DB_ALLOW_DROP=yes 重新执行)"
231
+ return 1
232
+ fi
233
+ echo " ⚠️ ADD_DB_ALLOW_DROP=yes 已设置:放行破坏性变更(确认已完成迁移评审)"
234
+ fi
147
235
  read -rp "应用以上 schema 变更?[y/N] " ANS
148
236
  if [ "$ANS" = "y" ] || [ "$ANS" = "yes" ]; then
149
237
  atlas_cmd schema apply --url "$TARGET_URL" --to "file://$BASELINE_SQL" --dev-url "$ATLAS_DEV_URL" "${EXCLUDE_ARGS[@]}"
@@ -173,6 +261,8 @@ atlas_sync() {
173
261
  echo "!!! ATLAS_DEV_URL 未配置。请运行 add-coder init(分库引导自动创建 {project}-add-dev 常驻容器并登记)或手动配置"
174
262
  return 1
175
263
  fi
264
+ ensure_template1_extensions "${PROJECT_NAME:-add-project}-postgres" "${DATABASE_USER:-admin}"
265
+ prepare_atlas_dev_db
176
266
  build_target
177
267
  generate_baseline
178
268
  if run_atlas_diff; then
@@ -138,6 +138,118 @@ export const CollabContractRowSchema = z.looseObject({
138
138
  updatedAt: z.date(),
139
139
  })
140
140
 
141
+ // ===== Agent Memory 行 schema(对齐 prisma/add.prisma,Plan: add-coder-agent-memory-plan-v2) =====
142
+
143
+ export const MemoryKindSchema = z.enum([
144
+ "DECISION", "CONSTRAINT", "PITFALL", "FAILURE", "LESSON",
145
+ "PATTERN", "CONVENTION", "FACT", "HANDOFF_DIGEST", "HYPOTHESIS",
146
+ ])
147
+ export const MemoryStatusSchema = z.enum([
148
+ "CANDIDATE", "PENDING", "ACTIVE", "STALE", "SUPERSEDED", "REJECTED", "ARCHIVED",
149
+ ])
150
+ export const MemoryScopeTypeSchema = z.enum([
151
+ "ORGANIZATION", "REPOSITORY", "BRANCH", "MODULE", "PATH", "SYMBOL", "PLAN", "SPEC",
152
+ ])
153
+ export const MemorySourceTypeSchema = z.enum([
154
+ "PLAN", "SPEC", "DPS_GATE", "DEV_OPERATION", "RAHS_GATE", "HANDOFF", "MANUAL", "IMPORT",
155
+ ])
156
+ export const EmbeddingStateSchema = z.enum(["DISABLED", "PENDING", "READY", "FAILED", "STALE"])
157
+ export const RecallOutcomeSchema = z.enum([
158
+ "UNKNOWN", "USED", "USEFUL", "IRRELEVANT", "OUTDATED", "CONTRADICTED", "HARMFUL",
159
+ ])
160
+
161
+ export const AddMemoryRowSchema = z.looseObject({
162
+ id: z.string(),
163
+ kind: MemoryKindSchema,
164
+ status: MemoryStatusSchema,
165
+ topic: z.string(),
166
+ content: z.string(),
167
+ summary: z.string().nullable(),
168
+ scopeType: MemoryScopeTypeSchema,
169
+ scopeValue: z.string(),
170
+ repositoryRef: z.string(),
171
+ importance: z.number(),
172
+ confidence: z.number(),
173
+ validFrom: z.date(),
174
+ validUntil: z.date().nullable(),
175
+ supersededById: z.string().nullable(),
176
+ contentHash: z.string(),
177
+ embeddingModel: z.string().nullable(),
178
+ embeddingDim: z.number().nullable(),
179
+ embeddingState: EmbeddingStateSchema,
180
+ createdBy: z.string().nullable(),
181
+ approvedBy: z.string().nullable(),
182
+ approvedAt: z.date().nullable(),
183
+ metadata: z.unknown().nullable(),
184
+ createdAt: z.date(),
185
+ updatedAt: z.date(),
186
+ })
187
+
188
+ export const AddMemoryEvidenceRowSchema = z.looseObject({
189
+ id: z.string(),
190
+ repositoryRef: z.string(),
191
+ sourceType: MemorySourceTypeSchema,
192
+ sourceRef: z.string(),
193
+ planKeyword: z.string().nullable(),
194
+ excerpt: z.string(),
195
+ contentHash: z.string(),
196
+ occurredAt: z.date().nullable(),
197
+ metadata: z.unknown().nullable(),
198
+ createdAt: z.date(),
199
+ })
200
+
201
+ export const AddMemoryEvidenceLinkRowSchema = z.looseObject({
202
+ memoryId: z.string(),
203
+ evidenceId: z.string(),
204
+ relation: z.string().nullable(),
205
+ createdAt: z.date(),
206
+ })
207
+
208
+ export const AddMetricSnapshotRowSchema = z.looseObject({
209
+ id: z.string(),
210
+ repositoryRef: z.string(),
211
+ metricType: z.string(),
212
+ value: z.number(),
213
+ baseline: z.number().nullable(),
214
+ delta: z.number().nullable(),
215
+ unit: z.string().nullable(),
216
+ planKeyword: z.string().nullable(),
217
+ specRef: z.string().nullable(),
218
+ commitSha: z.string().nullable(),
219
+ sourceRef: z.string(),
220
+ metadata: z.unknown().nullable(),
221
+ measuredAt: z.date(),
222
+ })
223
+
224
+ export const AddMemoryRecallRowSchema = z.looseObject({
225
+ id: z.string(),
226
+ repositoryRef: z.string(),
227
+ query: z.string(),
228
+ stage: z.string(),
229
+ consumerRef: z.string().nullable(),
230
+ scopeContext: z.unknown(),
231
+ candidateIds: z.unknown(),
232
+ selectedIds: z.unknown(),
233
+ scoreBreakdown: z.unknown(),
234
+ exclusionReasons: z.unknown().nullable(),
235
+ rankingVersion: z.string(),
236
+ tokenBudget: z.number(),
237
+ injectedTokens: z.number(),
238
+ latencyMs: z.number().nullable(),
239
+ degradedMode: z.string().nullable(),
240
+ createdAt: z.date(),
241
+ })
242
+
243
+ export const AddMemoryRecallItemRowSchema = z.looseObject({
244
+ recallId: z.string(),
245
+ memoryId: z.string(),
246
+ selected: z.boolean(),
247
+ rank: z.number().nullable(),
248
+ outcome: RecallOutcomeSchema,
249
+ feedback: z.string().nullable(),
250
+ updatedAt: z.date(),
251
+ })
252
+
141
253
  // ===== 类型派生(单一真源:schema → 类型) =====
142
254
 
143
255
  export type PlanRow = z.infer<typeof PlanRowSchema>
@@ -147,6 +259,12 @@ export type AuditLogRow = z.infer<typeof AuditLogRowSchema>
147
259
  export type DevOperationRow = z.infer<typeof DevOperationRowSchema>
148
260
  export type AddUserRow = z.infer<typeof AddUserRowSchema>
149
261
  export type CollabContractRow = z.infer<typeof CollabContractRowSchema>
262
+ export type AddMemoryRow = z.infer<typeof AddMemoryRowSchema>
263
+ export type AddMemoryEvidenceRow = z.infer<typeof AddMemoryEvidenceRowSchema>
264
+ export type AddMemoryEvidenceLinkRow = z.infer<typeof AddMemoryEvidenceLinkRowSchema>
265
+ export type AddMetricSnapshotRow = z.infer<typeof AddMetricSnapshotRowSchema>
266
+ export type AddMemoryRecallRow = z.infer<typeof AddMemoryRecallRowSchema>
267
+ export type AddMemoryRecallItemRow = z.infer<typeof AddMemoryRecallItemRowSchema>
150
268
 
151
269
  // ===== 查询参数(Prisma 最常用子集,结构化约束 + 运算符支持) =====
152
270
 
@@ -159,6 +277,7 @@ export interface QueryArgs<T> {
159
277
  orderBy?: { [K in keyof T]?: OrderDirection }
160
278
  take?: number
161
279
  skip?: number
280
+ cursor?: Record<string, unknown>
162
281
  include?: Record<string, unknown>
163
282
  }
164
283
 
@@ -0,0 +1,27 @@
1
+ /*
2
+ * create_hitl 的交互裁决(手写模块;**不要放进 hitl-interaction.strategy.ts**——
3
+ * 那是 `hitl-interaction-rules.toml` 的生成产物,任何手写逻辑都会被 generate 覆盖)。
4
+ *
5
+ * 背景(2026-09-14 修复 Codex 空转):
6
+ * 客户端按安装环境裁决交互方式:Qoder=genui、Codex=mcpApps、其余=inputRequired。
7
+ * `update_hitl` 早已有 `mcpApps` 分支(引导走 core widget),但 `create_hitl` **漏了这一支**,
8
+ * 于是 Codex 下走 elicitation 弹框 → 客户端不展示 → 一直"还在要输入" →
9
+ * 连续 8 轮后以 `inputRequired.maxRounds` 失败(实测报 `still required input after 8 rounds`)。
10
+ */
11
+
12
+ /**
13
+ * create_hitl 是否需要跳过"创建确认弹框":
14
+ * - `_fallback` / `_use_genui`:调用方显式声明的无弹框路径(既有语义);
15
+ * - **`mcpApps`(Codex)**:审批交互在 core widget(`render_hitl_approval`)里完成,
16
+ * 此处直接产出 DRAFT 提案,再由 widget 供用户拍板。
17
+ */
18
+ export function shouldSkipHitlCreateDialog(
19
+ mode: string, // 运行期取 toml 裁决值,收窄到 string 边界
20
+ flags: { fallback?: boolean; useGenui?: boolean; mcpApps?: boolean } = {},
21
+ ): boolean {
22
+ /*
23
+ * `_mcp_apps` 显式开关(2026-09-14 自 farm-agent 回灌):环境裁决之外再给调用方一个强制入口——
24
+ * 适配其它客户端/build、或需要确定性地走 widget 流程时不必依赖 toml 探测结果。
25
+ */
26
+ return Boolean(flags.useGenui || flags.fallback || flags.mcpApps) || mode === "mcpApps"
27
+ }
@@ -0,0 +1,110 @@
1
+ /*
2
+ * HITL 提案文档内容(生成 + 裁决回写)
3
+ *
4
+ * 为什么抽出来(2026-09-14 修复"生成器不满足自身 schema"):
5
+ * `create_hitl` 原先把 markdown 直接内联在工具体里拼字符串,产物**缺 `## 审批结论`**,
6
+ * 而真源 `templates/core/templates/hitl-template.md` 与 `hitl-template.schema.json`
7
+ * 都把它列为必需章节;同时 `update_hitl` 只改状态行与维度列,**裁决结论不回写文档**——
8
+ * 结果 6/6 `*.hitl.md` 不满足自身 schema,"文件通道"残缺(结论只在 DB 与哨兵里)。
9
+ * 抽成纯函数后:生成物可用同一校验层直接断言(tests/hitl-proposal-content.test.ts)。
10
+ */
11
+
12
+ /** 真源模板中的必需章节名(改这里必须同时改 hitl-template.md / .schema.json) */
13
+ export const HITL_VERDICT_HEADING = "## 审批结论"
14
+
15
+ export interface HitlDimension {
16
+ name: string
17
+ content?: string
18
+ }
19
+
20
+ export interface BuildHitlProposalInput {
21
+ planName: string
22
+ round: number
23
+ type: string
24
+ createdAt: string
25
+ dimensions?: HitlDimension[]
26
+ }
27
+
28
+ const escapeCell = (value: string) => value.replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>")
29
+
30
+ /** 生成 HITL 提案 markdown(与 `hitl-template.md` 章节对齐) */
31
+ export function buildHitlProposalMarkdown(input: BuildHitlProposalInput): string {
32
+ const dims = input.dimensions ?? []
33
+ const tableRows = dims.length > 0
34
+ ? dims.map((d, i) => `| ${i + 1} | ${d.name} | ${escapeCell(d.content ?? "")} | 同意/驳回 |`).join("\n")
35
+ : [
36
+ "| 1 | 实施主体 | | 同意/驳回 |",
37
+ "| 2 | 数据模型 | | 同意/驳回 |",
38
+ "| 3 | MCP 工具 | | 同意/驳回 |",
39
+ "| 4 | 文件命名 | | 同意/驳回 |",
40
+ "| 5 | 模板 + schema | | 同意/驳回 |",
41
+ "| 6 | 新增依赖 | | 同意/驳回 |",
42
+ "| 7 | 预计文件数 | | 同意/驳回 |",
43
+ "| 8 | 预计轮次 | | 同意/驳回 |",
44
+ ].join("\n")
45
+
46
+ return [
47
+ `# ${input.planName} — HITL 提案 (round ${input.round})`,
48
+ "",
49
+ `> 创建: ${input.createdAt} | 类型: ${input.type} | 状态: DRAFT`,
50
+ "",
51
+ "## HITL 计划总览",
52
+ "",
53
+ "请填写以下决策维度,人工审核后点击 update_hitl 弹框选择「同意/驳回」完成审批:",
54
+ "",
55
+ "| # | 维度 | 方案内容 | 决策 |",
56
+ "|---|------|----------|:----:|",
57
+ tableRows,
58
+ "",
59
+ HITL_VERDICT_HEADING,
60
+ "",
61
+ "> **tongyi**:方案通过。",
62
+ "> **bohui**:方案驳回,需修正后重新 create_hitl 发起下一轮。",
63
+ "",
64
+ "| 时间 | 决策 | 原因 |",
65
+ "|------|:----:|------|",
66
+ "| | | |",
67
+ "",
68
+ ].join("\n")
69
+ }
70
+
71
+ export interface HitlVerdict {
72
+ status: string
73
+ at: string
74
+ reason?: string
75
+ }
76
+
77
+ /** 结论表骨架(DRAFT 状态下为空白行;与 buildHitlProposalMarkdown 产出同形) */
78
+ export const HITL_VERDICT_TABLE = [
79
+ "| 时间 | 决策 | 原因 |",
80
+ "|------|:----:|------|",
81
+ ] as const
82
+
83
+ /** 保证「审批结论」章节存在(历史产物/旧生成器产物补骨架,不改动已有结论行) */
84
+ export function ensureHitlVerdictSection(content: string): string {
85
+ if (content.includes(HITL_VERDICT_HEADING)) return content
86
+ return content.trimEnd() + "\n\n" + [HITL_VERDICT_HEADING, "", ...HITL_VERDICT_TABLE, "| | | |", ""].join("\n")
87
+ }
88
+
89
+ /**
90
+ * 把裁决回写进提案:刷新状态行 + 在「审批结论」表写入(或覆盖)一行。
91
+ * 幂等:同一提案重复调用只保留最后一行,不追加重复行。
92
+ */
93
+ export function applyHitlDecisionToProposal(content: string, verdict: HitlVerdict): string {
94
+ const withStatus = ensureHitlVerdictSection(content).replace(/(状态:\s*)[A-Z_]+/, `$1${verdict.status}`)
95
+ const row = `| ${verdict.at} | ${verdict.status} | ${escapeCell(verdict.reason ?? "")} |`
96
+ const idx = withStatus.indexOf(HITL_VERDICT_HEADING)
97
+ const freshSection = [HITL_VERDICT_HEADING, "", "| 时间 | 决策 | 原因 |", "|------|:----:|------|", row, ""].join("\n")
98
+ if (idx < 0) {
99
+ // 历史产物(生成器修复前)没有该章节 → 补章节再写行,保证"文件通道"完整
100
+ return withStatus.trimEnd() + "\n\n" + freshSection
101
+ }
102
+ const lines = withStatus.slice(idx).split("\n")
103
+ const headerIdx = lines.findIndex((l) => l.startsWith("| 时间 |"))
104
+ if (headerIdx < 0) return withStatus.slice(0, idx) + freshSection
105
+ // 保留到分隔行,替换全部数据行;其后非表格内容(如追加说明)原样保留
106
+ const kept = lines.slice(headerIdx + 2).filter((l) => !l.trim().startsWith("|"))
107
+ while (kept.length > 0 && kept[0].trim() === "") kept.shift()
108
+ const head = withStatus.slice(0, idx) // 标题/元信息/维度表等「审批结论」之前的内容
109
+ return head + [...lines.slice(0, headerIdx + 2), row, ...(kept.length > 0 ? ["", ...kept] : [])].join("\n")
110
+ }
@@ -0,0 +1,85 @@
1
+ /*
2
+ * HITL 审批实例 HTML(Plan hitl-widget-runtime-gap §WidgetInstance / §RenderFallback)
3
+ *
4
+ * 为什么需要:MCP Apps 类客户端(Codex)才渲染 widget;其余环境(Qoder/弹框/无 UI)下
5
+ * 审批必须仍有确定性入口。本模块把 core widget 模板 + 本次提案的维度数据落成一份**可直接打开**的
6
+ * 实例 HTML(文件面板/浏览器均可),并把路径回给调用方(render_hitl_approval 的 fallback)。
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
9
+ import { join } from "node:path"
10
+
11
+ export const HITL_INSTANCE_DIR = "hitl"
12
+
13
+ export interface HitlInstanceInput {
14
+ planName: string
15
+ type: string
16
+ round: number
17
+ status: string
18
+ dimensions: { name: string; content: string }[]
19
+ templateHtml: string
20
+ }
21
+
22
+ function escapeHtml(value: string): string {
23
+ return value
24
+ .replace(/&/g, "&amp;")
25
+ .replace(/</g, "&lt;")
26
+ .replace(/>/g, "&gt;")
27
+ .replace(/"/g, "&quot;")
28
+ }
29
+
30
+ /** 生成实例 HTML:在模板 </body> 前注入 JSON 载荷 + 人类可读维度表(模板无占位符,故用注入而非替换) */
31
+ export function buildHitlInstanceHtml(input: HitlInstanceInput): string {
32
+ const payload = {
33
+ planName: input.planName,
34
+ type: input.type,
35
+ round: input.round,
36
+ status: input.status,
37
+ dimensions: input.dimensions,
38
+ }
39
+ const rows = input.dimensions
40
+ .map(
41
+ (d, i) =>
42
+ `<tr><td>${i + 1}</td><td>${escapeHtml(d.name)}</td><td>${escapeHtml(d.content)}</td><td>同意 / 驳回</td></tr>`,
43
+ )
44
+ .join("")
45
+ const injected = [
46
+ `<script id="hitl-instance-payload" type="application/json">${JSON.stringify(payload).replace(/</g, "\\u003c")}</script>`,
47
+ `<section id="hitl-fallback-panel" data-plan="${escapeHtml(input.planName)}" data-round="${input.round}">`,
48
+ `<h2>HITL 审批(${escapeHtml(input.planName)} · ${escapeHtml(input.type)} round ${input.round})</h2>`,
49
+ `<p>状态:${escapeHtml(input.status)} · 共 ${input.dimensions.length} 个维度。逐项确认后,把裁决结果告知 AI 即可落库(本页面为只读降级入口,不直接改库)。</p>`,
50
+ `<table><thead><tr><th>#</th><th>维度</th><th>方案内容</th><th>决策</th></tr></thead><tbody>${rows}</tbody></table>`,
51
+ `</section>`,
52
+ ].join("\n")
53
+
54
+ const bodyClose = input.templateHtml.match(/<\/body>/i)
55
+ if (bodyClose && bodyClose.index !== undefined) {
56
+ return (
57
+ input.templateHtml.slice(0, bodyClose.index) + injected + "\n" + input.templateHtml.slice(bodyClose.index)
58
+ )
59
+ }
60
+ return `${input.templateHtml}\n${injected}\n`
61
+ }
62
+
63
+ export interface WriteHitlInstanceResult {
64
+ htmlPath: string
65
+ created: boolean
66
+ }
67
+
68
+ /**
69
+ * 落盘实例 HTML(幂等:同 plan+round 覆盖写)。
70
+ * 模板缺失 → 抛明确错误(禁止静默返回空 HTML)。
71
+ */
72
+ export function writeHitlInstanceHtml(
73
+ input: Omit<HitlInstanceInput, "templateHtml"> & { projectRoot: string; magicDir: string },
74
+ ): WriteHitlInstanceResult {
75
+ const templatePath = join(input.projectRoot, input.magicDir, "templates", "hitl-approval-widget.html")
76
+ if (!existsSync(templatePath)) {
77
+ throw new Error(`HITL widget 模板缺失: ${templatePath}(请执行 add-coder sync)`)
78
+ }
79
+ const dir = join(input.projectRoot, input.magicDir, HITL_INSTANCE_DIR)
80
+ mkdirSync(dir, { recursive: true })
81
+ const htmlPath = join(dir, `${input.planName}-round${input.round}.html`)
82
+ const html = buildHitlInstanceHtml({ ...input, templateHtml: readFileSync(templatePath, "utf-8") })
83
+ writeFileSync(htmlPath, html, "utf-8")
84
+ return { htmlPath, created: true }
85
+ }