openclaw-hybrid-memory 2026.5.310 → 2026.6.10

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 (535) hide show
  1. package/api/plugin-runtime.ts +2 -0
  2. package/backends/facts-db/contradictions.ts +1 -1
  3. package/cli/cmd-extract-directives.ts +225 -11
  4. package/cli/cmd-extract-proposals.ts +5 -6
  5. package/cli/cmd-extract-reinforcement.ts +71 -0
  6. package/cli/cmd-feedback.ts +15 -9
  7. package/cli/commands/manage/register-reflection-pipeline.ts +247 -13
  8. package/cli/commands/manage/register-storage-maintenance.ts +224 -15
  9. package/cli/commands/manage/storage-stats-helpers.ts +13 -2
  10. package/cli/context.ts +9 -19
  11. package/cli/distill.ts +31 -1
  12. package/cli/register.ts +28 -38
  13. package/dist/api/plugin-runtime.js.map +1 -1
  14. package/dist/backends/agent-health-store.js.map +1 -1
  15. package/dist/backends/apitap-store.js.map +1 -1
  16. package/dist/backends/audit-store.js.map +1 -1
  17. package/dist/backends/base-sqlite-store.js.map +1 -1
  18. package/dist/backends/cost-tracker.js.map +1 -1
  19. package/dist/backends/credentials-db.js +2 -3
  20. package/dist/backends/credentials-db.js.map +1 -1
  21. package/dist/backends/crystallization-store.js.map +1 -1
  22. package/dist/backends/edict-store.js.map +1 -1
  23. package/dist/backends/event-bus.js.map +1 -1
  24. package/dist/backends/event-log.js.map +1 -1
  25. package/dist/backends/facts-db/cache-manager.js.map +1 -1
  26. package/dist/backends/facts-db/clusters.js.map +1 -1
  27. package/dist/backends/facts-db/contradictions.js +1 -1
  28. package/dist/backends/facts-db/contradictions.js.map +1 -1
  29. package/dist/backends/facts-db/crud.js.map +1 -1
  30. package/dist/backends/facts-db/db-connection.js.map +1 -1
  31. package/dist/backends/facts-db/entity-autolink.js.map +1 -1
  32. package/dist/backends/facts-db/entity-layer.js.map +1 -1
  33. package/dist/backends/facts-db/episodes.js.map +1 -1
  34. package/dist/backends/facts-db/fact-queries.js.map +1 -1
  35. package/dist/backends/facts-db/fact-read-queries.js.map +1 -1
  36. package/dist/backends/facts-db/facts-db-layer1.js.map +1 -1
  37. package/dist/backends/facts-db/facts-db-layer2.js.map +1 -1
  38. package/dist/backends/facts-db/facts-db-layer3.js.map +1 -1
  39. package/dist/backends/facts-db/fts-text.js.map +1 -1
  40. package/dist/backends/facts-db/generated-skills/policy.js.map +1 -1
  41. package/dist/backends/facts-db/generated-skills.js.map +1 -1
  42. package/dist/backends/facts-db/housekeeping.js.map +1 -1
  43. package/dist/backends/facts-db/links.js.map +1 -1
  44. package/dist/backends/facts-db/maintenance.js.map +1 -1
  45. package/dist/backends/facts-db/procedures/crud.js.map +1 -1
  46. package/dist/backends/facts-db/procedures/internal.js.map +1 -1
  47. package/dist/backends/facts-db/procedures/promotion.js.map +1 -1
  48. package/dist/backends/facts-db/procedures/search.js.map +1 -1
  49. package/dist/backends/facts-db/procedures/stats.js.map +1 -1
  50. package/dist/backends/facts-db/reinforcement.js.map +1 -1
  51. package/dist/backends/facts-db/row-mapper.js.map +1 -1
  52. package/dist/backends/facts-db/scan-cursors.js.map +1 -1
  53. package/dist/backends/facts-db/schema-bootstrap.js.map +1 -1
  54. package/dist/backends/facts-db/scope-sql.js.map +1 -1
  55. package/dist/backends/facts-db/search.js.map +1 -1
  56. package/dist/backends/facts-db/stats.js.map +1 -1
  57. package/dist/backends/facts-db/types.js.map +1 -1
  58. package/dist/backends/facts-db/variants.js.map +1 -1
  59. package/dist/backends/identity-reflection-store.js.map +1 -1
  60. package/dist/backends/issue-store.js.map +1 -1
  61. package/dist/backends/learnings-db.js.map +1 -1
  62. package/dist/backends/migrations/facts-migrations.js.map +1 -1
  63. package/dist/backends/migrations/procedures.js.map +1 -1
  64. package/dist/backends/narratives-db.js.map +1 -1
  65. package/dist/backends/persona-state-store.js.map +1 -1
  66. package/dist/backends/proposals-db.js.map +1 -1
  67. package/dist/backends/scope-filter-sql.js.map +1 -1
  68. package/dist/backends/sqlite-schema-meta.js.map +1 -1
  69. package/dist/backends/tool-proposal-store.js.map +1 -1
  70. package/dist/backends/vector-db/constants.js.map +1 -1
  71. package/dist/backends/vector-db/path-utils.js.map +1 -1
  72. package/dist/backends/vector-db/runtime-locks.js.map +1 -1
  73. package/dist/backends/vector-db/vector-db-class.js.map +1 -1
  74. package/dist/backends/wal.js.map +1 -1
  75. package/dist/backends/workflow-store.js.map +1 -1
  76. package/dist/benchmark/shadow-eval.js.map +1 -1
  77. package/dist/cli/active-tasks.js.map +1 -1
  78. package/dist/cli/backup.js.map +1 -1
  79. package/dist/cli/benchmark.js.map +1 -1
  80. package/dist/cli/cmd-backfill.js.map +1 -1
  81. package/dist/cli/cmd-config.js.map +1 -1
  82. package/dist/cli/cmd-credentials.js.map +1 -1
  83. package/dist/cli/cmd-demo.js.map +1 -1
  84. package/dist/cli/cmd-distill.js.map +1 -1
  85. package/dist/cli/cmd-doctor.js.map +1 -1
  86. package/dist/cli/cmd-examples.js.map +1 -1
  87. package/dist/cli/cmd-extract-daily.js.map +1 -1
  88. package/dist/cli/cmd-extract-directives.js +141 -10
  89. package/dist/cli/cmd-extract-directives.js.map +1 -1
  90. package/dist/cli/cmd-extract-procedures.js.map +1 -1
  91. package/dist/cli/cmd-extract-proposals.js +3 -2
  92. package/dist/cli/cmd-extract-proposals.js.map +1 -1
  93. package/dist/cli/cmd-extract-reinforcement.js +39 -0
  94. package/dist/cli/cmd-extract-reinforcement.js.map +1 -1
  95. package/dist/cli/cmd-extract-sessions.js.map +1 -1
  96. package/dist/cli/cmd-feedback.js +9 -4
  97. package/dist/cli/cmd-feedback.js.map +1 -1
  98. package/dist/cli/cmd-health.js.map +1 -1
  99. package/dist/cli/cmd-providers.js.map +1 -1
  100. package/dist/cli/cmd-selfcorrection.js.map +1 -1
  101. package/dist/cli/cmd-setup.js.map +1 -1
  102. package/dist/cli/cmd-status.js.map +1 -1
  103. package/dist/cli/cmd-store.js.map +1 -1
  104. package/dist/cli/cmd-user-friendly.js.map +1 -1
  105. package/dist/cli/cmd-verify.js.map +1 -1
  106. package/dist/cli/commands/manage/bindings.js.map +1 -1
  107. package/dist/cli/commands/manage/dream-cycle-followup.js.map +1 -1
  108. package/dist/cli/commands/manage/maintenance-heartbeat.js.map +1 -1
  109. package/dist/cli/commands/manage/register-agents-audit-runall.js.map +1 -1
  110. package/dist/cli/commands/manage/register-analyze-maintenance-logs.js.map +1 -1
  111. package/dist/cli/commands/manage/register-budget-proposals.js.map +1 -1
  112. package/dist/cli/commands/manage/register-config-cli.js.map +1 -1
  113. package/dist/cli/commands/manage/register-corrections-and-pipeline.js.map +1 -1
  114. package/dist/cli/commands/manage/register-corrections.js.map +1 -1
  115. package/dist/cli/commands/manage/register-council.js.map +1 -1
  116. package/dist/cli/commands/manage/register-credentials-scope.js.map +1 -1
  117. package/dist/cli/commands/manage/register-digest.js.map +1 -1
  118. package/dist/cli/commands/manage/register-lifecycle.js.map +1 -1
  119. package/dist/cli/commands/manage/register-procedure-lifecycle.js.map +1 -1
  120. package/dist/cli/commands/manage/register-reconcile-cron-ledgers.js.map +1 -1
  121. package/dist/cli/commands/manage/register-reflection-pipeline.js +144 -7
  122. package/dist/cli/commands/manage/register-reflection-pipeline.js.map +1 -1
  123. package/dist/cli/commands/manage/register-self-correction-feedback.js.map +1 -1
  124. package/dist/cli/commands/manage/register-storage-and-stats.js.map +1 -1
  125. package/dist/cli/commands/manage/register-storage-entities-decay.js.map +1 -1
  126. package/dist/cli/commands/manage/register-storage-graph-audit.js.map +1 -1
  127. package/dist/cli/commands/manage/register-storage-maintenance.js +152 -9
  128. package/dist/cli/commands/manage/register-storage-maintenance.js.map +1 -1
  129. package/dist/cli/commands/manage/register-validate-cron-exit.js.map +1 -1
  130. package/dist/cli/commands/manage/storage-stats-helpers.js +10 -3
  131. package/dist/cli/commands/manage/storage-stats-helpers.js.map +1 -1
  132. package/dist/cli/commands/register-manage-commands.js.map +1 -1
  133. package/dist/cli/config-feature-summaries.js.map +1 -1
  134. package/dist/cli/config-output-sink.js.map +1 -1
  135. package/dist/cli/distill-session-jsonl.js.map +1 -1
  136. package/dist/cli/distill.js +10 -1
  137. package/dist/cli/distill.js.map +1 -1
  138. package/dist/cli/global-verbose.js.map +1 -1
  139. package/dist/cli/goals.js.map +1 -1
  140. package/dist/cli/hybrid-mem-commander-utils.js.map +1 -1
  141. package/dist/cli/install/config-merge.js.map +1 -1
  142. package/dist/cli/install/cron-jobs.js.map +1 -1
  143. package/dist/cli/install/embedding-detect.js.map +1 -1
  144. package/dist/cli/install/run-install.js.map +1 -1
  145. package/dist/cli/install/workspace.js.map +1 -1
  146. package/dist/cli/proposals.js.map +1 -1
  147. package/dist/cli/register.js.map +1 -1
  148. package/dist/cli/shared.js.map +1 -1
  149. package/dist/cli/skills.js.map +1 -1
  150. package/dist/cli/task-queue-status.js.map +1 -1
  151. package/dist/cli/verified.js.map +1 -1
  152. package/dist/cli/verify/fact-count.js.map +1 -1
  153. package/dist/cli/verify/openclaw-config.js.map +1 -1
  154. package/dist/cli/verify/plugin-config-credentials.js.map +1 -1
  155. package/dist/cli/verify/sections/config-cron.js.map +1 -1
  156. package/dist/cli/verify/sections/embeddings.js.map +1 -1
  157. package/dist/cli/verify/sections/infrastructure.js.map +1 -1
  158. package/dist/cli/verify/sections/llm-models.js.map +1 -1
  159. package/dist/cli/verify/sections/reconcile.js.map +1 -1
  160. package/dist/cli/verify/verify-run-state.js.map +1 -1
  161. package/dist/cli/verify-llm-azure-auth.js.map +1 -1
  162. package/dist/cli/verify.js.map +1 -1
  163. package/dist/config/hybrid-schema.js.map +1 -1
  164. package/dist/config/index.js.map +1 -1
  165. package/dist/config/maintenance-fallback-policy.js.map +1 -1
  166. package/dist/config/parsers/capture.js.map +1 -1
  167. package/dist/config/parsers/core.js.map +1 -1
  168. package/dist/config/parsers/features.js.map +1 -1
  169. package/dist/config/parsers/index.js.map +1 -1
  170. package/dist/config/parsers/maintenance.js.map +1 -1
  171. package/dist/config/parsers/retrieval.js.map +1 -1
  172. package/dist/config/parsers/sensors.js.map +1 -1
  173. package/dist/config/skill-sections.js.map +1 -1
  174. package/dist/config/skill-size-limits.js.map +1 -1
  175. package/dist/config/types/agents.js.map +1 -1
  176. package/dist/config/types/bootstrap.js.map +1 -1
  177. package/dist/config/types/core.js.map +1 -1
  178. package/dist/config/types/index.js.map +1 -1
  179. package/dist/config/utils.js.map +1 -1
  180. package/dist/index-help.js.map +1 -1
  181. package/dist/index-testing-exports.js.map +1 -1
  182. package/dist/index.d.ts +1 -1
  183. package/dist/index.js +2 -2
  184. package/dist/index.js.map +1 -1
  185. package/dist/lifecycle/hook-resolution-api.js.map +1 -1
  186. package/dist/lifecycle/hooks.js +0 -1
  187. package/dist/lifecycle/hooks.js.map +1 -1
  188. package/dist/lifecycle/resolve-agent-id.js.map +1 -1
  189. package/dist/lifecycle/session-state.js.map +1 -1
  190. package/dist/lifecycle/stage-active-task.js.map +1 -1
  191. package/dist/lifecycle/stage-auth-failure.js.map +1 -1
  192. package/dist/lifecycle/stage-capture/run-capture.js.map +1 -1
  193. package/dist/lifecycle/stage-capture.js.map +1 -1
  194. package/dist/lifecycle/stage-cleanup.js.map +1 -1
  195. package/dist/lifecycle/stage-credential-hint.js.map +1 -1
  196. package/dist/lifecycle/stage-frustration.js.map +1 -1
  197. package/dist/lifecycle/stage-goal-stewardship.js.map +1 -1
  198. package/dist/lifecycle/stage-goal-subagent.js.map +1 -1
  199. package/dist/lifecycle/stage-injection.js +1 -1
  200. package/dist/lifecycle/stage-injection.js.map +1 -1
  201. package/dist/lifecycle/stage-recall/run-recall.js.map +1 -1
  202. package/dist/lifecycle/stage-recall.js.map +1 -1
  203. package/dist/lifecycle/stage-setup.js.map +1 -1
  204. package/dist/routes/dashboard/collectors.js.map +1 -1
  205. package/dist/routes/dashboard/html.js.map +1 -1
  206. package/dist/routes/dashboard/server.js.map +1 -1
  207. package/dist/routes/dashboard-graph.js.map +1 -1
  208. package/dist/routes/graphql-resolvers.js.map +1 -1
  209. package/dist/routes/graphql-server.js.map +1 -1
  210. package/dist/services/active-task-checkpoint.js.map +1 -1
  211. package/dist/services/active-task-injection.js.map +1 -1
  212. package/dist/services/active-task.js.map +1 -1
  213. package/dist/services/adaptive-catch-up-pacing.js +25 -0
  214. package/dist/services/adaptive-catch-up-pacing.js.map +1 -0
  215. package/dist/services/adaptive-maintenance-llm.js.map +1 -1
  216. package/dist/services/adaptive-model-limits.js.map +1 -1
  217. package/dist/services/ambient-retrieval.js.map +1 -1
  218. package/dist/services/apitap-service.js.map +1 -1
  219. package/dist/services/audit-health-exit-info.js.map +1 -1
  220. package/dist/services/audit-health-json.js.map +1 -1
  221. package/dist/services/auth-failure-detect.js.map +1 -1
  222. package/dist/services/auto-capture.js.map +1 -1
  223. package/dist/services/auto-classifier.js.map +1 -1
  224. package/dist/services/auto-skills-audit.js.map +1 -1
  225. package/dist/services/bootstrap-optional.js.map +1 -1
  226. package/dist/services/bootstrap-priority.js.map +1 -1
  227. package/dist/services/bootstrap.js.map +1 -1
  228. package/dist/services/capture-provenance.js.map +1 -1
  229. package/dist/services/capture-utils.js.map +1 -1
  230. package/dist/services/chat.js +22 -3
  231. package/dist/services/chat.js.map +1 -1
  232. package/dist/services/classification-scope.js.map +1 -1
  233. package/dist/services/classification.js.map +1 -1
  234. package/dist/services/cli-sql-dump.js.map +1 -1
  235. package/dist/services/consolidation.js.map +1 -1
  236. package/dist/services/context-audit.js +1 -1
  237. package/dist/services/context-audit.js.map +1 -1
  238. package/dist/services/context-budget.js.map +1 -1
  239. package/dist/services/context-engine.js.map +1 -1
  240. package/dist/services/contextual-variants.js.map +1 -1
  241. package/dist/services/continuous-verifier.js.map +1 -1
  242. package/dist/services/contradiction-adjudicator.js.map +1 -1
  243. package/dist/services/cost-context.js.map +1 -1
  244. package/dist/services/cost-feature-labels.js.map +1 -1
  245. package/dist/services/credential-migration.js.map +1 -1
  246. package/dist/services/credential-scanner.js.map +1 -1
  247. package/dist/services/credential-validation.js.map +1 -1
  248. package/dist/services/cron-exit-validator.js.map +1 -1
  249. package/dist/services/cron-guard.js.map +1 -1
  250. package/dist/services/cron-job-bash-harness.js +52 -5
  251. package/dist/services/cron-job-bash-harness.js.map +1 -1
  252. package/dist/services/cron-maintenance-reconciler.js +1 -3
  253. package/dist/services/cron-maintenance-reconciler.js.map +1 -1
  254. package/dist/services/cross-agent-learning.js.map +1 -1
  255. package/dist/services/crystallization-proposer.js.map +1 -1
  256. package/dist/services/dedupe-policy.js.map +1 -1
  257. package/dist/services/deprecated-cron-commands.js.map +1 -1
  258. package/dist/services/directive-extract.js.map +1 -1
  259. package/dist/services/document-chunker.js.map +1 -1
  260. package/dist/services/document-grader.js.map +1 -1
  261. package/dist/services/dream-cycle.js.map +1 -1
  262. package/dist/services/embedding-migration.js.map +1 -1
  263. package/dist/services/embedding-registry.js.map +1 -1
  264. package/dist/services/embeddings/chain-provider.js.map +1 -1
  265. package/dist/services/embeddings/factory.js.map +1 -1
  266. package/dist/services/embeddings/fallback-provider.js.map +1 -1
  267. package/dist/services/embeddings/ollama-provider.js.map +1 -1
  268. package/dist/services/embeddings/onnx-provider.js.map +1 -1
  269. package/dist/services/embeddings/openai-provider.js.map +1 -1
  270. package/dist/services/embeddings/shared.js +3 -3
  271. package/dist/services/embeddings/shared.js.map +1 -1
  272. package/dist/services/embeddings/types.js.map +1 -1
  273. package/dist/services/entity-enrichment-adaptive.js +128 -0
  274. package/dist/services/entity-enrichment-adaptive.js.map +1 -0
  275. package/dist/services/entity-enrichment-cli.js +389 -42
  276. package/dist/services/entity-enrichment-cli.js.map +1 -1
  277. package/dist/services/entity-enrichment.js +31 -5
  278. package/dist/services/entity-enrichment.js.map +1 -1
  279. package/dist/services/error-reporter/noisy-errors.js.map +1 -1
  280. package/dist/services/error-reporter/sanitize.js.map +1 -1
  281. package/dist/services/error-reporter.js.map +1 -1
  282. package/dist/services/event-hub-repair.js.map +1 -1
  283. package/dist/services/export-memory.js.map +1 -1
  284. package/dist/services/fact-extraction.js.map +1 -1
  285. package/dist/services/feedback-effectiveness.js.map +1 -1
  286. package/dist/services/find-duplicates.js.map +1 -1
  287. package/dist/services/frustration-detector.js.map +1 -1
  288. package/dist/services/fts-search.js.map +1 -1
  289. package/dist/services/gap-detector.js.map +1 -1
  290. package/dist/services/generated-skill-lifecycle.js.map +1 -1
  291. package/dist/services/generated-skill-validation.js.map +1 -1
  292. package/dist/services/goal-active-task-mirror.js.map +1 -1
  293. package/dist/services/goal-circuit-breaker.js.map +1 -1
  294. package/dist/services/goal-health.js.map +1 -1
  295. package/dist/services/goal-registry.js.map +1 -1
  296. package/dist/services/goal-stewardship-heartbeat.js.map +1 -1
  297. package/dist/services/goal-stewardship-llm-triage.js.map +1 -1
  298. package/dist/services/goal-stewardship-verify-cron.js.map +1 -1
  299. package/dist/services/goal-stewardship.js.map +1 -1
  300. package/dist/services/goal-subagent.js.map +1 -1
  301. package/dist/services/graph-retrieval.js.map +1 -1
  302. package/dist/services/humanizer-score.js.map +1 -1
  303. package/dist/services/hybrid-mem-cron-default-job-steps.js.map +1 -1
  304. package/dist/services/hyde-helper.js.map +1 -1
  305. package/dist/services/identity-reflection.js.map +1 -1
  306. package/dist/services/implicit-feedback-extract.js.map +1 -1
  307. package/dist/services/index.js.map +1 -1
  308. package/dist/services/ingest-utils.js.map +1 -1
  309. package/dist/services/intent-template.js.map +1 -1
  310. package/dist/services/json-array-parser.js.map +1 -1
  311. package/dist/services/knowledge-gaps.js.map +1 -1
  312. package/dist/services/language-keywords-build.js.map +1 -1
  313. package/dist/services/lifecycle/github-adapter.js.map +1 -1
  314. package/dist/services/llm-rate-limit-headers.js +1 -2
  315. package/dist/services/llm-rate-limit-headers.js.map +1 -1
  316. package/dist/services/maintenance-auto-fix.js.map +1 -1
  317. package/dist/services/maintenance-log-analyzer.js +7 -1
  318. package/dist/services/maintenance-log-analyzer.js.map +1 -1
  319. package/dist/services/maintenance-timestamp.js.map +1 -1
  320. package/dist/services/memory-diagnostics.js.map +1 -1
  321. package/dist/services/memory-index.js.map +1 -1
  322. package/dist/services/merge-results.js.map +1 -1
  323. package/dist/services/model-capabilities.js.map +1 -1
  324. package/dist/services/model-pricing.js.map +1 -1
  325. package/dist/services/narrative-recall.js.map +1 -1
  326. package/dist/services/openclaw-session-artifact.js.map +1 -1
  327. package/dist/services/passive-observer.js.map +1 -1
  328. package/dist/services/pattern-detector-hash.js.map +1 -1
  329. package/dist/services/pattern-detector.js.map +1 -1
  330. package/dist/services/pending-autopilot/foundation.js.map +1 -1
  331. package/dist/services/pending-autopilot/redaction.js.map +1 -1
  332. package/dist/services/pending-autopilot/store.js.map +1 -1
  333. package/dist/services/pending-autopilot/types.js.map +1 -1
  334. package/dist/services/pending-digest-autopilot-cron.js.map +1 -1
  335. package/dist/services/pending-digest-autopilot.js.map +1 -1
  336. package/dist/services/pending-review-digest.js.map +1 -1
  337. package/dist/services/persona-proposal-triage.js.map +1 -1
  338. package/dist/services/persona-state-promotion.js.map +1 -1
  339. package/dist/services/post-compaction-recall.js.map +1 -1
  340. package/dist/services/pre-consolidation-flush.js.map +1 -1
  341. package/dist/services/pre-finalization-guard.js.map +1 -1
  342. package/dist/services/procedure-cluster.js.map +1 -1
  343. package/dist/services/procedure-extractor.js.map +1 -1
  344. package/dist/services/procedure-promotion/duplicate-skill-cache.js.map +1 -1
  345. package/dist/services/procedure-promotion-policy.js.map +1 -1
  346. package/dist/services/procedure-selection-metrics.js.map +1 -1
  347. package/dist/services/procedure-skill-eval.js.map +1 -1
  348. package/dist/services/procedure-skill-generator.js.map +1 -1
  349. package/dist/services/procedure-skill-recipe.js.map +1 -1
  350. package/dist/services/procedure-skill-shrink.js.map +1 -1
  351. package/dist/services/procedure-skill-workflow.js.map +1 -1
  352. package/dist/services/provenance.js.map +1 -1
  353. package/dist/services/public-export-bundle.js.map +1 -1
  354. package/dist/services/python-bridge.js.map +1 -1
  355. package/dist/services/query-expander.js.map +1 -1
  356. package/dist/services/query-validator.js.map +1 -1
  357. package/dist/services/recall-pipeline.js.map +1 -1
  358. package/dist/services/recall-timing.js.map +1 -1
  359. package/dist/services/recent-http-attempts.js.map +1 -1
  360. package/dist/services/reflection/shared.js.map +1 -1
  361. package/dist/services/reflection.js.map +1 -1
  362. package/dist/services/reinforcement-extract.js.map +1 -1
  363. package/dist/services/reranker.js.map +1 -1
  364. package/dist/services/responses-adapter.js.map +1 -1
  365. package/dist/services/retrieval-aliases.js.map +1 -1
  366. package/dist/services/retrieval-mode-policy.js.map +1 -1
  367. package/dist/services/retrieval-orchestrator/packing.js.map +1 -1
  368. package/dist/services/retrieval-orchestrator.d.ts +2 -3
  369. package/dist/services/retrieval-orchestrator.js.map +1 -1
  370. package/dist/services/rrf-fusion.js.map +1 -1
  371. package/dist/services/self-correction-extract.js.map +1 -1
  372. package/dist/services/session-observability.js.map +1 -1
  373. package/dist/services/session-pre-filter.js.map +1 -1
  374. package/dist/services/shortest-path.js.map +1 -1
  375. package/dist/services/skill-allowed-tools.js.map +1 -1
  376. package/dist/services/skill-creator-validator.js.map +1 -1
  377. package/dist/services/skill-crystallizer-helpers.js.map +1 -1
  378. package/dist/services/skill-crystallizer.js.map +1 -1
  379. package/dist/services/skill-description-builder.js.map +1 -1
  380. package/dist/services/skill-eval-synthesizer.js.map +1 -1
  381. package/dist/services/skill-examples-builder.js.map +1 -1
  382. package/dist/services/skill-frontmatter.js.map +1 -1
  383. package/dist/services/skill-name-validator.js.map +1 -1
  384. package/dist/services/skill-prompt-injection.js.map +1 -1
  385. package/dist/services/skill-reference-sidecar.js.map +1 -1
  386. package/dist/services/skill-script-bundler.js.map +1 -1
  387. package/dist/services/skill-validator.js.map +1 -1
  388. package/dist/services/startup-memory-attribution.js.map +1 -1
  389. package/dist/services/task-hygiene.js.map +1 -1
  390. package/dist/services/task-ledger/canonical.js.map +1 -1
  391. package/dist/services/task-ledger-facts.js.map +1 -1
  392. package/dist/services/task-queue-leases.js.map +1 -1
  393. package/dist/services/task-queue-watchdog.js.map +1 -1
  394. package/dist/services/tool-effectiveness.js.map +1 -1
  395. package/dist/services/tool-proposer.js.map +1 -1
  396. package/dist/services/tools-md-section.js.map +1 -1
  397. package/dist/services/topic-clusters.js.map +1 -1
  398. package/dist/services/trajectory-tracker.js.map +1 -1
  399. package/dist/services/vector-backend-observability.js.map +1 -1
  400. package/dist/services/vector-lifecycle-audit.js.map +1 -1
  401. package/dist/services/vector-maintenance.js.map +1 -1
  402. package/dist/services/vector-search.js.map +1 -1
  403. package/dist/services/verification-store.js.map +1 -1
  404. package/dist/services/verified-fact-triage.js.map +1 -1
  405. package/dist/services/wal-helpers.js.map +1 -1
  406. package/dist/services/workflow-tracker.js.map +1 -1
  407. package/dist/setup/bootstrap-databases.js.map +1 -1
  408. package/dist/setup/cli-context/cli-services.js.map +1 -1
  409. package/dist/setup/cli-context/help-text.js.map +1 -1
  410. package/dist/setup/cli-context/metadata.js.map +1 -1
  411. package/dist/setup/cli-context/register-cli-with-help.js.map +1 -1
  412. package/dist/setup/cli-context/register-full.js.map +1 -1
  413. package/dist/setup/cli-context/register-help.js.map +1 -1
  414. package/dist/setup/cost-instrumentation.js.map +1 -1
  415. package/dist/setup/hybrid-memory-generation-state.js.map +1 -1
  416. package/dist/setup/hybrid-memory-reload-coordinator.js +13 -13
  417. package/dist/setup/hybrid-memory-reload-coordinator.js.map +1 -1
  418. package/dist/setup/plugin-service.js.map +1 -1
  419. package/dist/setup/provider-router.js.map +1 -1
  420. package/dist/setup/register-context-engine.js.map +1 -1
  421. package/dist/setup/register-hooks.js.map +1 -1
  422. package/dist/setup/register-plugin.js +25 -21
  423. package/dist/setup/register-plugin.js.map +1 -1
  424. package/dist/setup/register-tools.js.map +1 -1
  425. package/dist/setup/reregister-policy.js +2 -2
  426. package/dist/setup/reregister-policy.js.map +1 -1
  427. package/dist/setup/tool-installers.js.map +1 -1
  428. package/dist/setup/workspace-bootstrap.js.map +1 -1
  429. package/dist/src/worker/narratives.js.map +1 -1
  430. package/dist/tools/apitap-tools.js.map +1 -1
  431. package/dist/tools/credential-tools.js.map +1 -1
  432. package/dist/tools/crystallization-tools.js.map +1 -1
  433. package/dist/tools/dashboard-routes.js.map +1 -1
  434. package/dist/tools/document-tools.js.map +1 -1
  435. package/dist/tools/goal-tools.js.map +1 -1
  436. package/dist/tools/graph-tools.js.map +1 -1
  437. package/dist/tools/issue-tools.js.map +1 -1
  438. package/dist/tools/memory/build-runtime.js.map +1 -1
  439. package/dist/tools/memory/helpers.js.map +1 -1
  440. package/dist/tools/memory/register-checkpoint-tools.js.map +1 -1
  441. package/dist/tools/memory/register-directory-tools.js.map +1 -1
  442. package/dist/tools/memory/register-edict-tools.js.map +1 -1
  443. package/dist/tools/memory/register-episode-tools.js.map +1 -1
  444. package/dist/tools/memory/register-recall-tools.js.map +1 -1
  445. package/dist/tools/memory/register-store-tools.js.map +1 -1
  446. package/dist/tools/memory-tools.js.map +1 -1
  447. package/dist/tools/persona-tools.js.map +1 -1
  448. package/dist/tools/provenance-tools.js.map +1 -1
  449. package/dist/tools/public-api-routes.js.map +1 -1
  450. package/dist/tools/safe-register-http-route.js.map +1 -1
  451. package/dist/tools/self-extension-tools.js.map +1 -1
  452. package/dist/tools/task-hygiene-tools.js.map +1 -1
  453. package/dist/tools/utility-tools.js.map +1 -1
  454. package/dist/tools/verification-tools.js.map +1 -1
  455. package/dist/tools/workflow-tools.js.map +1 -1
  456. package/dist/types/issue-types.js.map +1 -1
  457. package/dist/types/learnings-types.js.map +1 -1
  458. package/dist/types/memory.js.map +1 -1
  459. package/dist/utils/apim-gateway-fetch.js.map +1 -1
  460. package/dist/utils/atomic-write.js.map +1 -1
  461. package/dist/utils/auth-failover.js.map +1 -1
  462. package/dist/utils/auth.js.map +1 -1
  463. package/dist/utils/compaction-model-watchdog.js.map +1 -1
  464. package/dist/utils/consolidation-controls.js.map +1 -1
  465. package/dist/utils/constants.js.map +1 -1
  466. package/dist/utils/date-detector.js.map +1 -1
  467. package/dist/utils/dates.js.map +1 -1
  468. package/dist/utils/decay.js.map +1 -1
  469. package/dist/utils/duration.js.map +1 -1
  470. package/dist/utils/embed-call.js.map +1 -1
  471. package/dist/utils/entity-lookup-resolve.js.map +1 -1
  472. package/dist/utils/entity-mention-quality.js.map +1 -1
  473. package/dist/utils/entity-stopwords.js.map +1 -1
  474. package/dist/utils/env-manager.js.map +1 -1
  475. package/dist/utils/error-tracking.js.map +1 -1
  476. package/dist/utils/event-loop-yield.js.map +1 -1
  477. package/dist/utils/extract-last-user-message.js.map +1 -1
  478. package/dist/utils/extraction-from-template.js.map +1 -1
  479. package/dist/utils/fact-embeddings.js.map +1 -1
  480. package/dist/utils/file-snapshot.js.map +1 -1
  481. package/dist/utils/format.js.map +1 -1
  482. package/dist/utils/fs.js.map +1 -1
  483. package/dist/utils/gh-repo-arg.js.map +1 -1
  484. package/dist/utils/hybrid-mem-json-cli.js.map +1 -1
  485. package/dist/utils/language-keywords.js.map +1 -1
  486. package/dist/utils/lifecycle-generation.js.map +1 -1
  487. package/dist/utils/llm-json-array.js.map +1 -1
  488. package/dist/utils/llm-selection.js.map +1 -1
  489. package/dist/utils/logger.js.map +1 -1
  490. package/dist/utils/model-provider-family.js.map +1 -1
  491. package/dist/utils/model-tier.js.map +1 -1
  492. package/dist/utils/openclaw-agent-defaults.js.map +1 -1
  493. package/dist/utils/path.js.map +1 -1
  494. package/dist/utils/plugin-root.js.map +1 -1
  495. package/dist/utils/plugin-update-check.js.map +1 -1
  496. package/dist/utils/procedure-risk.js.map +1 -1
  497. package/dist/utils/progress-indicators.js.map +1 -1
  498. package/dist/utils/prompt-loader.js.map +1 -1
  499. package/dist/utils/provenance.js.map +1 -1
  500. package/dist/utils/provider-detection.js.map +1 -1
  501. package/dist/utils/registration-superseded.js.map +1 -1
  502. package/dist/utils/salience.js.map +1 -1
  503. package/dist/utils/sanitize-messages.js.map +1 -1
  504. package/dist/utils/scope-filter.js.map +1 -1
  505. package/dist/utils/skill-discovery.js.map +1 -1
  506. package/dist/utils/sqlite-file-perms.js.map +1 -1
  507. package/dist/utils/sqlite-outcome-compat.js.map +1 -1
  508. package/dist/utils/sqlite-transaction.js.map +1 -1
  509. package/dist/utils/stable-stringify.js.map +1 -1
  510. package/dist/utils/subagent-ended-utils.js.map +1 -1
  511. package/dist/utils/tags.js.map +1 -1
  512. package/dist/utils/text.js.map +1 -1
  513. package/dist/utils/timeout.js.map +1 -1
  514. package/dist/utils/typebox.js.map +1 -1
  515. package/dist/utils/version-check.js.map +1 -1
  516. package/dist/utils/wal-replay.js.map +1 -1
  517. package/dist/versionInfo.js.map +1 -1
  518. package/index.ts +2 -2
  519. package/lifecycle/hooks.ts +0 -1
  520. package/npm-shrinkwrap.json +487 -186
  521. package/openclaw.plugin.json +1 -1
  522. package/package.json +2 -2
  523. package/services/adaptive-catch-up-pacing.ts +28 -0
  524. package/services/chat.ts +34 -1
  525. package/services/cron-job-bash-harness.ts +52 -5
  526. package/services/embeddings/shared.ts +5 -2
  527. package/services/entity-enrichment-adaptive.ts +245 -0
  528. package/services/entity-enrichment-cli.ts +553 -47
  529. package/services/entity-enrichment.ts +43 -2
  530. package/services/llm-rate-limit-headers.ts +1 -4
  531. package/services/maintenance-log-analyzer.ts +13 -9
  532. package/services/reinforcement-extract.ts +19 -0
  533. package/setup/hybrid-memory-reload-coordinator.ts +26 -0
  534. package/setup/register-plugin.ts +62 -32
  535. package/setup/reregister-policy.ts +10 -5
@@ -1 +1 @@
1
- {"version":3,"file":"skill-validator.js","names":[],"sources":["../../services/skill-validator.ts"],"sourcesContent":["/**\n * Skill Validator — static security analysis for generated SKILL.md content (Issue #208).\n *\n * DENY patterns:\n * - Shell execution commands in code blocks (bash, sh, exec, eval, system, etc.)\n * - Credential access patterns (env vars with KEY/SECRET/TOKEN/PASSWORD, ssh commands)\n * - Network calls (curl, wget, fetch, http.*)\n * - File system writes outside the skill directory (rm, mv, write to absolute paths)\n *\n * The validator is intentionally conservative: false positives are acceptable,\n * false negatives (allowing dangerous content) are not.\n *\n * Required section taxonomy is shared with GeneratedSkillValidationService via\n * config/skill-sections.ts (issues #1375, #1366, #1408).\n */\n\nimport { ACTION_VERB_PATTERN } from \"../utils/constants.js\";\nimport { stripLeadingHtmlComments } from \"../utils/text.js\";\nimport {\n MAX_SKILL_DESCRIPTION_CHARS,\n MAX_SKILL_FILE_BYTES,\n MAX_SKILL_FILE_BYTES_SAFE,\n utf8ByteLength,\n} from \"../config/skill-size-limits.js\";\nimport {\n CATEGORY_FRONTMATTER_KEYS,\n MAX_SKILL_LINES,\n getSectionTaxonomy,\n type SectionTaxonomyOverrides,\n} from \"../config/skill-sections.js\";\nimport { parseSkillFrontmatterKeys } from \"./skill-frontmatter.js\";\nexport {\n CATEGORY_FRONTMATTER_KEYS,\n DEFAULT_REQUIRED_SECTIONS,\n MAX_SKILL_LINES,\n getSectionTaxonomy,\n type SectionTaxonomyOverrides,\n} from \"../config/skill-sections.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\ninterface ValidationResult {\n valid: boolean;\n violations: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Rule definitions\n// ---------------------------------------------------------------------------\nconst MAX_FENCED_BLOCK_LINES = 80;\nconst MAX_TRANSCRIPT_LIKE_RATIO = 0.4;\nconst MAX_TOOL_BLOB_LINES = 40;\n\ninterface DenyRule {\n name: string;\n /** Applies to lines inside code blocks only */\n codeBlockOnly?: boolean;\n /** Regex pattern to match */\n pattern: RegExp;\n /** Human-readable description for the violation message */\n description: string;\n}\n\n/**\n * Case-insensitive PEM private-key block detector.\n * Shared with generated-skill-validation.ts and procedure-promotion-policy.ts (Issue #1382).\n * Character class includes digits and allows trailing words (e.g. \"BLOCK\" in PGP markers).\n * Matches: -----BEGIN PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY-----,\n * -----BEGIN PGP PRIVATE KEY BLOCK-----, -----begin private key-----, etc.\n */\nexport const PEM_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Za-z0-9 ]*PRIVATE KEY[A-Za-z0-9 ]*-----/i;\n\n/**\n * Private IP address pattern (RFC 1918 ranges).\n * Shared with generated-skill-validation.ts and procedure-promotion-policy.ts.\n * Matches: 10.x.x.x, 172.16-31.x.x, 192.168.x.x (four-octet private IPs).\n * Loopback (127.x) is intentionally excluded — it reveals nothing about network topology\n * and causes noisy false positives on health-check examples (Issue #1385).\n */\nexport const PRIVATE_IP_PATTERN = /\\b(?:10\\.\\d{1,3}\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.|192\\.168\\.)\\d{1,3}\\.\\d{1,3}\\b/;\n\n/** Default placeholder email domains used by {@link buildNonPlaceholderEmailPattern}. */\nexport const DEFAULT_PLACEHOLDER_EMAIL_DOMAINS = [\"example.com\", \"localhost\", \"test.com\", \"example.org\"];\n\n/**\n * Build a regex that matches real-looking email addresses while excluding placeholder domains.\n * Domain entries are regex-escaped so arbitrary strings are safe to pass.\n * If the allow-list is empty, returns a pattern that flags all valid-looking email addresses.\n * @param allowList - domains to treat as safe placeholders (default: {@link DEFAULT_PLACEHOLDER_EMAIL_DOMAINS})\n */\nexport function buildNonPlaceholderEmailPattern(allowList: string[]): RegExp {\n const escaped = allowList\n .filter((d) => d.length > 0)\n .map((d) => d.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"))\n .join(\"|\");\n if (escaped.length === 0) {\n // No placeholder domains: flag all valid-looking email addresses.\n return /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/i;\n }\n return new RegExp(`\\\\b[A-Za-z0-9._%+-]+@(?!(?:${escaped})(?![A-Za-z0-9.-]))[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,}\\\\b`, \"i\");\n}\n\nconst SECRET_PATTERNS: Array<[name: string, pattern: RegExp, description: string]> = [\n [\"private-key-block\", PEM_PRIVATE_KEY_PATTERN, \"Private key block detected\"],\n [\n \"bearer-token\",\n /\\bBearer\\s+[A-Za-z0-9._~+/-]+=*/i,\n \"Bearer token-like credential detected (must not be copied into skills)\",\n ],\n [\n \"api-key-assignment\",\n /\\b(?:password|passwd|pwd|secret|token|api[_-]?key|authorization|private[_-]?key)\\s*[:=]\\s*[^\\s,;}{[\\]]+/i,\n \"Credential assignment pattern detected (must not be copied into skills)\",\n ],\n [\n \"high-entropy-token\",\n /\\b(?:sk|pk|rk|ghp|gho|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{8,}\\b/i,\n \"High-entropy token pattern detected (must not be copied into skills)\",\n ],\n];\n\n/** Real-looking emails only; allows example.com, localhost, test.com, example.org placeholders. */\nexport const NON_PLACEHOLDER_EMAIL_PATTERN = buildNonPlaceholderEmailPattern(DEFAULT_PLACEHOLDER_EMAIL_DOMAINS);\n\n/**\n * Build the private-context pattern list. Accepts an optional email pattern override so that\n * operators can supply a custom placeholder-domain allow-list (Issue #1383).\n */\nfunction buildPrivateContextPatterns(\n emailPattern: RegExp,\n): Array<[name: string, pattern: RegExp, description: string]> {\n return [\n [\"private-ip\", PRIVATE_IP_PATTERN, \"Private IP / host inventory detected (replace with placeholders)\"],\n [\n \"home-path-linux\",\n /(?:^|[\\s\"'=:])\\/home\\/(?!user\\/|runner\\/|ubuntu\\/)[^\\s\"'/:]+\\/[^\\s\"']+/,\n \"User-specific /home/... path detected (replace with placeholders)\",\n ],\n [\n \"home-path-macos\",\n /(?:^|[\\s\"'=:])\\/Users\\/(?!user\\/|runner\\/)[^\\s\"'/:]+\\/[^\\s\"']+/,\n \"User-specific /Users/... path detected (replace with placeholders)\",\n ],\n [\n \"tilde-home-path\",\n // Negative lookahead (?!\\.) has been removed — dotfile paths (e.g. ~/.ssh, ~/.aws)\n // are the most sensitive and must be flagged, not excluded (Issue #1384).\n /(?:^|[\\s\"'=:])~\\/[^\\s\"']+/,\n \"Tilde home path detected (replace with placeholders; avoid personal paths in skills)\",\n ],\n [\"email-address\", emailPattern, \"Non-example email address detected (remove or replace with example.com)\"],\n ];\n}\n\nconst DENY_RULES: DenyRule[] = [\n // Shell execution\n {\n name: \"shell-eval\",\n codeBlockOnly: true,\n pattern: new RegExp(\"\\\\bev\" + \"al\\\\s*[\\\\(\\\\$\" + \"'\\\"`]\", \"i\"),\n description: \"ev\" + \"al() or ev\" + \"al$(...) in code block — arbitrary code execution\",\n },\n {\n name: \"shell-exec-func\",\n codeBlockOnly: true,\n pattern: /\\bexec\\s*\\(/i,\n description: \"exec() function call in code block\",\n },\n {\n name: \"shell-system\",\n codeBlockOnly: true,\n pattern: /\\bsystem\\s*\\(/i,\n description: \"system() call in code block\",\n },\n {\n name: \"shell-spawn\",\n codeBlockOnly: true,\n pattern: /\\bspawn\\s*\\(/i,\n description: \"spawn() call in code block — process creation\",\n },\n // Credential access\n {\n name: \"credential-env-secret\",\n codeBlockOnly: true,\n pattern: /\\$\\{?(?:\\w*(?:API_KEY|SECRET|PASSWORD|TOKEN|PRIVATE_KEY|ACCESS_KEY|AUTH|CREDENTIAL)\\w*)\\b/i,\n description: \"Environment variable referencing a credential secret\",\n },\n {\n name: \"ssh-command\",\n codeBlockOnly: true,\n pattern: /\\bssh\\s+(-\\w+\\s+)*\\w+@/i,\n description: \"SSH command with user@host in code block\",\n },\n // Network calls\n {\n name: \"curl-call\",\n codeBlockOnly: true,\n pattern: /\\bcurl\\s+/i,\n description: \"curl network call in code block\",\n },\n {\n name: \"wget-call\",\n codeBlockOnly: true,\n pattern: /\\bwget\\s+/i,\n description: \"wget network call in code block\",\n },\n {\n name: \"http-fetch\",\n codeBlockOnly: true,\n pattern: /\\bfetch\\s*\\(\\s*['\"]https?:\\/\\//i,\n description: \"HTTP fetch() call to external URL in code block\",\n },\n // Filesystem writes to absolute/dangerous paths\n {\n name: \"rm-rf\",\n codeBlockOnly: true,\n pattern: /\\brm\\s+-[rf]+\\s+\\/|rm\\s+-[rf]+\\s+~\\//i,\n description: \"Recursive deletion from absolute or home path in code block\",\n },\n {\n name: \"write-to-etc\",\n codeBlockOnly: true,\n pattern: />\\s*\\/etc\\//i,\n description: \"Redirect write to /etc/ in code block\",\n },\n {\n name: \"write-to-root\",\n codeBlockOnly: true,\n pattern: />\\s*\\/(?:usr|bin|sbin|root|boot)\\//i,\n description: \"Redirect write to system path in code block\",\n },\n // Dangerous JavaScript/TypeScript in non-SKILL context\n {\n name: \"require-fs\",\n codeBlockOnly: true,\n pattern: /require\\s*\\(\\s*['\"](?:child_process|fs|path)['\"]\\)/i,\n description: \"require('child_process'|'fs'|'path') in code block\",\n },\n {\n name: \"import-child-process\",\n codeBlockOnly: true,\n pattern: /import\\s+.*from\\s+['\"]child_process['\"]/i,\n description: \"import from 'child_process' in code block\",\n },\n];\n\n// ---------------------------------------------------------------------------\n// SkillValidator\n// ---------------------------------------------------------------------------\n\nexport class SkillValidator {\n private readonly privateContextPatterns: Array<[name: string, pattern: RegExp, description: string]>;\n private readonly sectionTaxonomyOverrides?: SectionTaxonomyOverrides;\n\n /**\n * @param options.emailPattern - custom non-placeholder email regex; defaults to\n * {@link NON_PLACEHOLDER_EMAIL_PATTERN}. Build with {@link buildNonPlaceholderEmailPattern}.\n * @param options.sectionTaxonomyOverrides - optional per-category section taxonomy overrides\n * (passed through from CrystallizationProposer; Issue #1408).\n */\n constructor(options?: { emailPattern?: RegExp; sectionTaxonomyOverrides?: SectionTaxonomyOverrides }) {\n this.privateContextPatterns = buildPrivateContextPatterns(options?.emailPattern ?? NON_PLACEHOLDER_EMAIL_PATTERN);\n this.sectionTaxonomyOverrides = options?.sectionTaxonomyOverrides;\n }\n\n /**\n * Validate generated SKILL.md content for:\n * - security violations (deny rules inside code blocks)\n * - required structure/sections for reusable workflows\n * - anti-log-dumping guardrails (transcript/log blobs)\n */\n validate(skillContent: string): ValidationResult {\n const violations: string[] = [];\n const rawBytes = utf8ByteLength(skillContent);\n if (rawBytes > MAX_SKILL_FILE_BYTES) {\n return {\n valid: false,\n violations: [\n `Skill exceeds OpenClaw loader byte limit (${rawBytes} > ${MAX_SKILL_FILE_BYTES}). Shrink SKILL.md or move deterministic detail into bounded sidecars.`,\n ],\n };\n }\n\n const normalizedSkillContent = stripLeadingHtmlComments(skillContent);\n const lines = normalizedSkillContent.split(\"\\n\");\n\n if (lines.length > MAX_SKILL_LINES) {\n violations.push(\n `Skill exceeds ${MAX_SKILL_LINES} lines (${lines.length}). Summarize logs/transcripts into workflow phases and checklists.`,\n );\n }\n\n const skillBytes = Buffer.byteLength(normalizedSkillContent, \"utf8\");\n if (skillBytes > MAX_SKILL_FILE_BYTES_SAFE) {\n violations.push(\n `Skill exceeds ${MAX_SKILL_FILE_BYTES_SAFE} bytes (${skillBytes}). Use progressive disclosure and bounded sidecars.`,\n );\n }\n\n if (/\\*\\*\\w+\\*\\*\\s*\\{/.test(normalizedSkillContent)) {\n violations.push(\"Legacy raw tool-call dump format detected (**tool** {json}); summarize into workflow phases.\");\n }\n\n if (/\\b[A-Za-z]:\\\\[^\\s/]+/.test(normalizedSkillContent) || /\\b[A-Za-z]:\\\\/.test(normalizedSkillContent)) {\n violations.push(\"Windows-style paths are not allowed; use forward-slash paths only.\");\n }\n\n const bodyLower = normalizedSkillContent.toLowerCase();\n const usesProcedure = /\\bprocedure\\b/.test(bodyLower);\n const usesWorkflow = /\\bworkflow\\b/.test(bodyLower);\n if (usesProcedure && usesWorkflow && /\\bthis procedure\\b|\\bthe procedure\\b/i.test(normalizedSkillContent)) {\n violations.push(\n \"Mixed procedure/workflow terminology: prefer workflow (user-facing) and recipe (machine-readable sidecar).\",\n );\n }\n\n // Secrets are a hard-fail regardless of where they appear.\n for (const [name, pattern, desc] of SECRET_PATTERNS) {\n if (pattern.test(skillContent)) {\n violations.push(`[${name}] ${desc}`);\n }\n }\n\n // Reject private context that should not be embedded into reusable skills.\n for (const [name, pattern, desc] of this.privateContextPatterns) {\n if (pattern.test(skillContent)) {\n violations.push(`[${name}] ${desc}`);\n }\n }\n\n // Required frontmatter + sections are enforced to prevent transcript-dumps.\n const frontmatter = parseFrontmatter(lines);\n if (!frontmatter.present) {\n violations.push(\"Missing required YAML frontmatter (--- ... ---) with name/description/category/provenance.\");\n } else {\n const keys = frontmatter.keys;\n for (const req of [\"name\", \"description\"] as const) {\n if (!keys.has(req)) violations.push(`Frontmatter missing required field: ${req}`);\n }\n const desc = keys.get(\"description\") ?? \"\";\n if (desc.length > MAX_SKILL_DESCRIPTION_CHARS) {\n violations.push(`Frontmatter description exceeds ${MAX_SKILL_DESCRIPTION_CHARS} characters.`);\n }\n const hasCategory =\n CATEGORY_FRONTMATTER_KEYS.some((key) => keys.has(key)) ||\n keys.has(\"metadata.category\") ||\n keys.has(\"metadata.tags\") ||\n keys.has(\"metadata.type\");\n if (!hasCategory) violations.push(\"Frontmatter missing required category (or equivalent: tags/type/kind).\");\n const hasProvenance =\n keys.has(\"provenance\") ||\n keys.has(\"metadata.provenance\") ||\n keys.has(\"generated_from\") ||\n keys.has(\"generatedfrom\") ||\n keys.has(\"source\") ||\n keys.has(\"source_pattern_id\") ||\n keys.has(\"source_procedure_id\");\n if (!hasProvenance) violations.push(\"Frontmatter missing provenance/generated-from metadata reference.\");\n }\n\n const headings = parseH2Headings(lines);\n // Use the shared taxonomy from config/skill-sections.ts so that both\n // SkillValidator and GeneratedSkillValidationService check the same sections\n // (issues #1375, #1408).\n const frontmatterCategory = frontmatter.present ? getFrontmatterCategory(frontmatter.keys) : undefined;\n const requiredSections = getSectionTaxonomy(frontmatterCategory, this.sectionTaxonomyOverrides);\n\n for (const section of requiredSections) {\n if (!hasHeadingAlias(headings, section.aliases)) {\n violations.push(`Missing required section: ${section.label}`);\n }\n }\n\n // Examples must include at least one compact real example (avoid placeholders).\n const examplesBody = extractSectionBody(lines, headings, [\"examples\"]);\n if (examplesBody !== null) {\n const examplesTrimmed = examplesBody.trim();\n if (examplesTrimmed.length === 0 || !containsConcreteExample(examplesBody)) {\n violations.push(\n \"Examples section must include at least one compact, concrete example (not just placeholders).\",\n );\n }\n }\n\n let inCodeBlock = false;\n let fenceChar: \"`\" | \"~\" | null = null;\n let fenceOpenLen = 0;\n let currentFenceStartLine = 0;\n let currentFenceLines = 0;\n let lineNumber = 0;\n let codeBlockLineCount = 0;\n let transcriptToolOrStackLineCount = 0;\n let toolBlobLikeLineCount = 0;\n\n for (const line of lines) {\n lineNumber++;\n const trimmed = line.trim();\n\n // Track fenced blocks; closing delimiter must match opener (``` vs ~~~) and length (CommonMark).\n // Closing lines must be fence + optional whitespace only — not ```json mid-block, etc.\n const fenceStart = trimmed.match(/^(`{3,}|~{3,})/);\n if (fenceStart) {\n const marker = fenceStart[1];\n const ch = marker[0] as \"`\" | \"~\";\n const len = marker.length;\n const afterMarker = trimmed.slice(marker.length);\n const isCloseLine = /^\\s*$/.test(afterMarker);\n if (!inCodeBlock) {\n inCodeBlock = true;\n fenceChar = ch;\n fenceOpenLen = len;\n currentFenceStartLine = lineNumber;\n currentFenceLines = 0;\n const contextLine = findPreviousNonEmptyLine(lines, lineNumber - 2);\n if (!contextLine || /^#{1,6}\\s+/.test(contextLine) || /^(`{3,}|~{3,})/.test(contextLine)) {\n violations.push(\n `Line ${lineNumber}: [codeblock-context] Code/log snippet must have a preceding explanatory sentence (avoid raw dumps).`,\n );\n }\n continue;\n }\n if (isCloseLine && fenceChar === ch && len >= fenceOpenLen) {\n if (currentFenceLines > MAX_FENCED_BLOCK_LINES) {\n violations.push(\n `Line ${currentFenceStartLine}: [codeblock-size] Fenced code/log block has ${currentFenceLines} lines (max ${MAX_FENCED_BLOCK_LINES}). Summarize instead.`,\n );\n }\n inCodeBlock = false;\n fenceChar = null;\n fenceOpenLen = 0;\n continue;\n }\n }\n\n if (inCodeBlock) {\n currentFenceLines++;\n codeBlockLineCount++;\n } else {\n const transcriptLike = /^\\s*(assistant|user|tool|system)\\s*:/i.test(line);\n const toolBlobLike = looksLikeToolOrJsonBlob(trimmed);\n const stackTraceLike = looksLikeStackTraceLine(trimmed);\n if (toolBlobLike) toolBlobLikeLineCount++;\n if (transcriptLike || toolBlobLike || stackTraceLike) transcriptToolOrStackLineCount++;\n }\n\n // Apply rules\n for (const rule of DENY_RULES) {\n if (rule.codeBlockOnly && !inCodeBlock) continue;\n if (rule.pattern.test(line)) {\n violations.push(`Line ${lineNumber}: [${rule.name}] ${rule.description} — \"${trimmed.slice(0, 80)}\"`);\n }\n }\n\n // Additional check: any code block containing shell-like content should\n // not have backtick command substitution\n if (inCodeBlock && /\\$\\([^)]+\\)/.test(line)) {\n violations.push(\n `Line ${lineNumber}: [shell-subst] Command substitution $(...) in code block — \"${trimmed.slice(0, 80)}\"`,\n );\n }\n }\n\n if (inCodeBlock && currentFenceLines > MAX_FENCED_BLOCK_LINES) {\n violations.push(\n `Line ${currentFenceStartLine}: [codeblock-size] Fenced code/log block has ${currentFenceLines} lines (max ${MAX_FENCED_BLOCK_LINES}) and no closing fence at end of file. Summarize instead.`,\n );\n }\n\n const rawLikeRatio =\n lines.length === 0 ? 0 : (codeBlockLineCount + transcriptToolOrStackLineCount) / Math.max(1, lines.length);\n if (rawLikeRatio > MAX_TRANSCRIPT_LIKE_RATIO) {\n violations.push(\n `[log-dump-guard] ${Math.round(rawLikeRatio * 100)}% of lines look like raw transcript/log/code. Summarize and keep snippets short.`,\n );\n }\n if (toolBlobLikeLineCount > MAX_TOOL_BLOB_LINES) {\n violations.push(\n `[tool-blob-guard] Detected ${toolBlobLikeLineCount} tool/JSON-like lines outside code blocks. Summarize tool-call blobs into workflow steps and checklists instead of pasting raw dumps.`,\n );\n }\n\n return {\n valid: violations.length === 0,\n violations,\n };\n }\n\n /**\n * Quick check: returns true if content passes validation.\n */\n isValid(skillContent: string): boolean {\n return this.validate(skillContent).valid;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Markdown helpers (lightweight, intentionally conservative)\n// ---------------------------------------------------------------------------\n\nexport function normalizeHeading(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}/\\s-]+/gu, \"\")\n .replace(/\\s+/g, \" \");\n}\n\nexport function parseH2Headings(lines: string[]): Array<{ raw: string; normalized: string; line: number }> {\n const out: Array<{ raw: string; normalized: string; line: number }> = [];\n let inFence = false;\n let fenceChar: \"`\" | \"~\" | null = null;\n let fenceOpenLen = 0;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n const trimmed = line.trim();\n const fenceStart = trimmed.match(/^(`{3,}|~{3,})/);\n if (fenceStart) {\n const marker = fenceStart[1];\n const ch = marker[0] as \"`\" | \"~\";\n const len = marker.length;\n const afterMarker = trimmed.slice(marker.length);\n const isCloseLine = /^\\s*$/.test(afterMarker);\n if (!inFence) {\n inFence = true;\n fenceChar = ch;\n fenceOpenLen = len;\n continue;\n }\n if (isCloseLine && fenceChar === ch && len >= fenceOpenLen) {\n inFence = false;\n fenceChar = null;\n fenceOpenLen = 0;\n continue;\n }\n continue;\n }\n if (inFence) continue;\n const match = line.match(/^##\\s+(.+?)\\s*$/);\n if (!match) continue;\n const raw = match[1];\n out.push({ raw, normalized: normalizeHeading(raw), line: i + 1 });\n }\n return out;\n}\n\nexport function hasHeadingAlias(headings: Array<{ normalized: string }>, aliases: string[]): boolean {\n const normalizedAliases = aliases.map(normalizeHeading);\n return headings.some((h) => normalizedAliases.includes(h.normalized));\n}\n\nfunction extractSectionBody(\n lines: string[],\n headings: Array<{ normalized: string; line: number }>,\n aliases: string[],\n): string | null {\n const normalizedAliases = new Set(aliases.map(normalizeHeading));\n const start = headings.find((h) => normalizedAliases.has(h.normalized));\n if (!start) return null;\n const afterStartIndex = start.line; // 1-based line number; body starts after it\n const next = headings.filter((h) => h.line > start.line).sort((a, b) => a.line - b.line)[0];\n const endLine = next ? next.line - 1 : lines.length;\n return lines.slice(afterStartIndex, endLine).join(\"\\n\").trim();\n}\n\nfunction containsConcreteExample(examplesBody: string): boolean {\n const lines = examplesBody.split(\"\\n\").map((l) => l.trim());\n const nonEmpty = lines.filter(Boolean);\n\n const listOk = nonEmpty.some((l) => {\n if (!/^(?:[-*+]|\\d+\\.)\\s+\\S/.test(l)) return false;\n if (/\\b(?:tbd|todo|placeholder|example here|fill in)\\b/i.test(l)) return false;\n if (l.length < 18) return false;\n if (!ACTION_VERB_PATTERN.test(l)) return false;\n return true;\n });\n if (listOk) return true;\n\n return nonEmpty.some((l) => {\n if (/^(?:[-*+]|\\d+\\.)\\s/.test(l)) return false;\n if (l.length < 40) return false;\n if (!ACTION_VERB_PATTERN.test(l)) return false;\n const words = l.split(/\\s+/).filter(Boolean);\n if (words.length < 4) return false;\n if (l.length > 15 && l === l.toUpperCase()) return false;\n if (/\\b(?:tbd|todo|placeholder|example here|fill in)\\b/i.test(l)) return false;\n return true;\n });\n}\n\nfunction findPreviousNonEmptyLine(lines: string[], startIndex: number): string | null {\n for (let i = startIndex; i >= 0; i--) {\n const trimmed = (lines[i] ?? \"\").trim();\n if (trimmed.length === 0) continue;\n return trimmed;\n }\n return null;\n}\n\nfunction unquoteFrontmatterValue(value: string | undefined): string | undefined {\n if (value == null) return undefined;\n const trimmed = value.trim();\n if (trimmed.length >= 2) {\n const first = trimmed[0];\n const last = trimmed[trimmed.length - 1];\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\n return trimmed.slice(1, -1).trim();\n }\n }\n return trimmed;\n}\n\nfunction getFrontmatterCategory(keys: Map<string, string>): string | undefined {\n const metaCategory = keys.get(\"metadata.category\");\n if (metaCategory) return unquoteFrontmatterValue(metaCategory);\n for (const key of CATEGORY_FRONTMATTER_KEYS) {\n const value = keys.get(key);\n if (value) return unquoteFrontmatterValue(value);\n }\n return undefined;\n}\n\nfunction parseFrontmatter(lines: string[]): {\n present: boolean;\n keys: Map<string, string>;\n endLine: number;\n} {\n const originalContent = lines.join(\"\\n\");\n const body = stripLeadingHtmlComments(originalContent);\n const bodyLines = body.split(\"\\n\");\n const strippedLineCount = originalContent.split(\"\\n\").length - bodyLines.length;\n if (bodyLines[0]?.trim() !== \"---\") {\n return { present: false, keys: new Map(), endLine: -1 };\n }\n let endIdx = -1;\n for (let i = 1; i < bodyLines.length; i++) {\n if (bodyLines[i]?.trim() === \"---\") {\n endIdx = i;\n break;\n }\n }\n if (endIdx < 0) return { present: false, keys: new Map(), endLine: -1 };\n const block = bodyLines.slice(1, endIdx).join(\"\\n\");\n const keys = parseSkillFrontmatterKeys(block);\n return { present: true, keys, endLine: endIdx + 1 + strippedLineCount };\n}\n\nfunction looksLikeToolOrJsonBlob(trimmed: string): boolean {\n if (trimmed.length === 0) return false;\n if (/^diff --git\\b/i.test(trimmed) || /^@@\\s+[-+]\\d+/i.test(trimmed)) return true;\n if (/^\\s*\\{/.test(trimmed) || /^\\s*\\[/.test(trimmed)) {\n if (trimmed.includes(`\":`) || trimmed.includes(\"':\")) return true;\n }\n if (/\"tool\"\\s*:|\"toolCall\"|tool_call_id|\"args\"\\s*:|\"content\"\\s*:/i.test(trimmed)) return true;\n if (/^\\s*Command:\\s+/i.test(trimmed) || /^\\s*Output:\\s+/i.test(trimmed)) return true;\n return false;\n}\n\nfunction looksLikeStackTraceLine(trimmed: string): boolean {\n if (trimmed.length === 0) return false;\n if (/^\\s*at\\s+\\S+/.test(trimmed)) return true;\n if (/^(?:Caused by:|Exception in thread|Traceback \\(most recent call last\\):)/i.test(trimmed)) return true;\n return false;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"skill-validator.js","names":[],"sources":["../../services/skill-validator.ts"],"sourcesContent":["/**\n * Skill Validator — static security analysis for generated SKILL.md content (Issue #208).\n *\n * DENY patterns:\n * - Shell execution commands in code blocks (bash, sh, exec, eval, system, etc.)\n * - Credential access patterns (env vars with KEY/SECRET/TOKEN/PASSWORD, ssh commands)\n * - Network calls (curl, wget, fetch, http.*)\n * - File system writes outside the skill directory (rm, mv, write to absolute paths)\n *\n * The validator is intentionally conservative: false positives are acceptable,\n * false negatives (allowing dangerous content) are not.\n *\n * Required section taxonomy is shared with GeneratedSkillValidationService via\n * config/skill-sections.ts (issues #1375, #1366, #1408).\n */\n\nimport { ACTION_VERB_PATTERN } from \"../utils/constants.js\";\nimport { stripLeadingHtmlComments } from \"../utils/text.js\";\nimport {\n MAX_SKILL_DESCRIPTION_CHARS,\n MAX_SKILL_FILE_BYTES,\n MAX_SKILL_FILE_BYTES_SAFE,\n utf8ByteLength,\n} from \"../config/skill-size-limits.js\";\nimport {\n CATEGORY_FRONTMATTER_KEYS,\n MAX_SKILL_LINES,\n getSectionTaxonomy,\n type SectionTaxonomyOverrides,\n} from \"../config/skill-sections.js\";\nimport { parseSkillFrontmatterKeys } from \"./skill-frontmatter.js\";\nexport {\n CATEGORY_FRONTMATTER_KEYS,\n DEFAULT_REQUIRED_SECTIONS,\n MAX_SKILL_LINES,\n getSectionTaxonomy,\n type SectionTaxonomyOverrides,\n} from \"../config/skill-sections.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\ninterface ValidationResult {\n valid: boolean;\n violations: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Rule definitions\n// ---------------------------------------------------------------------------\nconst MAX_FENCED_BLOCK_LINES = 80;\nconst MAX_TRANSCRIPT_LIKE_RATIO = 0.4;\nconst MAX_TOOL_BLOB_LINES = 40;\n\ninterface DenyRule {\n name: string;\n /** Applies to lines inside code blocks only */\n codeBlockOnly?: boolean;\n /** Regex pattern to match */\n pattern: RegExp;\n /** Human-readable description for the violation message */\n description: string;\n}\n\n/**\n * Case-insensitive PEM private-key block detector.\n * Shared with generated-skill-validation.ts and procedure-promotion-policy.ts (Issue #1382).\n * Character class includes digits and allows trailing words (e.g. \"BLOCK\" in PGP markers).\n * Matches: -----BEGIN PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY-----,\n * -----BEGIN PGP PRIVATE KEY BLOCK-----, -----begin private key-----, etc.\n */\nexport const PEM_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Za-z0-9 ]*PRIVATE KEY[A-Za-z0-9 ]*-----/i;\n\n/**\n * Private IP address pattern (RFC 1918 ranges).\n * Shared with generated-skill-validation.ts and procedure-promotion-policy.ts.\n * Matches: 10.x.x.x, 172.16-31.x.x, 192.168.x.x (four-octet private IPs).\n * Loopback (127.x) is intentionally excluded — it reveals nothing about network topology\n * and causes noisy false positives on health-check examples (Issue #1385).\n */\nexport const PRIVATE_IP_PATTERN = /\\b(?:10\\.\\d{1,3}\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.|192\\.168\\.)\\d{1,3}\\.\\d{1,3}\\b/;\n\n/** Default placeholder email domains used by {@link buildNonPlaceholderEmailPattern}. */\nexport const DEFAULT_PLACEHOLDER_EMAIL_DOMAINS = [\"example.com\", \"localhost\", \"test.com\", \"example.org\"];\n\n/**\n * Build a regex that matches real-looking email addresses while excluding placeholder domains.\n * Domain entries are regex-escaped so arbitrary strings are safe to pass.\n * If the allow-list is empty, returns a pattern that flags all valid-looking email addresses.\n * @param allowList - domains to treat as safe placeholders (default: {@link DEFAULT_PLACEHOLDER_EMAIL_DOMAINS})\n */\nexport function buildNonPlaceholderEmailPattern(allowList: string[]): RegExp {\n const escaped = allowList\n .filter((d) => d.length > 0)\n .map((d) => d.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"))\n .join(\"|\");\n if (escaped.length === 0) {\n // No placeholder domains: flag all valid-looking email addresses.\n return /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/i;\n }\n return new RegExp(`\\\\b[A-Za-z0-9._%+-]+@(?!(?:${escaped})(?![A-Za-z0-9.-]))[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,}\\\\b`, \"i\");\n}\n\nconst SECRET_PATTERNS: Array<[name: string, pattern: RegExp, description: string]> = [\n [\"private-key-block\", PEM_PRIVATE_KEY_PATTERN, \"Private key block detected\"],\n [\n \"bearer-token\",\n /\\bBearer\\s+[A-Za-z0-9._~+/-]+=*/i,\n \"Bearer token-like credential detected (must not be copied into skills)\",\n ],\n [\n \"api-key-assignment\",\n /\\b(?:password|passwd|pwd|secret|token|api[_-]?key|authorization|private[_-]?key)\\s*[:=]\\s*[^\\s,;}{[\\]]+/i,\n \"Credential assignment pattern detected (must not be copied into skills)\",\n ],\n [\n \"high-entropy-token\",\n /\\b(?:sk|pk|rk|ghp|gho|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{8,}\\b/i,\n \"High-entropy token pattern detected (must not be copied into skills)\",\n ],\n];\n\n/** Real-looking emails only; allows example.com, localhost, test.com, example.org placeholders. */\nexport const NON_PLACEHOLDER_EMAIL_PATTERN = buildNonPlaceholderEmailPattern(DEFAULT_PLACEHOLDER_EMAIL_DOMAINS);\n\n/**\n * Build the private-context pattern list. Accepts an optional email pattern override so that\n * operators can supply a custom placeholder-domain allow-list (Issue #1383).\n */\nfunction buildPrivateContextPatterns(\n emailPattern: RegExp,\n): Array<[name: string, pattern: RegExp, description: string]> {\n return [\n [\"private-ip\", PRIVATE_IP_PATTERN, \"Private IP / host inventory detected (replace with placeholders)\"],\n [\n \"home-path-linux\",\n /(?:^|[\\s\"'=:])\\/home\\/(?!user\\/|runner\\/|ubuntu\\/)[^\\s\"'/:]+\\/[^\\s\"']+/,\n \"User-specific /home/... path detected (replace with placeholders)\",\n ],\n [\n \"home-path-macos\",\n /(?:^|[\\s\"'=:])\\/Users\\/(?!user\\/|runner\\/)[^\\s\"'/:]+\\/[^\\s\"']+/,\n \"User-specific /Users/... path detected (replace with placeholders)\",\n ],\n [\n \"tilde-home-path\",\n // Negative lookahead (?!\\.) has been removed — dotfile paths (e.g. ~/.ssh, ~/.aws)\n // are the most sensitive and must be flagged, not excluded (Issue #1384).\n /(?:^|[\\s\"'=:])~\\/[^\\s\"']+/,\n \"Tilde home path detected (replace with placeholders; avoid personal paths in skills)\",\n ],\n [\"email-address\", emailPattern, \"Non-example email address detected (remove or replace with example.com)\"],\n ];\n}\n\nconst DENY_RULES: DenyRule[] = [\n // Shell execution\n {\n name: \"shell-eval\",\n codeBlockOnly: true,\n pattern: new RegExp(\"\\\\bev\" + \"al\\\\s*[\\\\(\\\\$\" + \"'\\\"`]\", \"i\"),\n description: \"ev\" + \"al() or ev\" + \"al$(...) in code block — arbitrary code execution\",\n },\n {\n name: \"shell-exec-func\",\n codeBlockOnly: true,\n pattern: /\\bexec\\s*\\(/i,\n description: \"exec() function call in code block\",\n },\n {\n name: \"shell-system\",\n codeBlockOnly: true,\n pattern: /\\bsystem\\s*\\(/i,\n description: \"system() call in code block\",\n },\n {\n name: \"shell-spawn\",\n codeBlockOnly: true,\n pattern: /\\bspawn\\s*\\(/i,\n description: \"spawn() call in code block — process creation\",\n },\n // Credential access\n {\n name: \"credential-env-secret\",\n codeBlockOnly: true,\n pattern: /\\$\\{?(?:\\w*(?:API_KEY|SECRET|PASSWORD|TOKEN|PRIVATE_KEY|ACCESS_KEY|AUTH|CREDENTIAL)\\w*)\\b/i,\n description: \"Environment variable referencing a credential secret\",\n },\n {\n name: \"ssh-command\",\n codeBlockOnly: true,\n pattern: /\\bssh\\s+(-\\w+\\s+)*\\w+@/i,\n description: \"SSH command with user@host in code block\",\n },\n // Network calls\n {\n name: \"curl-call\",\n codeBlockOnly: true,\n pattern: /\\bcurl\\s+/i,\n description: \"curl network call in code block\",\n },\n {\n name: \"wget-call\",\n codeBlockOnly: true,\n pattern: /\\bwget\\s+/i,\n description: \"wget network call in code block\",\n },\n {\n name: \"http-fetch\",\n codeBlockOnly: true,\n pattern: /\\bfetch\\s*\\(\\s*['\"]https?:\\/\\//i,\n description: \"HTTP fetch() call to external URL in code block\",\n },\n // Filesystem writes to absolute/dangerous paths\n {\n name: \"rm-rf\",\n codeBlockOnly: true,\n pattern: /\\brm\\s+-[rf]+\\s+\\/|rm\\s+-[rf]+\\s+~\\//i,\n description: \"Recursive deletion from absolute or home path in code block\",\n },\n {\n name: \"write-to-etc\",\n codeBlockOnly: true,\n pattern: />\\s*\\/etc\\//i,\n description: \"Redirect write to /etc/ in code block\",\n },\n {\n name: \"write-to-root\",\n codeBlockOnly: true,\n pattern: />\\s*\\/(?:usr|bin|sbin|root|boot)\\//i,\n description: \"Redirect write to system path in code block\",\n },\n // Dangerous JavaScript/TypeScript in non-SKILL context\n {\n name: \"require-fs\",\n codeBlockOnly: true,\n pattern: /require\\s*\\(\\s*['\"](?:child_process|fs|path)['\"]\\)/i,\n description: \"require('child_process'|'fs'|'path') in code block\",\n },\n {\n name: \"import-child-process\",\n codeBlockOnly: true,\n pattern: /import\\s+.*from\\s+['\"]child_process['\"]/i,\n description: \"import from 'child_process' in code block\",\n },\n];\n\n// ---------------------------------------------------------------------------\n// SkillValidator\n// ---------------------------------------------------------------------------\n\nexport class SkillValidator {\n private readonly privateContextPatterns: Array<[name: string, pattern: RegExp, description: string]>;\n private readonly sectionTaxonomyOverrides?: SectionTaxonomyOverrides;\n\n /**\n * @param options.emailPattern - custom non-placeholder email regex; defaults to\n * {@link NON_PLACEHOLDER_EMAIL_PATTERN}. Build with {@link buildNonPlaceholderEmailPattern}.\n * @param options.sectionTaxonomyOverrides - optional per-category section taxonomy overrides\n * (passed through from CrystallizationProposer; Issue #1408).\n */\n constructor(options?: { emailPattern?: RegExp; sectionTaxonomyOverrides?: SectionTaxonomyOverrides }) {\n this.privateContextPatterns = buildPrivateContextPatterns(options?.emailPattern ?? NON_PLACEHOLDER_EMAIL_PATTERN);\n this.sectionTaxonomyOverrides = options?.sectionTaxonomyOverrides;\n }\n\n /**\n * Validate generated SKILL.md content for:\n * - security violations (deny rules inside code blocks)\n * - required structure/sections for reusable workflows\n * - anti-log-dumping guardrails (transcript/log blobs)\n */\n validate(skillContent: string): ValidationResult {\n const violations: string[] = [];\n const rawBytes = utf8ByteLength(skillContent);\n if (rawBytes > MAX_SKILL_FILE_BYTES) {\n return {\n valid: false,\n violations: [\n `Skill exceeds OpenClaw loader byte limit (${rawBytes} > ${MAX_SKILL_FILE_BYTES}). Shrink SKILL.md or move deterministic detail into bounded sidecars.`,\n ],\n };\n }\n\n const normalizedSkillContent = stripLeadingHtmlComments(skillContent);\n const lines = normalizedSkillContent.split(\"\\n\");\n\n if (lines.length > MAX_SKILL_LINES) {\n violations.push(\n `Skill exceeds ${MAX_SKILL_LINES} lines (${lines.length}). Summarize logs/transcripts into workflow phases and checklists.`,\n );\n }\n\n const skillBytes = Buffer.byteLength(normalizedSkillContent, \"utf8\");\n if (skillBytes > MAX_SKILL_FILE_BYTES_SAFE) {\n violations.push(\n `Skill exceeds ${MAX_SKILL_FILE_BYTES_SAFE} bytes (${skillBytes}). Use progressive disclosure and bounded sidecars.`,\n );\n }\n\n if (/\\*\\*\\w+\\*\\*\\s*\\{/.test(normalizedSkillContent)) {\n violations.push(\"Legacy raw tool-call dump format detected (**tool** {json}); summarize into workflow phases.\");\n }\n\n if (/\\b[A-Za-z]:\\\\[^\\s/]+/.test(normalizedSkillContent) || /\\b[A-Za-z]:\\\\/.test(normalizedSkillContent)) {\n violations.push(\"Windows-style paths are not allowed; use forward-slash paths only.\");\n }\n\n const bodyLower = normalizedSkillContent.toLowerCase();\n const usesProcedure = /\\bprocedure\\b/.test(bodyLower);\n const usesWorkflow = /\\bworkflow\\b/.test(bodyLower);\n if (usesProcedure && usesWorkflow && /\\bthis procedure\\b|\\bthe procedure\\b/i.test(normalizedSkillContent)) {\n violations.push(\n \"Mixed procedure/workflow terminology: prefer workflow (user-facing) and recipe (machine-readable sidecar).\",\n );\n }\n\n // Secrets are a hard-fail regardless of where they appear.\n for (const [name, pattern, desc] of SECRET_PATTERNS) {\n if (pattern.test(skillContent)) {\n violations.push(`[${name}] ${desc}`);\n }\n }\n\n // Reject private context that should not be embedded into reusable skills.\n for (const [name, pattern, desc] of this.privateContextPatterns) {\n if (pattern.test(skillContent)) {\n violations.push(`[${name}] ${desc}`);\n }\n }\n\n // Required frontmatter + sections are enforced to prevent transcript-dumps.\n const frontmatter = parseFrontmatter(lines);\n if (!frontmatter.present) {\n violations.push(\"Missing required YAML frontmatter (--- ... ---) with name/description/category/provenance.\");\n } else {\n const keys = frontmatter.keys;\n for (const req of [\"name\", \"description\"] as const) {\n if (!keys.has(req)) violations.push(`Frontmatter missing required field: ${req}`);\n }\n const desc = keys.get(\"description\") ?? \"\";\n if (desc.length > MAX_SKILL_DESCRIPTION_CHARS) {\n violations.push(`Frontmatter description exceeds ${MAX_SKILL_DESCRIPTION_CHARS} characters.`);\n }\n const hasCategory =\n CATEGORY_FRONTMATTER_KEYS.some((key) => keys.has(key)) ||\n keys.has(\"metadata.category\") ||\n keys.has(\"metadata.tags\") ||\n keys.has(\"metadata.type\");\n if (!hasCategory) violations.push(\"Frontmatter missing required category (or equivalent: tags/type/kind).\");\n const hasProvenance =\n keys.has(\"provenance\") ||\n keys.has(\"metadata.provenance\") ||\n keys.has(\"generated_from\") ||\n keys.has(\"generatedfrom\") ||\n keys.has(\"source\") ||\n keys.has(\"source_pattern_id\") ||\n keys.has(\"source_procedure_id\");\n if (!hasProvenance) violations.push(\"Frontmatter missing provenance/generated-from metadata reference.\");\n }\n\n const headings = parseH2Headings(lines);\n // Use the shared taxonomy from config/skill-sections.ts so that both\n // SkillValidator and GeneratedSkillValidationService check the same sections\n // (issues #1375, #1408).\n const frontmatterCategory = frontmatter.present ? getFrontmatterCategory(frontmatter.keys) : undefined;\n const requiredSections = getSectionTaxonomy(frontmatterCategory, this.sectionTaxonomyOverrides);\n\n for (const section of requiredSections) {\n if (!hasHeadingAlias(headings, section.aliases)) {\n violations.push(`Missing required section: ${section.label}`);\n }\n }\n\n // Examples must include at least one compact real example (avoid placeholders).\n const examplesBody = extractSectionBody(lines, headings, [\"examples\"]);\n if (examplesBody !== null) {\n const examplesTrimmed = examplesBody.trim();\n if (examplesTrimmed.length === 0 || !containsConcreteExample(examplesBody)) {\n violations.push(\n \"Examples section must include at least one compact, concrete example (not just placeholders).\",\n );\n }\n }\n\n let inCodeBlock = false;\n let fenceChar: \"`\" | \"~\" | null = null;\n let fenceOpenLen = 0;\n let currentFenceStartLine = 0;\n let currentFenceLines = 0;\n let lineNumber = 0;\n let codeBlockLineCount = 0;\n let transcriptToolOrStackLineCount = 0;\n let toolBlobLikeLineCount = 0;\n\n for (const line of lines) {\n lineNumber++;\n const trimmed = line.trim();\n\n // Track fenced blocks; closing delimiter must match opener (``` vs ~~~) and length (CommonMark).\n // Closing lines must be fence + optional whitespace only — not ```json mid-block, etc.\n const fenceStart = trimmed.match(/^(`{3,}|~{3,})/);\n if (fenceStart) {\n const marker = fenceStart[1];\n const ch = marker[0] as \"`\" | \"~\";\n const len = marker.length;\n const afterMarker = trimmed.slice(marker.length);\n const isCloseLine = /^\\s*$/.test(afterMarker);\n if (!inCodeBlock) {\n inCodeBlock = true;\n fenceChar = ch;\n fenceOpenLen = len;\n currentFenceStartLine = lineNumber;\n currentFenceLines = 0;\n const contextLine = findPreviousNonEmptyLine(lines, lineNumber - 2);\n if (!contextLine || /^#{1,6}\\s+/.test(contextLine) || /^(`{3,}|~{3,})/.test(contextLine)) {\n violations.push(\n `Line ${lineNumber}: [codeblock-context] Code/log snippet must have a preceding explanatory sentence (avoid raw dumps).`,\n );\n }\n continue;\n }\n if (isCloseLine && fenceChar === ch && len >= fenceOpenLen) {\n if (currentFenceLines > MAX_FENCED_BLOCK_LINES) {\n violations.push(\n `Line ${currentFenceStartLine}: [codeblock-size] Fenced code/log block has ${currentFenceLines} lines (max ${MAX_FENCED_BLOCK_LINES}). Summarize instead.`,\n );\n }\n inCodeBlock = false;\n fenceChar = null;\n fenceOpenLen = 0;\n continue;\n }\n }\n\n if (inCodeBlock) {\n currentFenceLines++;\n codeBlockLineCount++;\n } else {\n const transcriptLike = /^\\s*(assistant|user|tool|system)\\s*:/i.test(line);\n const toolBlobLike = looksLikeToolOrJsonBlob(trimmed);\n const stackTraceLike = looksLikeStackTraceLine(trimmed);\n if (toolBlobLike) toolBlobLikeLineCount++;\n if (transcriptLike || toolBlobLike || stackTraceLike) transcriptToolOrStackLineCount++;\n }\n\n // Apply rules\n for (const rule of DENY_RULES) {\n if (rule.codeBlockOnly && !inCodeBlock) continue;\n if (rule.pattern.test(line)) {\n violations.push(`Line ${lineNumber}: [${rule.name}] ${rule.description} — \"${trimmed.slice(0, 80)}\"`);\n }\n }\n\n // Additional check: any code block containing shell-like content should\n // not have backtick command substitution\n if (inCodeBlock && /\\$\\([^)]+\\)/.test(line)) {\n violations.push(\n `Line ${lineNumber}: [shell-subst] Command substitution $(...) in code block — \"${trimmed.slice(0, 80)}\"`,\n );\n }\n }\n\n if (inCodeBlock && currentFenceLines > MAX_FENCED_BLOCK_LINES) {\n violations.push(\n `Line ${currentFenceStartLine}: [codeblock-size] Fenced code/log block has ${currentFenceLines} lines (max ${MAX_FENCED_BLOCK_LINES}) and no closing fence at end of file. Summarize instead.`,\n );\n }\n\n const rawLikeRatio =\n lines.length === 0 ? 0 : (codeBlockLineCount + transcriptToolOrStackLineCount) / Math.max(1, lines.length);\n if (rawLikeRatio > MAX_TRANSCRIPT_LIKE_RATIO) {\n violations.push(\n `[log-dump-guard] ${Math.round(rawLikeRatio * 100)}% of lines look like raw transcript/log/code. Summarize and keep snippets short.`,\n );\n }\n if (toolBlobLikeLineCount > MAX_TOOL_BLOB_LINES) {\n violations.push(\n `[tool-blob-guard] Detected ${toolBlobLikeLineCount} tool/JSON-like lines outside code blocks. Summarize tool-call blobs into workflow steps and checklists instead of pasting raw dumps.`,\n );\n }\n\n return {\n valid: violations.length === 0,\n violations,\n };\n }\n\n /**\n * Quick check: returns true if content passes validation.\n */\n isValid(skillContent: string): boolean {\n return this.validate(skillContent).valid;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Markdown helpers (lightweight, intentionally conservative)\n// ---------------------------------------------------------------------------\n\nexport function normalizeHeading(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}/\\s-]+/gu, \"\")\n .replace(/\\s+/g, \" \");\n}\n\nexport function parseH2Headings(lines: string[]): Array<{ raw: string; normalized: string; line: number }> {\n const out: Array<{ raw: string; normalized: string; line: number }> = [];\n let inFence = false;\n let fenceChar: \"`\" | \"~\" | null = null;\n let fenceOpenLen = 0;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n const trimmed = line.trim();\n const fenceStart = trimmed.match(/^(`{3,}|~{3,})/);\n if (fenceStart) {\n const marker = fenceStart[1];\n const ch = marker[0] as \"`\" | \"~\";\n const len = marker.length;\n const afterMarker = trimmed.slice(marker.length);\n const isCloseLine = /^\\s*$/.test(afterMarker);\n if (!inFence) {\n inFence = true;\n fenceChar = ch;\n fenceOpenLen = len;\n continue;\n }\n if (isCloseLine && fenceChar === ch && len >= fenceOpenLen) {\n inFence = false;\n fenceChar = null;\n fenceOpenLen = 0;\n continue;\n }\n continue;\n }\n if (inFence) continue;\n const match = line.match(/^##\\s+(.+?)\\s*$/);\n if (!match) continue;\n const raw = match[1];\n out.push({ raw, normalized: normalizeHeading(raw), line: i + 1 });\n }\n return out;\n}\n\nexport function hasHeadingAlias(headings: Array<{ normalized: string }>, aliases: string[]): boolean {\n const normalizedAliases = aliases.map(normalizeHeading);\n return headings.some((h) => normalizedAliases.includes(h.normalized));\n}\n\nfunction extractSectionBody(\n lines: string[],\n headings: Array<{ normalized: string; line: number }>,\n aliases: string[],\n): string | null {\n const normalizedAliases = new Set(aliases.map(normalizeHeading));\n const start = headings.find((h) => normalizedAliases.has(h.normalized));\n if (!start) return null;\n const afterStartIndex = start.line; // 1-based line number; body starts after it\n const next = headings.filter((h) => h.line > start.line).sort((a, b) => a.line - b.line)[0];\n const endLine = next ? next.line - 1 : lines.length;\n return lines.slice(afterStartIndex, endLine).join(\"\\n\").trim();\n}\n\nfunction containsConcreteExample(examplesBody: string): boolean {\n const lines = examplesBody.split(\"\\n\").map((l) => l.trim());\n const nonEmpty = lines.filter(Boolean);\n\n const listOk = nonEmpty.some((l) => {\n if (!/^(?:[-*+]|\\d+\\.)\\s+\\S/.test(l)) return false;\n if (/\\b(?:tbd|todo|placeholder|example here|fill in)\\b/i.test(l)) return false;\n if (l.length < 18) return false;\n if (!ACTION_VERB_PATTERN.test(l)) return false;\n return true;\n });\n if (listOk) return true;\n\n return nonEmpty.some((l) => {\n if (/^(?:[-*+]|\\d+\\.)\\s/.test(l)) return false;\n if (l.length < 40) return false;\n if (!ACTION_VERB_PATTERN.test(l)) return false;\n const words = l.split(/\\s+/).filter(Boolean);\n if (words.length < 4) return false;\n if (l.length > 15 && l === l.toUpperCase()) return false;\n if (/\\b(?:tbd|todo|placeholder|example here|fill in)\\b/i.test(l)) return false;\n return true;\n });\n}\n\nfunction findPreviousNonEmptyLine(lines: string[], startIndex: number): string | null {\n for (let i = startIndex; i >= 0; i--) {\n const trimmed = (lines[i] ?? \"\").trim();\n if (trimmed.length === 0) continue;\n return trimmed;\n }\n return null;\n}\n\nfunction unquoteFrontmatterValue(value: string | undefined): string | undefined {\n if (value == null) return undefined;\n const trimmed = value.trim();\n if (trimmed.length >= 2) {\n const first = trimmed[0];\n const last = trimmed[trimmed.length - 1];\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\n return trimmed.slice(1, -1).trim();\n }\n }\n return trimmed;\n}\n\nfunction getFrontmatterCategory(keys: Map<string, string>): string | undefined {\n const metaCategory = keys.get(\"metadata.category\");\n if (metaCategory) return unquoteFrontmatterValue(metaCategory);\n for (const key of CATEGORY_FRONTMATTER_KEYS) {\n const value = keys.get(key);\n if (value) return unquoteFrontmatterValue(value);\n }\n return undefined;\n}\n\nfunction parseFrontmatter(lines: string[]): {\n present: boolean;\n keys: Map<string, string>;\n endLine: number;\n} {\n const originalContent = lines.join(\"\\n\");\n const body = stripLeadingHtmlComments(originalContent);\n const bodyLines = body.split(\"\\n\");\n const strippedLineCount = originalContent.split(\"\\n\").length - bodyLines.length;\n if (bodyLines[0]?.trim() !== \"---\") {\n return { present: false, keys: new Map(), endLine: -1 };\n }\n let endIdx = -1;\n for (let i = 1; i < bodyLines.length; i++) {\n if (bodyLines[i]?.trim() === \"---\") {\n endIdx = i;\n break;\n }\n }\n if (endIdx < 0) return { present: false, keys: new Map(), endLine: -1 };\n const block = bodyLines.slice(1, endIdx).join(\"\\n\");\n const keys = parseSkillFrontmatterKeys(block);\n return { present: true, keys, endLine: endIdx + 1 + strippedLineCount };\n}\n\nfunction looksLikeToolOrJsonBlob(trimmed: string): boolean {\n if (trimmed.length === 0) return false;\n if (/^diff --git\\b/i.test(trimmed) || /^@@\\s+[-+]\\d+/i.test(trimmed)) return true;\n if (/^\\s*\\{/.test(trimmed) || /^\\s*\\[/.test(trimmed)) {\n if (trimmed.includes(`\":`) || trimmed.includes(\"':\")) return true;\n }\n if (/\"tool\"\\s*:|\"toolCall\"|tool_call_id|\"args\"\\s*:|\"content\"\\s*:/i.test(trimmed)) return true;\n if (/^\\s*Command:\\s+/i.test(trimmed) || /^\\s*Output:\\s+/i.test(trimmed)) return true;\n return false;\n}\n\nfunction looksLikeStackTraceLine(trimmed: string): boolean {\n if (trimmed.length === 0) return false;\n if (/^\\s*at\\s+\\S+/.test(trimmed)) return true;\n if (/^(?:Caused by:|Exception in thread|Traceback \\(most recent call last\\):)/i.test(trimmed)) return true;\n return false;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"startup-memory-attribution.js","names":[],"sources":["../../services/startup-memory-attribution.ts"],"sourcesContent":["type LoggerLike = {\n info?: (msg: string) => void;\n};\n\ntype CheckpointTagValue = string | number | boolean | null | undefined;\n\nexport interface StartupMemoryCheckpointEntry {\n timestamp: string;\n owner: string;\n subsystem: string;\n operation: string;\n phase: string;\n rssBytes: number;\n rssDeltaBytes: number;\n heapUsedBytes: number;\n heapTotalBytes: number;\n externalBytes: number;\n arrayBuffersBytes: number;\n activeHandles: number;\n activeRequests: number;\n tags: Record<string, CheckpointTagValue>;\n}\n\nconst MAX_STORED_ENTRIES = 64;\nconst emittedOnceKeys = new Set<string>();\nconst entries: StartupMemoryCheckpointEntry[] = [];\nlet baselineRssBytes: number | null = null;\n\nfunction getActiveHandlesCount(): number {\n // Undocumented Node.js internals used for diagnostics-only attribution.\n // Return -1 when not available on the current runtime.\n const proc = process as NodeJS.Process & { _getActiveHandles?: () => unknown[] };\n if (typeof proc._getActiveHandles !== \"function\") return -1;\n return proc._getActiveHandles()?.length ?? -1;\n}\n\nfunction getActiveRequestsCount(): number {\n // Undocumented Node.js internals used for diagnostics-only attribution.\n // Return -1 when not available on the current runtime.\n const proc = process as NodeJS.Process & { _getActiveRequests?: () => unknown[] };\n if (typeof proc._getActiveRequests !== \"function\") return -1;\n return proc._getActiveRequests()?.length ?? -1;\n}\n\nfunction stringifyTags(tags: Record<string, CheckpointTagValue>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(tags)) {\n if (value === undefined || value === null) continue;\n parts.push(`${key}=${String(value)}`);\n }\n return parts.length > 0 ? ` ${parts.join(\" \")}` : \"\";\n}\n\nexport function resetStartupMemoryAttributionForTests(): void {\n emittedOnceKeys.clear();\n entries.length = 0;\n baselineRssBytes = null;\n}\n\nexport function resetStartupMemoryAttribution(): void {\n emittedOnceKeys.clear();\n baselineRssBytes = null;\n}\n\nexport function getStartupMemoryAttributionEntries(): StartupMemoryCheckpointEntry[] {\n return [...entries];\n}\n\nexport function recordStartupMemoryCheckpoint(opts: {\n logger?: LoggerLike;\n subsystem: string;\n operation: string;\n phase: string;\n owner?: string;\n onceKey?: string;\n tags?: Record<string, CheckpointTagValue>;\n}): StartupMemoryCheckpointEntry | null {\n const onceKey = opts.onceKey ?? `${opts.subsystem}:${opts.operation}:${opts.phase}`;\n if (emittedOnceKeys.has(onceKey)) {\n return null;\n }\n emittedOnceKeys.add(onceKey);\n\n const usage = process.memoryUsage();\n if (baselineRssBytes === null) {\n baselineRssBytes = usage.rss;\n }\n\n const entry: StartupMemoryCheckpointEntry = {\n timestamp: new Date().toISOString(),\n owner: opts.owner ?? \"hybrid-memory\",\n subsystem: opts.subsystem,\n operation: opts.operation,\n phase: opts.phase,\n rssBytes: usage.rss,\n rssDeltaBytes: usage.rss - baselineRssBytes,\n heapUsedBytes: usage.heapUsed,\n heapTotalBytes: usage.heapTotal,\n externalBytes: usage.external,\n arrayBuffersBytes: usage.arrayBuffers,\n activeHandles: getActiveHandlesCount(),\n activeRequests: getActiveRequestsCount(),\n tags: opts.tags ?? {},\n };\n\n entries.push(entry);\n if (entries.length > MAX_STORED_ENTRIES) {\n entries.shift();\n }\n\n opts.logger?.info?.(\n `memory-hybrid: startup-memory-checkpoint owner=${entry.owner} subsystem=${entry.subsystem} operation=${entry.operation} phase=${entry.phase} rssBytes=${entry.rssBytes} rssDeltaBytes=${entry.rssDeltaBytes} heapUsedBytes=${entry.heapUsedBytes} heapTotalBytes=${entry.heapTotalBytes} externalBytes=${entry.externalBytes} arrayBuffersBytes=${entry.arrayBuffersBytes} activeHandles=${entry.activeHandles} activeRequests=${entry.activeRequests}${stringifyTags(entry.tags)}`,\n );\n\n return entry;\n}\n"],"mappings":";AAuBA,MAAM,qBAAqB;AAC3B,MAAM,kCAAkB,IAAI,KAAa;AACzC,MAAM,UAA0C,EAAE;AAClD,IAAI,mBAAkC;AAEtC,SAAS,wBAAgC;CAGvC,MAAM,OAAO;CACb,IAAI,OAAO,KAAK,sBAAsB,YAAY,OAAO;CACzD,OAAO,KAAK,mBAAmB,EAAE,UAAU;;AAG7C,SAAS,yBAAiC;CAGxC,MAAM,OAAO;CACb,IAAI,OAAO,KAAK,uBAAuB,YAAY,OAAO;CAC1D,OAAO,KAAK,oBAAoB,EAAE,UAAU;;AAG9C,SAAS,cAAc,MAAkD;CACvE,MAAM,QAAkB,EAAE;CAC1B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;EAC/C,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM;EAC3C,MAAM,KAAK,GAAG,IAAI,GAAG,OAAO,MAAM,GAAG;;CAEvC,OAAO,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,IAAI,KAAK;;AASpD,SAAgB,gCAAsC;CACpD,gBAAgB,OAAO;CACvB,mBAAmB;;AAOrB,SAAgB,8BAA8B,MAQN;CACtC,MAAM,UAAU,KAAK,WAAW,GAAG,KAAK,UAAU,GAAG,KAAK,UAAU,GAAG,KAAK;CAC5E,IAAI,gBAAgB,IAAI,QAAQ,EAC9B,OAAO;CAET,gBAAgB,IAAI,QAAQ;CAE5B,MAAM,QAAQ,QAAQ,aAAa;CACnC,IAAI,qBAAqB,MACvB,mBAAmB,MAAM;CAG3B,MAAM,QAAsC;EAC1C,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,OAAO,KAAK,SAAS;EACrB,WAAW,KAAK;EAChB,WAAW,KAAK;EAChB,OAAO,KAAK;EACZ,UAAU,MAAM;EAChB,eAAe,MAAM,MAAM;EAC3B,eAAe,MAAM;EACrB,gBAAgB,MAAM;EACtB,eAAe,MAAM;EACrB,mBAAmB,MAAM;EACzB,eAAe,uBAAuB;EACtC,gBAAgB,wBAAwB;EACxC,MAAM,KAAK,QAAQ,EAAE;EACtB;CAED,QAAQ,KAAK,MAAM;CACnB,IAAI,QAAQ,SAAS,oBACnB,QAAQ,OAAO;CAGjB,KAAK,QAAQ,OACX,kDAAkD,MAAM,MAAM,aAAa,MAAM,UAAU,aAAa,MAAM,UAAU,SAAS,MAAM,MAAM,YAAY,MAAM,SAAS,iBAAiB,MAAM,cAAc,iBAAiB,MAAM,cAAc,kBAAkB,MAAM,eAAe,iBAAiB,MAAM,cAAc,qBAAqB,MAAM,kBAAkB,iBAAiB,MAAM,cAAc,kBAAkB,MAAM,iBAAiB,cAAc,MAAM,KAAK,GACnd;CAED,OAAO"}
1
+ {"version":3,"file":"startup-memory-attribution.js","names":[],"sources":["../../services/startup-memory-attribution.ts"],"sourcesContent":["type LoggerLike = {\n info?: (msg: string) => void;\n};\n\ntype CheckpointTagValue = string | number | boolean | null | undefined;\n\nexport interface StartupMemoryCheckpointEntry {\n timestamp: string;\n owner: string;\n subsystem: string;\n operation: string;\n phase: string;\n rssBytes: number;\n rssDeltaBytes: number;\n heapUsedBytes: number;\n heapTotalBytes: number;\n externalBytes: number;\n arrayBuffersBytes: number;\n activeHandles: number;\n activeRequests: number;\n tags: Record<string, CheckpointTagValue>;\n}\n\nconst MAX_STORED_ENTRIES = 64;\nconst emittedOnceKeys = new Set<string>();\nconst entries: StartupMemoryCheckpointEntry[] = [];\nlet baselineRssBytes: number | null = null;\n\nfunction getActiveHandlesCount(): number {\n // Undocumented Node.js internals used for diagnostics-only attribution.\n // Return -1 when not available on the current runtime.\n const proc = process as NodeJS.Process & { _getActiveHandles?: () => unknown[] };\n if (typeof proc._getActiveHandles !== \"function\") return -1;\n return proc._getActiveHandles()?.length ?? -1;\n}\n\nfunction getActiveRequestsCount(): number {\n // Undocumented Node.js internals used for diagnostics-only attribution.\n // Return -1 when not available on the current runtime.\n const proc = process as NodeJS.Process & { _getActiveRequests?: () => unknown[] };\n if (typeof proc._getActiveRequests !== \"function\") return -1;\n return proc._getActiveRequests()?.length ?? -1;\n}\n\nfunction stringifyTags(tags: Record<string, CheckpointTagValue>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(tags)) {\n if (value === undefined || value === null) continue;\n parts.push(`${key}=${String(value)}`);\n }\n return parts.length > 0 ? ` ${parts.join(\" \")}` : \"\";\n}\n\nexport function resetStartupMemoryAttributionForTests(): void {\n emittedOnceKeys.clear();\n entries.length = 0;\n baselineRssBytes = null;\n}\n\nexport function resetStartupMemoryAttribution(): void {\n emittedOnceKeys.clear();\n baselineRssBytes = null;\n}\n\nexport function getStartupMemoryAttributionEntries(): StartupMemoryCheckpointEntry[] {\n return [...entries];\n}\n\nexport function recordStartupMemoryCheckpoint(opts: {\n logger?: LoggerLike;\n subsystem: string;\n operation: string;\n phase: string;\n owner?: string;\n onceKey?: string;\n tags?: Record<string, CheckpointTagValue>;\n}): StartupMemoryCheckpointEntry | null {\n const onceKey = opts.onceKey ?? `${opts.subsystem}:${opts.operation}:${opts.phase}`;\n if (emittedOnceKeys.has(onceKey)) {\n return null;\n }\n emittedOnceKeys.add(onceKey);\n\n const usage = process.memoryUsage();\n if (baselineRssBytes === null) {\n baselineRssBytes = usage.rss;\n }\n\n const entry: StartupMemoryCheckpointEntry = {\n timestamp: new Date().toISOString(),\n owner: opts.owner ?? \"hybrid-memory\",\n subsystem: opts.subsystem,\n operation: opts.operation,\n phase: opts.phase,\n rssBytes: usage.rss,\n rssDeltaBytes: usage.rss - baselineRssBytes,\n heapUsedBytes: usage.heapUsed,\n heapTotalBytes: usage.heapTotal,\n externalBytes: usage.external,\n arrayBuffersBytes: usage.arrayBuffers,\n activeHandles: getActiveHandlesCount(),\n activeRequests: getActiveRequestsCount(),\n tags: opts.tags ?? {},\n };\n\n entries.push(entry);\n if (entries.length > MAX_STORED_ENTRIES) {\n entries.shift();\n }\n\n opts.logger?.info?.(\n `memory-hybrid: startup-memory-checkpoint owner=${entry.owner} subsystem=${entry.subsystem} operation=${entry.operation} phase=${entry.phase} rssBytes=${entry.rssBytes} rssDeltaBytes=${entry.rssDeltaBytes} heapUsedBytes=${entry.heapUsedBytes} heapTotalBytes=${entry.heapTotalBytes} externalBytes=${entry.externalBytes} arrayBuffersBytes=${entry.arrayBuffersBytes} activeHandles=${entry.activeHandles} activeRequests=${entry.activeRequests}${stringifyTags(entry.tags)}`,\n );\n\n return entry;\n}\n"],"mappings":";AAuBA,MAAM,qBAAqB;AAC3B,MAAM,kCAAkB,IAAI,IAAY;AACxC,MAAM,UAA0C,CAAC;AACjD,IAAI,mBAAkC;AAEtC,SAAS,wBAAgC;CAGvC,MAAM,OAAO;CACb,IAAI,OAAO,KAAK,sBAAsB,YAAY,OAAO;CACzD,OAAO,KAAK,kBAAkB,GAAG,UAAU;AAC7C;AAEA,SAAS,yBAAiC;CAGxC,MAAM,OAAO;CACb,IAAI,OAAO,KAAK,uBAAuB,YAAY,OAAO;CAC1D,OAAO,KAAK,mBAAmB,GAAG,UAAU;AAC9C;AAEA,SAAS,cAAc,MAAkD;CACvE,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,GAAG;EAC/C,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM;EAC3C,MAAM,KAAK,GAAG,IAAI,GAAG,OAAO,KAAK,GAAG;CACtC;CACA,OAAO,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,GAAG,MAAM;AACpD;AAQA,SAAgB,gCAAsC;CACpD,gBAAgB,MAAM;CACtB,mBAAmB;AACrB;AAMA,SAAgB,8BAA8B,MAQN;CACtC,MAAM,UAAU,KAAK,WAAW,GAAG,KAAK,UAAU,GAAG,KAAK,UAAU,GAAG,KAAK;CAC5E,IAAI,gBAAgB,IAAI,OAAO,GAC7B,OAAO;CAET,gBAAgB,IAAI,OAAO;CAE3B,MAAM,QAAQ,QAAQ,YAAY;CAClC,IAAI,qBAAqB,MACvB,mBAAmB,MAAM;CAG3B,MAAM,QAAsC;EAC1C,4BAAW,IAAI,KAAK,GAAE,YAAY;EAClC,OAAO,KAAK,SAAS;EACrB,WAAW,KAAK;EAChB,WAAW,KAAK;EAChB,OAAO,KAAK;EACZ,UAAU,MAAM;EAChB,eAAe,MAAM,MAAM;EAC3B,eAAe,MAAM;EACrB,gBAAgB,MAAM;EACtB,eAAe,MAAM;EACrB,mBAAmB,MAAM;EACzB,eAAe,sBAAsB;EACrC,gBAAgB,uBAAuB;EACvC,MAAM,KAAK,QAAQ,CAAC;CACtB;CAEA,QAAQ,KAAK,KAAK;CAClB,IAAI,QAAQ,SAAS,oBACnB,QAAQ,MAAM;CAGhB,KAAK,QAAQ,OACX,kDAAkD,MAAM,MAAM,aAAa,MAAM,UAAU,aAAa,MAAM,UAAU,SAAS,MAAM,MAAM,YAAY,MAAM,SAAS,iBAAiB,MAAM,cAAc,iBAAiB,MAAM,cAAc,kBAAkB,MAAM,eAAe,iBAAiB,MAAM,cAAc,qBAAqB,MAAM,kBAAkB,iBAAiB,MAAM,cAAc,kBAAkB,MAAM,iBAAiB,cAAc,MAAM,IAAI,GACnd;CAEA,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"task-hygiene.js","names":[],"sources":["../../services/task-hygiene.ts"],"sourcesContent":["/**\n * Task hygiene — stronger nudges for ACTIVE-TASKS.md rows (separate from Goals).\n * @see docs/TASK-HYGIENE.md\n */\n\nimport { basename } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport type { ActiveTaskEntry } from \"./active-task.js\";\nimport { isSubagentSession } from \"./active-task.js\";\nimport type { ActiveTaskLongRunningRegistrationMode } from \"../config/types/index.js\";\nimport { getEnv } from \"../utils/env-manager.js\";\nimport { execFile } from \"../utils/process-runner.js\";\n\nexport type LongRunningWorkflowKind = \"pr_queue\" | \"pr_until_merged\" | \"ci_monitor\" | \"issue_sweep\" | \"deployment\";\nexport type LongRunningRegistrationMode = ActiveTaskLongRunningRegistrationMode;\n\nexport type LongRunningWorkflowProposal = {\n kind: LongRunningWorkflowKind;\n label: string;\n description: string;\n next: string;\n repoContext?: string;\n};\n\n/**\n * Classification outcomes for a PR-backed task's live blocker state.\n * These are returned by `fetchLivePrBlockerStatus` and consumed by hygiene logic.\n */\nexport type PrBlockerStatus =\n | \"unresolved_review_threads\"\n | \"red_ci\"\n | \"pending_ci\"\n | \"merge_conflict\"\n | \"human_approval\"\n | \"no_live_blocker\";\n\nconst execFileAsync = promisify(execFile);\n\ninterface GhPrViewJson {\n number: number;\n mergeStateStatus?: string;\n reviewDecision?: string;\n reviewThreads?: {\n nodes: Array<{\n id: string;\n isResolved: boolean;\n isOutdated: boolean;\n }>;\n };\n mergeable?: string;\n statusCheckRollup?: Array<{\n state?: string;\n conclusion?: string;\n status?: string;\n }>;\n commits?: {\n nodes: Array<{ commit: { checkSuites?: { nodes: Array<{ conclusion?: string; status?: string }> } } }>;\n };\n}\n\nconst BLOCKING_CONCLUSIONS = new Set([\"FAILURE\", \"TIMED_OUT\", \"STARTUP_FAILURE\"]);\nconst PENDING_CHECK_STATES = new Set([\"IN_PROGRESS\", \"QUEUED\", \"PENDING\", \"WAITING\", \"REQUESTED\"]);\n\n/**\n * Fetch live PR state from GitHub (via `gh`) and classify the blocking status.\n *\n * This function exists to ensure hygiene does not mis-classify a PR as \"waiting/blocked\"\n * based on shallow signals (mergeStateStatus, reviewDecision) alone — it always checks\n * for actionable unresolved review threads before concluding there is no live blocker.\n *\n * Returns \"no_live_blocker\" only when CI is green/success, there are no unresolved threads,\n * no merge conflict, and no pending human approval.\n */\nexport async function fetchLivePrBlockerStatus(\n owner: string,\n repo: string,\n prNumber: number,\n opts: { signal?: AbortSignal } = {},\n): Promise<PrBlockerStatus> {\n const token = (getEnv(\"GITHUB_TOKEN\") ?? getEnv(\"GH_TOKEN\") ?? \"\").trim();\n if (!token) {\n // No token — skip live check, be conservative\n return \"no_live_blocker\";\n }\n\n const ghArgs = [\n \"pr\",\n \"view\",\n String(prNumber),\n \"--repo\",\n `${owner}/${repo}`,\n \"--json\",\n \"mergeStateStatus,reviewDecision,reviewThreads,mergeable,statusCheckRollup,commits\",\n \"--jq=.\",\n ];\n\n const ac = new AbortController();\n const timeout = setTimeout(() => ac.abort(), 20_000);\n const outerSignal = opts.signal ?? ac.signal;\n\n try {\n const { stdout } = await execFileAsync(\"gh\", ghArgs, {\n encoding: \"utf-8\",\n signal: outerSignal,\n timeout: 25_000,\n });\n const pr: GhPrViewJson = JSON.parse(stdout);\n\n // 1. Check for unresolved review threads (most important — actionable human feedback)\n const threads = pr.reviewThreads?.nodes ?? [];\n const unresolvedThreads = threads.filter((t) => !t.isResolved && !t.isOutdated);\n if (unresolvedThreads.length > 0) {\n return \"unresolved_review_threads\";\n }\n\n // 2. Check CI (from statusCheckRollup + checkSuites on latest commit)\n const statusCheckRollup = pr.statusCheckRollup ?? [];\n const commitNodes = pr.commits?.nodes ?? [];\n const latestCommit = commitNodes[commitNodes.length - 1]?.commit;\n const checkSuites = latestCommit?.checkSuites?.nodes ?? [];\n\n const hasFailure =\n statusCheckRollup.some(\n (c) =>\n c.state === \"FAILURE\" || c.state === \"TIMED_OUT\" || (c.conclusion && BLOCKING_CONCLUSIONS.has(c.conclusion)),\n ) || checkSuites.some((cs) => cs.conclusion && BLOCKING_CONCLUSIONS.has(cs.conclusion));\n const hasPending =\n statusCheckRollup.some(\n (c) => c.state === \"PENDING\" || c.state === \"IN_PROGRESS\" || (c.status && PENDING_CHECK_STATES.has(c.status)),\n ) || checkSuites.some((cs) => cs.status && PENDING_CHECK_STATES.has(cs.status));\n\n if (hasFailure) return \"red_ci\";\n if (hasPending) return \"pending_ci\";\n\n // 3. Check merge conflict\n if (pr.mergeStateStatus === \"BLOCKED\" || pr.mergeStateStatus === \"UNSTABLE\") {\n // Double-check via mergeable field if available\n if (pr.mergeable === \"CONFLICTING\") return \"merge_conflict\";\n }\n if (pr.mergeable === \"CONFLICTING\") return \"merge_conflict\";\n\n // 4. Human approval pending\n if (pr.reviewDecision === \"CHANGES_REQUESTED\") return \"human_approval\";\n if (pr.reviewDecision === \"REVIEW_REQUIRED\") return \"human_approval\";\n\n return \"no_live_blocker\";\n } catch {\n // Network or parse error — be conservative, don't modify task state\n return \"no_live_blocker\";\n } finally {\n clearTimeout(timeout);\n }\n}\n\nconst REPO_REF_RE = /\\b([a-z0-9][a-z0-9_.-]{1,98})\\/([a-z0-9][a-z0-9_.-]{1,98})\\b/gi;\nconst GITHUB_URL_RE =\n /(?:https?:\\/\\/)?(?:www\\.)?github\\.com\\/([a-z0-9][a-z0-9_.-]{1,98})\\/([a-z0-9][a-z0-9_.-]{1,98})(?:[/?#\\s,;.)]|$)/gi;\nconst PR_NUM_RE = /(?:\\bpr\\b|\\bpull request\\b)?\\s*#(\\d+)\\b/i;\nconst DEPLOY_TARGET_RE = /\\b(prod|production|staging|stage|qa|dev|preview)\\b/i;\nconst DEPLOY_ACTION_RE =\n /\\b(?:deploy|rollout)\\b|\\b(run|start|trigger|execute|perform|monitor|watch|track)\\b[\\s\\S]{0,30}\\b(deploy|deployment|rollout)\\b/i;\nconst RELEASE_TO_ENV_RE =\n /\\brelease\\b[\\s\\S]{0,25}\\b(to|into)\\b[\\s\\S]{0,10}\\b(prod|production|staging|stage|qa|dev|preview)\\b|\\b(run|start|trigger|execute|perform|monitor|watch|track)\\b[\\s\\S]{0,30}\\brelease\\b[\\s\\S]{0,25}\\b(to|into)\\b[\\s\\S]{0,10}\\b(prod|production|staging|stage|qa|dev|preview)\\b/i;\nconst ISSUE_SWEEP_RE =\n /\\bfix\\b[\\s\\S]{0,10}\\ball\\b[\\s\\S]{0,10}\\b(?:open\\s+|the\\s+)?issues?\\b(?!\\s+(?:with|on|for|from|at|about)|\\s+in\\s+(?!(?:this\\s+|the\\s+)?(?:repo|repository|github|backlog)))/i;\nconst GENERIC_WORKSPACE_NAMES = new Set([\"workspace\", \"workspaces\", \"tmp\", \"home\", \"openclaw\", \"task\"]);\nconst SLASH_NOISE_TOKENS = new Set([\"and\", \"or\", \"on\", \"off\", \"to\", \"for\", \"in\", \"by\", \"up\", \"down\", \"yes\", \"no\"]);\n\nfunction slugifyToken(value: string): string {\n const slug = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"task\";\n}\n\nfunction normalizeRepoToken(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/\\.git$/i, \"\")\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/-{2,}/g, \"-\")\n .replace(/^[-._]+|[-._]+$/g, \"\");\n}\n\nfunction toRepoContext(owner: string, repo: string): string | undefined {\n const o = normalizeRepoToken(owner);\n const r = normalizeRepoToken(repo);\n if (!o || !r) return undefined;\n if (o.includes(\".\")) return undefined;\n if (SLASH_NOISE_TOKENS.has(o) || SLASH_NOISE_TOKENS.has(r)) return undefined;\n return `${o}-${r}`;\n}\n\nfunction normalizeRepoContext(userText: string, workspaceRoot?: string): string | undefined {\n for (const match of userText.matchAll(GITHUB_URL_RE)) {\n const owner = match[1] ?? \"\";\n const repo = match[2] ?? \"\";\n const ctx = toRepoContext(owner, repo);\n if (ctx) return ctx;\n }\n for (const match of userText.matchAll(REPO_REF_RE)) {\n const owner = match[1] ?? \"\";\n const repo = match[2] ?? \"\";\n const ctx = toRepoContext(owner, repo);\n if (ctx) return ctx;\n }\n if (!workspaceRoot?.trim()) return undefined;\n const base = basename(workspaceRoot.trim());\n const normalized = slugifyToken(base);\n if (GENERIC_WORKSPACE_NAMES.has(normalized)) return undefined;\n return normalized;\n}\n\nfunction withRepoPrefix(base: string, repo?: string): string {\n return repo?.trim() ? `wf-${repo}-${base}` : `wf-${base}`;\n}\n\nfunction buildWorkflowProposal(\n kind: LongRunningWorkflowKind,\n userText: string,\n workspaceRoot?: string,\n): LongRunningWorkflowProposal {\n const repoContext = normalizeRepoContext(userText, workspaceRoot);\n const prNumber = PR_NUM_RE.exec(userText)?.[1];\n const deployTarget = DEPLOY_TARGET_RE.exec(userText)?.[1]?.toLowerCase();\n\n if (kind === \"pr_queue\") {\n return {\n kind,\n label: withRepoPrefix(\"pr-queue\", repoContext),\n description: `Process PR queue${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Review pending PRs and move each one to merge-ready or blocked with explicit blockers.\",\n repoContext,\n };\n }\n\n if (kind === \"pr_until_merged\") {\n const suffix = prNumber ? `pr-${prNumber}-until-merged` : \"pr-until-merged\";\n return {\n kind,\n label: withRepoPrefix(suffix, repoContext),\n description: prNumber\n ? `Drive PR #${prNumber} to merged state${repoContext ? ` (${repoContext})` : \"\"}`\n : `Drive PR workflow to merged state${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Track CI/review feedback, apply fixes, and continue until merge criteria are satisfied.\",\n repoContext,\n };\n }\n\n if (kind === \"ci_monitor\") {\n return {\n kind,\n label: withRepoPrefix(\"ci-monitor\", repoContext),\n description: `Monitor CI workflow${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Watch CI checks, triage failures, and report pass/fail with concrete next action.\",\n repoContext,\n };\n }\n\n if (kind === \"issue_sweep\") {\n return {\n kind,\n label: withRepoPrefix(\"issue-sweep\", repoContext),\n description: `Fix all open issues${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Work through issues in priority order and track unresolved blockers explicitly.\",\n repoContext,\n };\n }\n\n return {\n kind,\n label: withRepoPrefix(`deploy-${slugifyToken(deployTarget ?? \"workflow\")}`, repoContext),\n description: `Deployment workflow${deployTarget ? ` (${deployTarget})` : \"\"}${repoContext ? ` for ${repoContext}` : \"\"}`,\n next: \"Run deployment steps, monitor rollout health, and confirm outcome before closing the task.\",\n repoContext,\n };\n}\n\nexport function detectLongRunningWorkflowProposal(\n userText: string,\n workspaceRoot?: string,\n): LongRunningWorkflowProposal | null {\n const text = userText.trim();\n if (!text) return null;\n\n if (/\\bpr queue\\b/i.test(text)) {\n return buildWorkflowProposal(\"pr_queue\", text, workspaceRoot);\n }\n if (/\\bcontinue\\b[\\s\\S]{0,40}\\buntil\\b[\\s\\S]{0,30}\\bmerged\\b/i.test(text)) {\n return buildWorkflowProposal(\"pr_until_merged\", text, workspaceRoot);\n }\n if (\n /\\b(monitor|watch|track)\\b[\\s\\S]{0,20}\\bci\\b/i.test(text) ||\n /\\b(wait|waiting)\\b[\\s\\S]{0,25}\\b(ci|checks?)\\b/i.test(text)\n ) {\n return buildWorkflowProposal(\"ci_monitor\", text, workspaceRoot);\n }\n if (ISSUE_SWEEP_RE.test(text)) {\n return buildWorkflowProposal(\"issue_sweep\", text, workspaceRoot);\n }\n if (DEPLOY_ACTION_RE.test(text) || RELEASE_TO_ENV_RE.test(text)) {\n return buildWorkflowProposal(\"deployment\", text, workspaceRoot);\n }\n return null;\n}\n\nexport function buildLongRunningTaskDraft(\n proposal: LongRunningWorkflowProposal,\n nowIso = new Date().toISOString(),\n): ActiveTaskEntry {\n return {\n label: proposal.label,\n description: proposal.description,\n status: \"In progress\",\n next: proposal.next,\n started: nowIso,\n updated: nowIso,\n };\n}\n\nfunction isMainOrPrivateSessionKey(sessionKey?: string | null): boolean {\n if (!sessionKey) return false;\n const trimmed = sessionKey.trim().toLowerCase();\n if (!trimmed) return false;\n if (trimmed.startsWith(\"agent:main:\") || trimmed.startsWith(\"agent:private:\")) return true;\n return trimmed === \"main\" || trimmed === \"private\";\n}\n\nexport function shouldAutoRegisterLongRunningTask(\n mode: LongRunningRegistrationMode,\n sessionKey?: string | null,\n): boolean {\n return (\n mode === \"auto_main_private\" && isMainOrPrivateSessionKey(sessionKey) && !isSubagentSession(sessionKey ?? undefined)\n );\n}\n\nexport function buildLongRunningTaskRegistrationBlock(\n proposal: LongRunningWorkflowProposal,\n draft: ActiveTaskEntry,\n opts: {\n mode: LongRunningRegistrationMode;\n autoCreated: boolean;\n alreadyActive: boolean;\n sessionKey?: string | null;\n },\n): string {\n const lines = [\n \"<active-task-registration>\",\n `**Long-running workflow detected:** ${proposal.kind.replace(/_/g, \" \")}`,\n `- Stable task label: \\`${draft.label}\\`${proposal.repoContext ? ` (repo context: \\`${proposal.repoContext}\\`)` : \"\"}`,\n ];\n\n if (opts.autoCreated) {\n lines.push(\"- Auto-registered this task in the active-task ledger (auto_main_private policy).\");\n } else if (opts.alreadyActive) {\n lines.push(\"- Matching active task already exists; continuing with existing tracking row.\");\n } else if (opts.mode === \"confirm\") {\n lines.push(\"- Guard: register/confirm this task before proceeding with external workflow mutations.\");\n } else if (opts.mode === \"auto_main_private\" && !shouldAutoRegisterLongRunningTask(opts.mode, opts.sessionKey)) {\n lines.push(\"- Auto-registration policy only applies to main/private sessions; register this task manually.\");\n } else {\n lines.push(\"- Suggested registration payload (use `active-tasks add` or project facts equivalent):\");\n }\n\n lines.push(\"```json\");\n lines.push(\n JSON.stringify(\n {\n label: draft.label,\n description: draft.description,\n status: draft.status,\n next: draft.next,\n },\n null,\n 2,\n ),\n );\n lines.push(\"```\");\n lines.push(\n \"- For outcome-oriented multi-session execution, follow with `active_task_propose_goal` + `goal_register`.\",\n );\n lines.push(\"</active-task-registration>\");\n return lines.join(\"\\n\");\n}\n\nexport function buildHeartbeatTaskHygieneBlock(\n tasks: ActiveTaskEntry[],\n opts: {\n maxChars: number;\n suggestGoalAfterTaskAgeDays: number;\n /** Format label lists (default: all labels, comma-separated). */\n formatLabelList?: (labels: string[]) => string;\n },\n): string {\n const formatLabels = opts.formatLabelList ?? ((labels: string[]) => labels.map((l) => `[${l}]`).join(\", \"));\n const stale = tasks.filter((t) => t.stale);\n const lines: string[] = [\n \"<task-hygiene>\",\n \"**Heartbeat — active task review**\",\n \"Reconcile ACTIVE-TASKS.md: complete finished work, update **Next**, or verify subagents before replying HEARTBEAT_OK.\",\n ];\n\n if (stale.length > 0) {\n lines.push(`- **Stale tasks (${stale.length}):** ${formatLabels(stale.map((t) => t.label))} — update or complete.`);\n } else {\n lines.push(\"- No tasks flagged stale by the current threshold.\");\n }\n\n if (opts.suggestGoalAfterTaskAgeDays > 0) {\n const cutoff = Date.now() - opts.suggestGoalAfterTaskAgeDays * 86_400_000;\n const longRunning = tasks.filter((t) => {\n const u = new Date(t.updated).getTime();\n return !Number.isNaN(u) && u < cutoff;\n });\n if (longRunning.length > 0) {\n lines.push(\n `- **Long-running (>${opts.suggestGoalAfterTaskAgeDays}d since last update):** ${formatLabels(longRunning.map((t) => t.label))}`,\n );\n lines.push(\n \"- If this work should survive many sessions, use **`active_task_propose_goal`** then **`goal_register`** (with acceptance criteria).\",\n );\n }\n }\n\n lines.push(\"</task-hygiene>\");\n let out = lines.join(\"\\n\");\n if (out.length > opts.maxChars) {\n out = `${out.slice(0, opts.maxChars - 20)}\\n…(truncated)\\n</task-hygiene>`;\n }\n return out;\n}\n\n/** Nudge on heartbeat when goals are blocked/stalled but ACTIVE-TASKS may look fine (issue #1096). */\nexport function buildGoalEscalationHeartbeatBlock(\n goals: Array<{ label: string; status: string }>,\n opts: { maxChars: number },\n): string {\n const bad = goals.filter((g) => g.status === \"blocked\" || g.status === \"stalled\");\n if (bad.length === 0) return \"\";\n const lines: string[] = [\n \"<goal-escalation>\",\n \"**Heartbeat — blocked or stalled goals**\",\n \"Do not reply HEARTBEAT_OK as if everything is fine until these are triaged or unblocked.\",\n ...bad.map((g) => `- **[${g.label}]** (${g.status})`),\n \"</goal-escalation>\",\n ];\n const closingTag = \"</goal-escalation>\";\n const body = lines.join(\"\\n\");\n if (body.length <= opts.maxChars) {\n return body;\n }\n const openingTag = \"<goal-escalation>\";\n const truncatedSuffix = `\\n…(truncated)\\n${closingTag}`;\n const minChars = openingTag.length + truncatedSuffix.length;\n if (opts.maxChars < minChars) {\n return `${openingTag}${truncatedSuffix}`;\n }\n const availableBodyChars = opts.maxChars - truncatedSuffix.length;\n return `${body.slice(0, availableBodyChars)}${truncatedSuffix}`;\n}\n\nexport function buildProposeGoalDraftFromTask(task: ActiveTaskEntry): {\n suggestedLabel: string;\n suggestedDescription: string;\n suggestedCriteria: string[];\n notes: string;\n} {\n const criteria: string[] = [];\n if (task.next?.trim()) criteria.push(`Complete next step: ${task.next.trim()}`);\n criteria.push(`Task was in status \"${task.status}\" (from ACTIVE-TASKS.md).`);\n criteria.push(\"Verify outcome matches user expectations before calling goal_complete.\");\n return {\n suggestedLabel: task.label,\n suggestedDescription: task.description || `Follow through on active task [${task.label}]`,\n suggestedCriteria: criteria,\n notes:\n \"This is a draft from the task row — refine acceptance_criteria with the user before goal_register when policy requires confirmation.\",\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"task-hygiene.js","names":[],"sources":["../../services/task-hygiene.ts"],"sourcesContent":["/**\n * Task hygiene — stronger nudges for ACTIVE-TASKS.md rows (separate from Goals).\n * @see docs/TASK-HYGIENE.md\n */\n\nimport { basename } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport type { ActiveTaskEntry } from \"./active-task.js\";\nimport { isSubagentSession } from \"./active-task.js\";\nimport type { ActiveTaskLongRunningRegistrationMode } from \"../config/types/index.js\";\nimport { getEnv } from \"../utils/env-manager.js\";\nimport { execFile } from \"../utils/process-runner.js\";\n\nexport type LongRunningWorkflowKind = \"pr_queue\" | \"pr_until_merged\" | \"ci_monitor\" | \"issue_sweep\" | \"deployment\";\nexport type LongRunningRegistrationMode = ActiveTaskLongRunningRegistrationMode;\n\nexport type LongRunningWorkflowProposal = {\n kind: LongRunningWorkflowKind;\n label: string;\n description: string;\n next: string;\n repoContext?: string;\n};\n\n/**\n * Classification outcomes for a PR-backed task's live blocker state.\n * These are returned by `fetchLivePrBlockerStatus` and consumed by hygiene logic.\n */\nexport type PrBlockerStatus =\n | \"unresolved_review_threads\"\n | \"red_ci\"\n | \"pending_ci\"\n | \"merge_conflict\"\n | \"human_approval\"\n | \"no_live_blocker\";\n\nconst execFileAsync = promisify(execFile);\n\ninterface GhPrViewJson {\n number: number;\n mergeStateStatus?: string;\n reviewDecision?: string;\n reviewThreads?: {\n nodes: Array<{\n id: string;\n isResolved: boolean;\n isOutdated: boolean;\n }>;\n };\n mergeable?: string;\n statusCheckRollup?: Array<{\n state?: string;\n conclusion?: string;\n status?: string;\n }>;\n commits?: {\n nodes: Array<{ commit: { checkSuites?: { nodes: Array<{ conclusion?: string; status?: string }> } } }>;\n };\n}\n\nconst BLOCKING_CONCLUSIONS = new Set([\"FAILURE\", \"TIMED_OUT\", \"STARTUP_FAILURE\"]);\nconst PENDING_CHECK_STATES = new Set([\"IN_PROGRESS\", \"QUEUED\", \"PENDING\", \"WAITING\", \"REQUESTED\"]);\n\n/**\n * Fetch live PR state from GitHub (via `gh`) and classify the blocking status.\n *\n * This function exists to ensure hygiene does not mis-classify a PR as \"waiting/blocked\"\n * based on shallow signals (mergeStateStatus, reviewDecision) alone — it always checks\n * for actionable unresolved review threads before concluding there is no live blocker.\n *\n * Returns \"no_live_blocker\" only when CI is green/success, there are no unresolved threads,\n * no merge conflict, and no pending human approval.\n */\nexport async function fetchLivePrBlockerStatus(\n owner: string,\n repo: string,\n prNumber: number,\n opts: { signal?: AbortSignal } = {},\n): Promise<PrBlockerStatus> {\n const token = (getEnv(\"GITHUB_TOKEN\") ?? getEnv(\"GH_TOKEN\") ?? \"\").trim();\n if (!token) {\n // No token — skip live check, be conservative\n return \"no_live_blocker\";\n }\n\n const ghArgs = [\n \"pr\",\n \"view\",\n String(prNumber),\n \"--repo\",\n `${owner}/${repo}`,\n \"--json\",\n \"mergeStateStatus,reviewDecision,reviewThreads,mergeable,statusCheckRollup,commits\",\n \"--jq=.\",\n ];\n\n const ac = new AbortController();\n const timeout = setTimeout(() => ac.abort(), 20_000);\n const outerSignal = opts.signal ?? ac.signal;\n\n try {\n const { stdout } = await execFileAsync(\"gh\", ghArgs, {\n encoding: \"utf-8\",\n signal: outerSignal,\n timeout: 25_000,\n });\n const pr: GhPrViewJson = JSON.parse(stdout);\n\n // 1. Check for unresolved review threads (most important — actionable human feedback)\n const threads = pr.reviewThreads?.nodes ?? [];\n const unresolvedThreads = threads.filter((t) => !t.isResolved && !t.isOutdated);\n if (unresolvedThreads.length > 0) {\n return \"unresolved_review_threads\";\n }\n\n // 2. Check CI (from statusCheckRollup + checkSuites on latest commit)\n const statusCheckRollup = pr.statusCheckRollup ?? [];\n const commitNodes = pr.commits?.nodes ?? [];\n const latestCommit = commitNodes[commitNodes.length - 1]?.commit;\n const checkSuites = latestCommit?.checkSuites?.nodes ?? [];\n\n const hasFailure =\n statusCheckRollup.some(\n (c) =>\n c.state === \"FAILURE\" || c.state === \"TIMED_OUT\" || (c.conclusion && BLOCKING_CONCLUSIONS.has(c.conclusion)),\n ) || checkSuites.some((cs) => cs.conclusion && BLOCKING_CONCLUSIONS.has(cs.conclusion));\n const hasPending =\n statusCheckRollup.some(\n (c) => c.state === \"PENDING\" || c.state === \"IN_PROGRESS\" || (c.status && PENDING_CHECK_STATES.has(c.status)),\n ) || checkSuites.some((cs) => cs.status && PENDING_CHECK_STATES.has(cs.status));\n\n if (hasFailure) return \"red_ci\";\n if (hasPending) return \"pending_ci\";\n\n // 3. Check merge conflict\n if (pr.mergeStateStatus === \"BLOCKED\" || pr.mergeStateStatus === \"UNSTABLE\") {\n // Double-check via mergeable field if available\n if (pr.mergeable === \"CONFLICTING\") return \"merge_conflict\";\n }\n if (pr.mergeable === \"CONFLICTING\") return \"merge_conflict\";\n\n // 4. Human approval pending\n if (pr.reviewDecision === \"CHANGES_REQUESTED\") return \"human_approval\";\n if (pr.reviewDecision === \"REVIEW_REQUIRED\") return \"human_approval\";\n\n return \"no_live_blocker\";\n } catch {\n // Network or parse error — be conservative, don't modify task state\n return \"no_live_blocker\";\n } finally {\n clearTimeout(timeout);\n }\n}\n\nconst REPO_REF_RE = /\\b([a-z0-9][a-z0-9_.-]{1,98})\\/([a-z0-9][a-z0-9_.-]{1,98})\\b/gi;\nconst GITHUB_URL_RE =\n /(?:https?:\\/\\/)?(?:www\\.)?github\\.com\\/([a-z0-9][a-z0-9_.-]{1,98})\\/([a-z0-9][a-z0-9_.-]{1,98})(?:[/?#\\s,;.)]|$)/gi;\nconst PR_NUM_RE = /(?:\\bpr\\b|\\bpull request\\b)?\\s*#(\\d+)\\b/i;\nconst DEPLOY_TARGET_RE = /\\b(prod|production|staging|stage|qa|dev|preview)\\b/i;\nconst DEPLOY_ACTION_RE =\n /\\b(?:deploy|rollout)\\b|\\b(run|start|trigger|execute|perform|monitor|watch|track)\\b[\\s\\S]{0,30}\\b(deploy|deployment|rollout)\\b/i;\nconst RELEASE_TO_ENV_RE =\n /\\brelease\\b[\\s\\S]{0,25}\\b(to|into)\\b[\\s\\S]{0,10}\\b(prod|production|staging|stage|qa|dev|preview)\\b|\\b(run|start|trigger|execute|perform|monitor|watch|track)\\b[\\s\\S]{0,30}\\brelease\\b[\\s\\S]{0,25}\\b(to|into)\\b[\\s\\S]{0,10}\\b(prod|production|staging|stage|qa|dev|preview)\\b/i;\nconst ISSUE_SWEEP_RE =\n /\\bfix\\b[\\s\\S]{0,10}\\ball\\b[\\s\\S]{0,10}\\b(?:open\\s+|the\\s+)?issues?\\b(?!\\s+(?:with|on|for|from|at|about)|\\s+in\\s+(?!(?:this\\s+|the\\s+)?(?:repo|repository|github|backlog)))/i;\nconst GENERIC_WORKSPACE_NAMES = new Set([\"workspace\", \"workspaces\", \"tmp\", \"home\", \"openclaw\", \"task\"]);\nconst SLASH_NOISE_TOKENS = new Set([\"and\", \"or\", \"on\", \"off\", \"to\", \"for\", \"in\", \"by\", \"up\", \"down\", \"yes\", \"no\"]);\n\nfunction slugifyToken(value: string): string {\n const slug = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"task\";\n}\n\nfunction normalizeRepoToken(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/\\.git$/i, \"\")\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/-{2,}/g, \"-\")\n .replace(/^[-._]+|[-._]+$/g, \"\");\n}\n\nfunction toRepoContext(owner: string, repo: string): string | undefined {\n const o = normalizeRepoToken(owner);\n const r = normalizeRepoToken(repo);\n if (!o || !r) return undefined;\n if (o.includes(\".\")) return undefined;\n if (SLASH_NOISE_TOKENS.has(o) || SLASH_NOISE_TOKENS.has(r)) return undefined;\n return `${o}-${r}`;\n}\n\nfunction normalizeRepoContext(userText: string, workspaceRoot?: string): string | undefined {\n for (const match of userText.matchAll(GITHUB_URL_RE)) {\n const owner = match[1] ?? \"\";\n const repo = match[2] ?? \"\";\n const ctx = toRepoContext(owner, repo);\n if (ctx) return ctx;\n }\n for (const match of userText.matchAll(REPO_REF_RE)) {\n const owner = match[1] ?? \"\";\n const repo = match[2] ?? \"\";\n const ctx = toRepoContext(owner, repo);\n if (ctx) return ctx;\n }\n if (!workspaceRoot?.trim()) return undefined;\n const base = basename(workspaceRoot.trim());\n const normalized = slugifyToken(base);\n if (GENERIC_WORKSPACE_NAMES.has(normalized)) return undefined;\n return normalized;\n}\n\nfunction withRepoPrefix(base: string, repo?: string): string {\n return repo?.trim() ? `wf-${repo}-${base}` : `wf-${base}`;\n}\n\nfunction buildWorkflowProposal(\n kind: LongRunningWorkflowKind,\n userText: string,\n workspaceRoot?: string,\n): LongRunningWorkflowProposal {\n const repoContext = normalizeRepoContext(userText, workspaceRoot);\n const prNumber = PR_NUM_RE.exec(userText)?.[1];\n const deployTarget = DEPLOY_TARGET_RE.exec(userText)?.[1]?.toLowerCase();\n\n if (kind === \"pr_queue\") {\n return {\n kind,\n label: withRepoPrefix(\"pr-queue\", repoContext),\n description: `Process PR queue${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Review pending PRs and move each one to merge-ready or blocked with explicit blockers.\",\n repoContext,\n };\n }\n\n if (kind === \"pr_until_merged\") {\n const suffix = prNumber ? `pr-${prNumber}-until-merged` : \"pr-until-merged\";\n return {\n kind,\n label: withRepoPrefix(suffix, repoContext),\n description: prNumber\n ? `Drive PR #${prNumber} to merged state${repoContext ? ` (${repoContext})` : \"\"}`\n : `Drive PR workflow to merged state${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Track CI/review feedback, apply fixes, and continue until merge criteria are satisfied.\",\n repoContext,\n };\n }\n\n if (kind === \"ci_monitor\") {\n return {\n kind,\n label: withRepoPrefix(\"ci-monitor\", repoContext),\n description: `Monitor CI workflow${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Watch CI checks, triage failures, and report pass/fail with concrete next action.\",\n repoContext,\n };\n }\n\n if (kind === \"issue_sweep\") {\n return {\n kind,\n label: withRepoPrefix(\"issue-sweep\", repoContext),\n description: `Fix all open issues${repoContext ? ` (${repoContext})` : \"\"}`,\n next: \"Work through issues in priority order and track unresolved blockers explicitly.\",\n repoContext,\n };\n }\n\n return {\n kind,\n label: withRepoPrefix(`deploy-${slugifyToken(deployTarget ?? \"workflow\")}`, repoContext),\n description: `Deployment workflow${deployTarget ? ` (${deployTarget})` : \"\"}${repoContext ? ` for ${repoContext}` : \"\"}`,\n next: \"Run deployment steps, monitor rollout health, and confirm outcome before closing the task.\",\n repoContext,\n };\n}\n\nexport function detectLongRunningWorkflowProposal(\n userText: string,\n workspaceRoot?: string,\n): LongRunningWorkflowProposal | null {\n const text = userText.trim();\n if (!text) return null;\n\n if (/\\bpr queue\\b/i.test(text)) {\n return buildWorkflowProposal(\"pr_queue\", text, workspaceRoot);\n }\n if (/\\bcontinue\\b[\\s\\S]{0,40}\\buntil\\b[\\s\\S]{0,30}\\bmerged\\b/i.test(text)) {\n return buildWorkflowProposal(\"pr_until_merged\", text, workspaceRoot);\n }\n if (\n /\\b(monitor|watch|track)\\b[\\s\\S]{0,20}\\bci\\b/i.test(text) ||\n /\\b(wait|waiting)\\b[\\s\\S]{0,25}\\b(ci|checks?)\\b/i.test(text)\n ) {\n return buildWorkflowProposal(\"ci_monitor\", text, workspaceRoot);\n }\n if (ISSUE_SWEEP_RE.test(text)) {\n return buildWorkflowProposal(\"issue_sweep\", text, workspaceRoot);\n }\n if (DEPLOY_ACTION_RE.test(text) || RELEASE_TO_ENV_RE.test(text)) {\n return buildWorkflowProposal(\"deployment\", text, workspaceRoot);\n }\n return null;\n}\n\nexport function buildLongRunningTaskDraft(\n proposal: LongRunningWorkflowProposal,\n nowIso = new Date().toISOString(),\n): ActiveTaskEntry {\n return {\n label: proposal.label,\n description: proposal.description,\n status: \"In progress\",\n next: proposal.next,\n started: nowIso,\n updated: nowIso,\n };\n}\n\nfunction isMainOrPrivateSessionKey(sessionKey?: string | null): boolean {\n if (!sessionKey) return false;\n const trimmed = sessionKey.trim().toLowerCase();\n if (!trimmed) return false;\n if (trimmed.startsWith(\"agent:main:\") || trimmed.startsWith(\"agent:private:\")) return true;\n return trimmed === \"main\" || trimmed === \"private\";\n}\n\nexport function shouldAutoRegisterLongRunningTask(\n mode: LongRunningRegistrationMode,\n sessionKey?: string | null,\n): boolean {\n return (\n mode === \"auto_main_private\" && isMainOrPrivateSessionKey(sessionKey) && !isSubagentSession(sessionKey ?? undefined)\n );\n}\n\nexport function buildLongRunningTaskRegistrationBlock(\n proposal: LongRunningWorkflowProposal,\n draft: ActiveTaskEntry,\n opts: {\n mode: LongRunningRegistrationMode;\n autoCreated: boolean;\n alreadyActive: boolean;\n sessionKey?: string | null;\n },\n): string {\n const lines = [\n \"<active-task-registration>\",\n `**Long-running workflow detected:** ${proposal.kind.replace(/_/g, \" \")}`,\n `- Stable task label: \\`${draft.label}\\`${proposal.repoContext ? ` (repo context: \\`${proposal.repoContext}\\`)` : \"\"}`,\n ];\n\n if (opts.autoCreated) {\n lines.push(\"- Auto-registered this task in the active-task ledger (auto_main_private policy).\");\n } else if (opts.alreadyActive) {\n lines.push(\"- Matching active task already exists; continuing with existing tracking row.\");\n } else if (opts.mode === \"confirm\") {\n lines.push(\"- Guard: register/confirm this task before proceeding with external workflow mutations.\");\n } else if (opts.mode === \"auto_main_private\" && !shouldAutoRegisterLongRunningTask(opts.mode, opts.sessionKey)) {\n lines.push(\"- Auto-registration policy only applies to main/private sessions; register this task manually.\");\n } else {\n lines.push(\"- Suggested registration payload (use `active-tasks add` or project facts equivalent):\");\n }\n\n lines.push(\"```json\");\n lines.push(\n JSON.stringify(\n {\n label: draft.label,\n description: draft.description,\n status: draft.status,\n next: draft.next,\n },\n null,\n 2,\n ),\n );\n lines.push(\"```\");\n lines.push(\n \"- For outcome-oriented multi-session execution, follow with `active_task_propose_goal` + `goal_register`.\",\n );\n lines.push(\"</active-task-registration>\");\n return lines.join(\"\\n\");\n}\n\nexport function buildHeartbeatTaskHygieneBlock(\n tasks: ActiveTaskEntry[],\n opts: {\n maxChars: number;\n suggestGoalAfterTaskAgeDays: number;\n /** Format label lists (default: all labels, comma-separated). */\n formatLabelList?: (labels: string[]) => string;\n },\n): string {\n const formatLabels = opts.formatLabelList ?? ((labels: string[]) => labels.map((l) => `[${l}]`).join(\", \"));\n const stale = tasks.filter((t) => t.stale);\n const lines: string[] = [\n \"<task-hygiene>\",\n \"**Heartbeat — active task review**\",\n \"Reconcile ACTIVE-TASKS.md: complete finished work, update **Next**, or verify subagents before replying HEARTBEAT_OK.\",\n ];\n\n if (stale.length > 0) {\n lines.push(`- **Stale tasks (${stale.length}):** ${formatLabels(stale.map((t) => t.label))} — update or complete.`);\n } else {\n lines.push(\"- No tasks flagged stale by the current threshold.\");\n }\n\n if (opts.suggestGoalAfterTaskAgeDays > 0) {\n const cutoff = Date.now() - opts.suggestGoalAfterTaskAgeDays * 86_400_000;\n const longRunning = tasks.filter((t) => {\n const u = new Date(t.updated).getTime();\n return !Number.isNaN(u) && u < cutoff;\n });\n if (longRunning.length > 0) {\n lines.push(\n `- **Long-running (>${opts.suggestGoalAfterTaskAgeDays}d since last update):** ${formatLabels(longRunning.map((t) => t.label))}`,\n );\n lines.push(\n \"- If this work should survive many sessions, use **`active_task_propose_goal`** then **`goal_register`** (with acceptance criteria).\",\n );\n }\n }\n\n lines.push(\"</task-hygiene>\");\n let out = lines.join(\"\\n\");\n if (out.length > opts.maxChars) {\n out = `${out.slice(0, opts.maxChars - 20)}\\n…(truncated)\\n</task-hygiene>`;\n }\n return out;\n}\n\n/** Nudge on heartbeat when goals are blocked/stalled but ACTIVE-TASKS may look fine (issue #1096). */\nexport function buildGoalEscalationHeartbeatBlock(\n goals: Array<{ label: string; status: string }>,\n opts: { maxChars: number },\n): string {\n const bad = goals.filter((g) => g.status === \"blocked\" || g.status === \"stalled\");\n if (bad.length === 0) return \"\";\n const lines: string[] = [\n \"<goal-escalation>\",\n \"**Heartbeat — blocked or stalled goals**\",\n \"Do not reply HEARTBEAT_OK as if everything is fine until these are triaged or unblocked.\",\n ...bad.map((g) => `- **[${g.label}]** (${g.status})`),\n \"</goal-escalation>\",\n ];\n const closingTag = \"</goal-escalation>\";\n const body = lines.join(\"\\n\");\n if (body.length <= opts.maxChars) {\n return body;\n }\n const openingTag = \"<goal-escalation>\";\n const truncatedSuffix = `\\n…(truncated)\\n${closingTag}`;\n const minChars = openingTag.length + truncatedSuffix.length;\n if (opts.maxChars < minChars) {\n return `${openingTag}${truncatedSuffix}`;\n }\n const availableBodyChars = opts.maxChars - truncatedSuffix.length;\n return `${body.slice(0, availableBodyChars)}${truncatedSuffix}`;\n}\n\nexport function buildProposeGoalDraftFromTask(task: ActiveTaskEntry): {\n suggestedLabel: string;\n suggestedDescription: string;\n suggestedCriteria: string[];\n notes: string;\n} {\n const criteria: string[] = [];\n if (task.next?.trim()) criteria.push(`Complete next step: ${task.next.trim()}`);\n criteria.push(`Task was in status \"${task.status}\" (from ACTIVE-TASKS.md).`);\n criteria.push(\"Verify outcome matches user expectations before calling goal_complete.\");\n return {\n suggestedLabel: task.label,\n suggestedDescription: task.description || `Follow through on active task [${task.label}]`,\n suggestedCriteria: criteria,\n notes:\n \"This is a draft from the task row — refine acceptance_criteria with the user before goal_register when policy requires confirmation.\",\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"canonical.js","names":[],"sources":["../../../services/task-ledger/canonical.ts"],"sourcesContent":["import type { MemoryEntry } from \"../../types/memory.js\";\nimport type { ActiveTaskStatus } from \"../active-task.js\";\n\nconst TERMINAL = new Set([\"done\", \"completed\", \"cancelled\", \"closed\", \"abandoned\", \"superseded\"]);\n\nexport function canonicalLabel(entity: string): string {\n if (!entity?.trim()) return \"\";\n return entity\n .trim()\n .toLowerCase()\n .replace(/\\s+(?:pr\\s+queue|pull\\s+request\\s+queue|pr-stewardship|pull\\s+request\\s+stewardship)\\s*$/i, \"\")\n .replace(/[\\s/_]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nexport function readCanonicalLabelFromFact(fact: MemoryEntry): string | null {\n if (!fact.provenanceJson) return null;\n try {\n const parsed = JSON.parse(fact.provenanceJson) as {\n activeTask?: { canonicalLabel?: unknown };\n canonical_label?: unknown;\n };\n const raw = parsed.activeTask?.canonicalLabel ?? parsed.canonical_label;\n return typeof raw === \"string\" && raw.trim().length > 0 ? canonicalLabel(raw) : null;\n } catch {\n return null;\n }\n}\n\nexport function factCanonicalLabel(fact: MemoryEntry): string {\n const fromProvenance = readCanonicalLabelFromFact(fact);\n if (fromProvenance) return fromProvenance;\n return canonicalLabel(fact.entity ?? \"\");\n}\n\nexport function activeTaskProvenance(canonical: string, existing?: string | null): string {\n const base = { activeTask: { canonicalLabel: canonical }, canonical_label: canonical };\n if (existing) {\n try {\n const parsed = JSON.parse(existing) as Record<string, unknown>;\n return JSON.stringify({ ...parsed, ...base });\n } catch {\n // If existing provenance is not valid JSON, use base only\n }\n }\n return JSON.stringify(base);\n}\n\nfunction readFactRowid(fact: MemoryEntry): number | null {\n const rowid = (fact as MemoryEntry & { rowid?: unknown; _rowid?: unknown }).rowid;\n if (typeof rowid === \"number\" && Number.isFinite(rowid)) return rowid;\n const fallback = (fact as MemoryEntry & { _rowid?: unknown })._rowid;\n return typeof fallback === \"number\" && Number.isFinite(fallback) ? fallback : null;\n}\n\n/** Tie-break comparator: returns negative if a is newer than b, positive if b is newer than a.\n * Comparison order: createdAt → sourceDate (when both finite) → rowid (when available) → insertion order.\n * Rowid/insertion-order fallback provides stable last-write wins semantics for timestamp ties. */\nfunction factNewerThan(a: MemoryEntry, b: MemoryEntry, aOrder: number, bOrder: number): number {\n // Return < 0 when a is newer than b (a should win the slot).\n // createdAt: larger = newer\n if (a.createdAt !== b.createdAt) return b.createdAt - a.createdAt;\n // sourceDate: larger = more recent external event\n const aSrc = typeof a.sourceDate === \"number\" && Number.isFinite(a.sourceDate) ? a.sourceDate : null;\n const bSrc = typeof b.sourceDate === \"number\" && Number.isFinite(b.sourceDate) ? b.sourceDate : null;\n // Check if this is a status comparison where one is terminal and the other is not.\n // Terminal status should always win, regardless of sourceDate.\n const aKey = (a.key ?? \"\").trim() || \"_body\";\n const bKey = (b.key ?? \"\").trim() || \"_body\";\n if (aKey === bKey && aKey === \"status\") {\n const aVal = a.value ?? \"\";\n const bVal = b.value ?? \"\";\n const aIsTerminal = isTerminalFactStatus(aVal);\n const bIsTerminal = isTerminalFactStatus(bVal);\n if (aIsTerminal !== bIsTerminal) {\n // Prefer terminal status over non-terminal.\n return aIsTerminal ? -1 : 1;\n }\n }\n // Now compare sourceDate values (both present, or exactly one present).\n if (aSrc !== null && bSrc !== null && aSrc !== bSrc) return bSrc - aSrc;\n if ((aSrc === null) !== (bSrc === null)) {\n // Prefer fresh local writes (no sourceDate) over stale external sourceDate events.\n if (aSrc === null && bSrc !== null) return -1;\n if (aSrc !== null && bSrc === null) return 1;\n }\n const aRowid = readFactRowid(a);\n const bRowid = readFactRowid(b);\n if (aRowid !== null && bRowid !== null && aRowid !== bRowid) return bRowid - aRowid;\n // Last-resort deterministic tie-break when rowid is unavailable.\n return bOrder - aOrder;\n}\n\n/** Latest value per entity+key from non-superseded project facts.\n * Entity labels are normalized via factCanonicalLabel() (provenance-aware + trim + toLowerCase + suffix/separator cleanup)\n * so that case-variant entries (e.g. \"Humanizer\" / \"humanizer\") are merged into one group.\n * When multiple facts for the same entity+key have equal timestamps, last-write-wins ensures\n * newer writes always win — terminal status updates will not be silently dropped behind earlier\n * non-terminal facts with equal createdAt. */\nexport function groupProjectFactsByEntity(facts: MemoryEntry[]): Map<string, Map<string, MemoryEntry>> {\n const byEntity = new Map<string, Map<string, MemoryEntry>>();\n const winnerOrderByEntity = new Map<string, Map<string, number>>();\n const nowSec = Math.floor(Date.now() / 1000);\n for (const [idx, f] of facts.entries()) {\n if (typeof f.supersededAt === \"number\" && Number.isFinite(f.supersededAt)) continue;\n if (typeof f.expiresAt === \"number\" && Number.isFinite(f.expiresAt) && f.expiresAt <= nowSec) continue;\n if (!f.entity?.trim()) continue;\n const canonical = factCanonicalLabel(f);\n if (!canonical) continue;\n const k = (f.key ?? \"\").trim() || \"_body\";\n let km = byEntity.get(canonical);\n let winnerOrders = winnerOrderByEntity.get(canonical);\n if (!km) {\n km = new Map();\n byEntity.set(canonical, km);\n winnerOrders = new Map();\n winnerOrderByEntity.set(canonical, winnerOrders);\n }\n if (!winnerOrders) {\n winnerOrders = new Map();\n winnerOrderByEntity.set(canonical, winnerOrders);\n }\n const prev = km.get(k);\n const prevOrder = winnerOrders.get(k) ?? Number.NEGATIVE_INFINITY;\n if (!prev || factNewerThan(f, prev, idx, prevOrder) < 0) {\n km.set(k, f);\n winnerOrders.set(k, idx);\n }\n }\n return byEntity;\n}\n\nexport function factStatusToDisplay(raw: string): ActiveTaskStatus {\n const s = raw.trim().toLowerCase();\n if (s === \"open\") return \"In progress\";\n if (s === \"in_progress\" || s === \"in progress\") return \"In progress\";\n if (s === \"blocked\" || s.startsWith(\"blocked\")) return \"Stalled\";\n if (s === \"waiting\") return \"Waiting\";\n if (s === \"failed\" || s === \"error\") return \"Failed\";\n if (s === \"stalled\") return \"Stalled\";\n if (TERMINAL.has(s)) return \"Done\";\n return \"In progress\";\n}\n\nexport function displayStatusToFact(status: ActiveTaskStatus): string {\n switch (status) {\n case \"In progress\":\n return \"in_progress\";\n case \"Done\":\n return \"done\";\n case \"Failed\":\n return \"failed\";\n case \"Waiting\":\n return \"waiting\";\n case \"Stalled\":\n return \"blocked\";\n default:\n return \"in_progress\";\n }\n}\n\nexport function isTerminalFactStatus(raw: string): boolean {\n return TERMINAL.has(raw.trim().toLowerCase());\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"canonical.js","names":[],"sources":["../../../services/task-ledger/canonical.ts"],"sourcesContent":["import type { MemoryEntry } from \"../../types/memory.js\";\nimport type { ActiveTaskStatus } from \"../active-task.js\";\n\nconst TERMINAL = new Set([\"done\", \"completed\", \"cancelled\", \"closed\", \"abandoned\", \"superseded\"]);\n\nexport function canonicalLabel(entity: string): string {\n if (!entity?.trim()) return \"\";\n return entity\n .trim()\n .toLowerCase()\n .replace(/\\s+(?:pr\\s+queue|pull\\s+request\\s+queue|pr-stewardship|pull\\s+request\\s+stewardship)\\s*$/i, \"\")\n .replace(/[\\s/_]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nexport function readCanonicalLabelFromFact(fact: MemoryEntry): string | null {\n if (!fact.provenanceJson) return null;\n try {\n const parsed = JSON.parse(fact.provenanceJson) as {\n activeTask?: { canonicalLabel?: unknown };\n canonical_label?: unknown;\n };\n const raw = parsed.activeTask?.canonicalLabel ?? parsed.canonical_label;\n return typeof raw === \"string\" && raw.trim().length > 0 ? canonicalLabel(raw) : null;\n } catch {\n return null;\n }\n}\n\nexport function factCanonicalLabel(fact: MemoryEntry): string {\n const fromProvenance = readCanonicalLabelFromFact(fact);\n if (fromProvenance) return fromProvenance;\n return canonicalLabel(fact.entity ?? \"\");\n}\n\nexport function activeTaskProvenance(canonical: string, existing?: string | null): string {\n const base = { activeTask: { canonicalLabel: canonical }, canonical_label: canonical };\n if (existing) {\n try {\n const parsed = JSON.parse(existing) as Record<string, unknown>;\n return JSON.stringify({ ...parsed, ...base });\n } catch {\n // If existing provenance is not valid JSON, use base only\n }\n }\n return JSON.stringify(base);\n}\n\nfunction readFactRowid(fact: MemoryEntry): number | null {\n const rowid = (fact as MemoryEntry & { rowid?: unknown; _rowid?: unknown }).rowid;\n if (typeof rowid === \"number\" && Number.isFinite(rowid)) return rowid;\n const fallback = (fact as MemoryEntry & { _rowid?: unknown })._rowid;\n return typeof fallback === \"number\" && Number.isFinite(fallback) ? fallback : null;\n}\n\n/** Tie-break comparator: returns negative if a is newer than b, positive if b is newer than a.\n * Comparison order: createdAt → sourceDate (when both finite) → rowid (when available) → insertion order.\n * Rowid/insertion-order fallback provides stable last-write wins semantics for timestamp ties. */\nfunction factNewerThan(a: MemoryEntry, b: MemoryEntry, aOrder: number, bOrder: number): number {\n // Return < 0 when a is newer than b (a should win the slot).\n // createdAt: larger = newer\n if (a.createdAt !== b.createdAt) return b.createdAt - a.createdAt;\n // sourceDate: larger = more recent external event\n const aSrc = typeof a.sourceDate === \"number\" && Number.isFinite(a.sourceDate) ? a.sourceDate : null;\n const bSrc = typeof b.sourceDate === \"number\" && Number.isFinite(b.sourceDate) ? b.sourceDate : null;\n // Check if this is a status comparison where one is terminal and the other is not.\n // Terminal status should always win, regardless of sourceDate.\n const aKey = (a.key ?? \"\").trim() || \"_body\";\n const bKey = (b.key ?? \"\").trim() || \"_body\";\n if (aKey === bKey && aKey === \"status\") {\n const aVal = a.value ?? \"\";\n const bVal = b.value ?? \"\";\n const aIsTerminal = isTerminalFactStatus(aVal);\n const bIsTerminal = isTerminalFactStatus(bVal);\n if (aIsTerminal !== bIsTerminal) {\n // Prefer terminal status over non-terminal.\n return aIsTerminal ? -1 : 1;\n }\n }\n // Now compare sourceDate values (both present, or exactly one present).\n if (aSrc !== null && bSrc !== null && aSrc !== bSrc) return bSrc - aSrc;\n if ((aSrc === null) !== (bSrc === null)) {\n // Prefer fresh local writes (no sourceDate) over stale external sourceDate events.\n if (aSrc === null && bSrc !== null) return -1;\n if (aSrc !== null && bSrc === null) return 1;\n }\n const aRowid = readFactRowid(a);\n const bRowid = readFactRowid(b);\n if (aRowid !== null && bRowid !== null && aRowid !== bRowid) return bRowid - aRowid;\n // Last-resort deterministic tie-break when rowid is unavailable.\n return bOrder - aOrder;\n}\n\n/** Latest value per entity+key from non-superseded project facts.\n * Entity labels are normalized via factCanonicalLabel() (provenance-aware + trim + toLowerCase + suffix/separator cleanup)\n * so that case-variant entries (e.g. \"Humanizer\" / \"humanizer\") are merged into one group.\n * When multiple facts for the same entity+key have equal timestamps, last-write-wins ensures\n * newer writes always win — terminal status updates will not be silently dropped behind earlier\n * non-terminal facts with equal createdAt. */\nexport function groupProjectFactsByEntity(facts: MemoryEntry[]): Map<string, Map<string, MemoryEntry>> {\n const byEntity = new Map<string, Map<string, MemoryEntry>>();\n const winnerOrderByEntity = new Map<string, Map<string, number>>();\n const nowSec = Math.floor(Date.now() / 1000);\n for (const [idx, f] of facts.entries()) {\n if (typeof f.supersededAt === \"number\" && Number.isFinite(f.supersededAt)) continue;\n if (typeof f.expiresAt === \"number\" && Number.isFinite(f.expiresAt) && f.expiresAt <= nowSec) continue;\n if (!f.entity?.trim()) continue;\n const canonical = factCanonicalLabel(f);\n if (!canonical) continue;\n const k = (f.key ?? \"\").trim() || \"_body\";\n let km = byEntity.get(canonical);\n let winnerOrders = winnerOrderByEntity.get(canonical);\n if (!km) {\n km = new Map();\n byEntity.set(canonical, km);\n winnerOrders = new Map();\n winnerOrderByEntity.set(canonical, winnerOrders);\n }\n if (!winnerOrders) {\n winnerOrders = new Map();\n winnerOrderByEntity.set(canonical, winnerOrders);\n }\n const prev = km.get(k);\n const prevOrder = winnerOrders.get(k) ?? Number.NEGATIVE_INFINITY;\n if (!prev || factNewerThan(f, prev, idx, prevOrder) < 0) {\n km.set(k, f);\n winnerOrders.set(k, idx);\n }\n }\n return byEntity;\n}\n\nexport function factStatusToDisplay(raw: string): ActiveTaskStatus {\n const s = raw.trim().toLowerCase();\n if (s === \"open\") return \"In progress\";\n if (s === \"in_progress\" || s === \"in progress\") return \"In progress\";\n if (s === \"blocked\" || s.startsWith(\"blocked\")) return \"Stalled\";\n if (s === \"waiting\") return \"Waiting\";\n if (s === \"failed\" || s === \"error\") return \"Failed\";\n if (s === \"stalled\") return \"Stalled\";\n if (TERMINAL.has(s)) return \"Done\";\n return \"In progress\";\n}\n\nexport function displayStatusToFact(status: ActiveTaskStatus): string {\n switch (status) {\n case \"In progress\":\n return \"in_progress\";\n case \"Done\":\n return \"done\";\n case \"Failed\":\n return \"failed\";\n case \"Waiting\":\n return \"waiting\";\n case \"Stalled\":\n return \"blocked\";\n default:\n return \"in_progress\";\n }\n}\n\nexport function isTerminalFactStatus(raw: string): boolean {\n return TERMINAL.has(raw.trim().toLowerCase());\n}\n"],"mappings":"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"}