@jonit-dev/night-watch-cli 1.7.24 → 1.7.25

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 (45) hide show
  1. package/dist/shared/types.d.ts +1 -1
  2. package/dist/shared/types.d.ts.map +1 -1
  3. package/dist/src/agents/soul-compiler.d.ts.map +1 -1
  4. package/dist/src/agents/soul-compiler.js +60 -6
  5. package/dist/src/agents/soul-compiler.js.map +1 -1
  6. package/dist/src/commands/qa.d.ts +4 -0
  7. package/dist/src/commands/qa.d.ts.map +1 -1
  8. package/dist/src/commands/qa.js +35 -0
  9. package/dist/src/commands/qa.js.map +1 -1
  10. package/dist/src/commands/serve.d.ts +12 -0
  11. package/dist/src/commands/serve.d.ts.map +1 -1
  12. package/dist/src/commands/serve.js +115 -0
  13. package/dist/src/commands/serve.js.map +1 -1
  14. package/dist/src/config.d.ts.map +1 -1
  15. package/dist/src/config.js +16 -3
  16. package/dist/src/config.js.map +1 -1
  17. package/dist/src/slack/channel-manager.js +3 -3
  18. package/dist/src/slack/channel-manager.js.map +1 -1
  19. package/dist/src/slack/client.d.ts +2 -1
  20. package/dist/src/slack/client.d.ts.map +1 -1
  21. package/dist/src/slack/client.js +20 -2
  22. package/dist/src/slack/client.js.map +1 -1
  23. package/dist/src/slack/deliberation.d.ts +17 -1
  24. package/dist/src/slack/deliberation.d.ts.map +1 -1
  25. package/dist/src/slack/deliberation.js +220 -46
  26. package/dist/src/slack/deliberation.js.map +1 -1
  27. package/dist/src/slack/interaction-listener.d.ts +48 -0
  28. package/dist/src/slack/interaction-listener.d.ts.map +1 -1
  29. package/dist/src/slack/interaction-listener.js +793 -12
  30. package/dist/src/slack/interaction-listener.js.map +1 -1
  31. package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts.map +1 -1
  32. package/dist/src/storage/repositories/sqlite/agent-persona-repository.js +209 -99
  33. package/dist/src/storage/repositories/sqlite/agent-persona-repository.js.map +1 -1
  34. package/dist/src/utils/avatar-generator.d.ts +1 -1
  35. package/dist/src/utils/avatar-generator.d.ts.map +1 -1
  36. package/dist/src/utils/avatar-generator.js +55 -15
  37. package/dist/src/utils/avatar-generator.js.map +1 -1
  38. package/dist/src/utils/notify.d.ts +1 -0
  39. package/dist/src/utils/notify.d.ts.map +1 -1
  40. package/dist/src/utils/notify.js +13 -1
  41. package/dist/src/utils/notify.js.map +1 -1
  42. package/package.json +1 -1
  43. package/scripts/night-watch-pr-reviewer-cron.sh +36 -8
  44. package/scripts/night-watch-qa-cron.sh +15 -3
  45. package/templates/night-watch-pr-reviewer.md +46 -17
@@ -1 +1 @@
1
- {"version":3,"file":"channel-manager.js","sourceRoot":"","sources":["../../../src/slack/channel-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAEnE;;;GAGG;AACH,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,2DAA2D;AAC9E,CAAC;AAED,MAAM,OAAO,cAAc;IACR,YAAY,CAAc;IAC1B,OAAO,CAAoB;IAE5C,YAAY,WAAwB,EAAE,MAAyB;QAC7D,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,oBAAoB,CAAC,WAAmB,EAAE,WAAmB;QACjE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,yBAAyB,EAAE,CAAC;YACnF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAE3D,uCAAuC;QACvC,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;YAC5B,OAAO,OAAO,CAAC,cAAc,CAAC;QAChC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,QAAQ,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACnD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;YAErE,mBAAmB;YACnB,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YAEjE,iCAAiC;YACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;YAEtE,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,SAAS,EACT,6BAA6B,WAAW,8DAA8D,EACtG,MAAM,CACP,CAAC;YACJ,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,8CAA8C,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;YACtF,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,qBAAqB,CAAC,WAAmB,EAAE,WAAmB;QAClE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO;YAAE,OAAO;QAEzC,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAE3D,IAAI,CAAC,OAAO,EAAE,cAAc;YAAE,OAAO;QAErC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;YAEtE,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,OAAO,CAAC,cAAc,EACtB,wBAAwB,WAAW,8CAA8C,EACjF,MAAM,CACP,CAAC;gBACF,+BAA+B;gBAC/B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAC1D,CAAC;YAED,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YAC/D,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,+CAA+C,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,uBAAuB,CAAC,OAAe,EAAE,MAAc,EAAE,KAAc;QAC3E,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ;YAAE,OAAO;QAEpF,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;QAEhE,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,OAAO,MAAM,MAAM,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC9E,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EACpC,IAAI,EACJ,GAAG,CACJ,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,mBAAmB,CAAC,OAAe,EAAE,WAAW,GAAG,QAAQ;QAC/D,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG;YAAE,OAAO;QAE/E,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;QAE1E,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,EAC/B,OAAO,EACP,OAAO,CACR,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,QAAQ,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClE,OAAO,CAAC,IAAI,CAAC,qCAAqC,QAAQ,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"channel-manager.js","sourceRoot":"","sources":["../../../src/slack/channel-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAEnE;;;GAGG;AACH,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,2DAA2D;AAC9E,CAAC;AAED,MAAM,OAAO,cAAc;IACR,YAAY,CAAc;IAC1B,OAAO,CAAoB;IAE5C,YAAY,WAAwB,EAAE,MAAyB;QAC7D,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,oBAAoB,CAAC,WAAmB,EAAE,WAAmB;QACjE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,yBAAyB,EAAE,CAAC;YACnF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAE3D,uCAAuC;QACvC,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;YAC5B,OAAO,OAAO,CAAC,cAAc,CAAC;QAChC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,QAAQ,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACnD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;YAErE,mBAAmB;YACnB,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YAEjE,iCAAiC;YACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;YAEtE,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,SAAS,EACT,gBAAgB,WAAW,iDAAiD,EAC5E,MAAM,CACP,CAAC;YACJ,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,8CAA8C,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;YACtF,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,qBAAqB,CAAC,WAAmB,EAAE,WAAmB;QAClE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO;YAAE,OAAO;QAEzC,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAE3D,IAAI,CAAC,OAAO,EAAE,cAAc;YAAE,OAAO;QAErC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;YAEtE,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,OAAO,CAAC,cAAc,EACtB,wBAAwB,WAAW,uBAAuB,EAC1D,MAAM,CACP,CAAC;gBACF,+BAA+B;gBAC/B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAC1D,CAAC;YAED,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YAC/D,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,+CAA+C,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,uBAAuB,CAAC,OAAe,EAAE,MAAc,EAAE,KAAc;QAC3E,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ;YAAE,OAAO;QAEpF,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;QAEhE,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,OAAO,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC3E,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EACpC,IAAI,EACJ,GAAG,CACJ,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,mBAAmB,CAAC,OAAe,EAAE,WAAW,GAAG,QAAQ;QAC/D,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG;YAAE,OAAO;QAE/E,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;QAE1E,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CACjC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,EAC/B,OAAO,EACP,OAAO,CACR,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,QAAQ,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClE,OAAO,CAAC,IAAI,CAAC,qCAAqC,QAAQ,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;CACF"}
@@ -12,6 +12,7 @@ export interface ISlackChannel {
12
12
  id: string;
13
13
  name: string;
14
14
  }
15
+ export declare function getFallbackAvatarUrl(persona: IAgentPersona): string;
15
16
  export declare class SlackClient {
16
17
  private readonly _client;
17
18
  constructor(botToken: string);
@@ -23,7 +24,7 @@ export declare class SlackClient {
23
24
  /**
24
25
  * Post a simple message to Slack using the bot's default identity.
25
26
  */
26
- postMessage(channel: string, text: string): Promise<void>;
27
+ postMessage(channel: string, text: string, threadTs?: string): Promise<void>;
27
28
  /**
28
29
  * Resolve the bot user id for mention detection/filtering.
29
30
  */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;gBAExB,QAAQ,EAAE,MAAM;IAI5B;;;OAGG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IA0BzB;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/D;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAM5C;;OAEG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAQhB;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUlD;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD;;OAEG;IACG,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,SAAK,GACT,OAAO,CAAC,aAAa,EAAE,CAAC;IAc3B;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAY9C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IA6B1D;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAiBzE"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAWD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAInE;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;gBAExB,QAAQ,EAAE,MAAM;IAI5B;;;OAGG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IA0BzB;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlF;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAM5C;;OAEG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAQhB;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUlD;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD;;OAEG;IACG,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,SAAK,GACT,OAAO,CAAC,aAAa,EAAE,CAAC;IAc3B;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAY9C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IA6B1D;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAiBzE"}
@@ -3,6 +3,23 @@
3
3
  * Uses the official @slack/web-api SDK for type-safe API calls.
4
4
  */
5
5
  import { WebClient } from '@slack/web-api';
6
+ function roleAvatarColor(role) {
7
+ const normalized = role.toLowerCase();
8
+ if (normalized.includes('security'))
9
+ return '8b1e2f';
10
+ if (normalized.includes('qa') || normalized.includes('quality'))
11
+ return '0f766e';
12
+ if (normalized.includes('lead') || normalized.includes('architect'))
13
+ return '1d4ed8';
14
+ if (normalized.includes('implementer') || normalized.includes('developer'))
15
+ return '374151';
16
+ return '111827';
17
+ }
18
+ export function getFallbackAvatarUrl(persona) {
19
+ const background = roleAvatarColor(persona.role);
20
+ const name = encodeURIComponent(persona.name.trim() || 'Night Watch');
21
+ return `https://ui-avatars.com/api/?name=${name}&background=${background}&color=ffffff&size=128&bold=true&format=png`;
22
+ }
6
23
  export class SlackClient {
7
24
  _client;
8
25
  constructor(botToken) {
@@ -16,7 +33,7 @@ export class SlackClient {
16
33
  // Slack icon_url must be a real HTTP URL — data URIs are not supported
17
34
  const iconUrl = persona.avatarUrl && !persona.avatarUrl.startsWith('data:')
18
35
  ? persona.avatarUrl
19
- : undefined;
36
+ : getFallbackAvatarUrl(persona);
20
37
  const result = await this._client.chat.postMessage({
21
38
  channel,
22
39
  text,
@@ -36,10 +53,11 @@ export class SlackClient {
36
53
  /**
37
54
  * Post a simple message to Slack using the bot's default identity.
38
55
  */
39
- async postMessage(channel, text) {
56
+ async postMessage(channel, text, threadTs) {
40
57
  await this._client.chat.postMessage({
41
58
  channel,
42
59
  text,
60
+ thread_ts: threadTs,
43
61
  });
44
62
  }
45
63
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAc3C,MAAM,OAAO,WAAW;IACL,OAAO,CAAY;IAEpC,YAAY,QAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CACf,OAAe,EACf,IAAY,EACZ,OAAsB,EACtB,QAAiB;QAEjB,uEAAuE;QACvE,MAAM,OAAO,GACX,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC;YACzD,CAAC,CAAC,OAAO,CAAC,SAAS;YACnB,CAAC,CAAC,SAAS,CAAC;QAEhB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;YACjD,OAAO;YACP,IAAI;YACJ,QAAQ,EAAE,OAAO,CAAC,IAAI;YACtB,QAAQ,EAAE,OAAO;YACjB,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,oDAAoD,OAAO,GAAG,CAAC,CAAC;QAClF,CAAC;QAED,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI;SACL,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,IAAY;QAC7C,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;YAClC,OAAO;YACP,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,OAAe,EACf,SAAiB,EACjB,KAAa;QAEb,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC;YAC/B,OAAO;YACP,SAAS;YACT,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB;QACjC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,IAAY;QAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC;YACrD,IAAI,EAAE,IAAI;iBACP,WAAW,EAAE;iBACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;iBAC3B,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;SAChB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,OAAO,EAAE,EAAY,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,SAAiB;QACpC,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IACnE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CACrB,OAAe,EACf,QAAgB,EAChB,KAAK,GAAG,EAAE;QAEV,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC;YACtD,OAAO;YACP,EAAE,EAAE,QAAQ;YACZ,KAAK;SACN,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,EAAE,EAAE,CAAC,CAAC,EAAY;YAClB,OAAO;YACP,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAW;SAC/B,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC;YACnD,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,GAAG;SACX,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,EAAE,EAAE,EAAE,CAAC,EAAY;YACnB,IAAI,EAAE,EAAE,CAAC,IAAc;SACxB,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS;QACb,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwC,CAAC;QAC9D,IAAI,MAA0B,CAAC;QAE/B,GAAG,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC3C,KAAK,EAAE,GAAG;gBACV,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9B,CAAC,CAAC;YAEH,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;gBAC1C,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;gBACrB,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,KAAK,WAAW,EAAE,CAAC;oBACjE,SAAS;gBACX,CAAC;gBAED,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE;oBACZ,EAAE;oBACF,IAAI,EAAE,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,CAAW;iBACxD,CAAC,CAAC;YACL,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAAC;YACzD,MAAM,GAAG,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,CAAC,QAAQ,MAAM,EAAE;QAEjB,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,OAAiB;QACpD,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QAEzC,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,iDAAiD;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YAC/C,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC;gBACtC,OAAO,EAAE,SAAS;gBAClB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;aACvB,CAAC,CAAC;YACH,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC;QAC/B,CAAC;QAED,OAAO,YAAY,CAAC;IACtB,CAAC;CACF"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/slack/client.ts"],"names":[],"mappings":"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"}
@@ -6,11 +6,19 @@
6
6
  import { IAgentPersona, IDiscussionTrigger, ISlackDiscussion } from "../../shared/types.js";
7
7
  import { SlackClient } from "./client.js";
8
8
  import { INightWatchConfig } from "../types.js";
9
+ interface IHumanizeSlackReplyOptions {
10
+ allowEmoji?: boolean;
11
+ allowNonFacialEmoji?: boolean;
12
+ maxSentences?: number;
13
+ }
14
+ export declare function humanizeSlackReply(raw: string, options?: IHumanizeSlackReplyOptions): string;
9
15
  export declare class DeliberationEngine {
10
16
  private readonly _slackClient;
11
17
  private readonly _config;
12
18
  private readonly _humanResumeTimers;
19
+ private readonly _emojiCadenceCounter;
13
20
  constructor(slackClient: SlackClient, config: INightWatchConfig);
21
+ private _humanizeForPost;
14
22
  /**
15
23
  * Start a new discussion thread for a trigger event.
16
24
  * Posts the opening message and kicks off the first round of contributions.
@@ -45,6 +53,14 @@ export declare class DeliberationEngine {
45
53
  * Reply as a persona in any Slack thread — no formal discussion required.
46
54
  * Used when someone @mentions a persona outside of a Night Watch discussion.
47
55
  */
48
- replyAsAgent(channel: string, threadTs: string, incomingText: string, persona: IAgentPersona): Promise<void>;
56
+ replyAsAgent(channel: string, threadTs: string, incomingText: string, persona: IAgentPersona, projectContext?: string): Promise<void>;
57
+ /**
58
+ * Generate and post a proactive message from a persona.
59
+ * Used by the interaction listener when a channel has been idle.
60
+ * The persona shares an observation, question, or suggestion based on
61
+ * project context and roadmap state — in their own voice.
62
+ */
63
+ postProactiveMessage(channel: string, persona: IAgentPersona, projectContext: string, roadmapContext: string): Promise<void>;
49
64
  }
65
+ export {};
50
66
  //# sourceMappingURL=deliberation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deliberation.d.ts","sourceRoot":"","sources":["../../../src/slack/deliberation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAmShD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoD;gBAE3E,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB;IAK/D;;;OAGG;IACG,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAmB/D,wBAAwB;IAqEtC;;OAEG;IACG,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA+CpF;;;OAGG;IACG,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IA+ChB;;OAEG;YACW,qBAAqB;IAkDnC;;;;OAIG;YACW,kBAAkB;IA2GhC;;;OAGG;IACG,mBAAmB,CACvB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IA8ChB;;;OAGG;IACG,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC;CA4BjB"}
1
+ {"version":3,"file":"deliberation.d.ts","sourceRoot":"","sources":["../../../src/slack/deliberation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAQhD,UAAU,0BAA0B;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyWD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,MAAM,CAgChG;AAYD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoD;IACvF,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;gBAEtD,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB;IAK/D,OAAO,CAAC,gBAAgB;IAmBxB;;;OAGG;IACG,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAmB/D,wBAAwB;IAqEtC;;OAEG;IACG,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqDpF;;;OAGG;IACG,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IA+ChB;;OAEG;YACW,qBAAqB;IAwDnC;;;;OAIG;YACW,kBAAkB;IAgHhC;;;OAGG;IACG,mBAAmB,CACvB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAqDhB;;;OAGG;IACG,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC;IA0ChB;;;;;OAKG;IACG,oBAAoB,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC;CAyCjB"}
@@ -9,6 +9,7 @@ const MAX_ROUNDS = 3;
9
9
  const MESSAGE_DELAY_MS = 1500; // Rate limit: 1.5s between posts
10
10
  const DISCUSSION_RESUME_DELAY_MS = 60_000;
11
11
  const DISCUSSION_REPLAY_GUARD_MS = 30 * 60_000;
12
+ const MAX_HUMANIZED_SENTENCES = 2;
12
13
  const inFlightDiscussionStarts = new Map();
13
14
  function discussionStartKey(trigger) {
14
15
  return `${trigger.projectPath}:${trigger.type}:${trigger.ref}`;
@@ -36,6 +37,8 @@ function getChannelForTrigger(trigger, config) {
36
37
  return slack.channels.incidents;
37
38
  case 'prd_kickoff':
38
39
  return slack.channels.eng; // Callers should populate trigger.channelId with proj channel
40
+ case 'code_watch':
41
+ return slack.channels.eng;
39
42
  default:
40
43
  return slack.channels.eng;
41
44
  }
@@ -87,6 +90,12 @@ function getParticipatingPersonas(triggerType, personas) {
87
90
  add(dev);
88
91
  add(carlos);
89
92
  break;
93
+ case 'code_watch':
94
+ add(dev);
95
+ add(carlos);
96
+ add(maya);
97
+ add(priya);
98
+ break;
90
99
  default:
91
100
  add(carlos);
92
101
  break;
@@ -145,11 +154,13 @@ function resolvePersonaAIConfig(persona, config) {
145
154
  function buildOpeningMessage(trigger) {
146
155
  switch (trigger.type) {
147
156
  case 'pr_review':
148
- return `Just opened a PR — ${trigger.ref}${trigger.prUrl ? ` ${trigger.prUrl}` : ''}. Ready for review. 🔨`;
157
+ return `Opened ${trigger.ref}${trigger.prUrl ? ` ${trigger.prUrl}` : ''}. Ready for eyes.`;
149
158
  case 'build_failure':
150
- return `Build failure on ${trigger.ref}. Looking into it now 🔍\n\n${trigger.context.slice(0, 500)}`;
159
+ return `Build broke on ${trigger.ref}. Looking into it.\n\n${trigger.context.slice(0, 500)}`;
151
160
  case 'prd_kickoff':
152
- return `Picking up PRD: ${trigger.ref}. Starting implementation. 🚀`;
161
+ return `Picking up ${trigger.ref}. Going to start carving out the implementation.`;
162
+ case 'code_watch':
163
+ return `Something caught my eye during a scan — want to get a second opinion on this.\n\n${trigger.context.slice(0, 600)}`;
153
164
  default:
154
165
  return trigger.context.slice(0, 500);
155
166
  }
@@ -159,28 +170,35 @@ function buildOpeningMessage(trigger) {
159
170
  * This is what gets sent to the AI provider to generate the agent's message.
160
171
  */
161
172
  function buildContributionPrompt(persona, trigger, threadHistory, round) {
162
- return `You are ${persona.name}, ${persona.role}, participating in a Slack thread with your team.
173
+ const isFirstRound = round === 1;
174
+ const isFinalRound = round >= MAX_ROUNDS;
175
+ return `You are ${persona.name}, ${persona.role}.
176
+ You're in a Slack thread with your teammates — Dev (implementer), Carlos (tech lead), Maya (security), and Priya (QA). This is a real conversation, not a report.
163
177
 
164
- ## Thread Context
165
178
  Trigger: ${trigger.type} — ${trigger.ref}
166
- Round: ${round} of ${MAX_ROUNDS}
179
+ Round: ${round}/${MAX_ROUNDS}${isFinalRound ? ' (final round — wrap up)' : ''}
167
180
 
168
181
  ## Context
169
182
  ${trigger.context.slice(0, 2000)}
170
183
 
171
184
  ## Thread So Far
172
- ${threadHistory || '(No messages yet)'}
185
+ ${threadHistory || '(Thread just started)'}
173
186
 
174
- ## Your Task
175
- Review the above from your specific expertise angle. Post a SHORT Slack message (2-3 sentences max).
176
- - This is Slack chat, not a document. Be concise.
177
- - Speak only to your domain don't repeat what others said.
178
- - Use your natural emoji style.
179
- - If everything looks fine from your angle, just say so briefly.
180
- - If you have a concern, state it clearly with a specific fix suggestion.
181
- - If you have no concerns and others seem satisfied, you can just react positively.
187
+ ## How to respond
188
+ Write a short Slack message 1 to 2 sentences. This is chat, not documentation.
189
+ ${isFirstRound ? '- First round: give your initial take from your angle. Be specific.' : '- Follow-up round: respond to what others said. Agree, push back, or add something new.'}
190
+ - Talk like a teammate, not an assistant. No pleasantries, no filler.
191
+ - Stay in your lane only comment on your domain unless something crosses into it.
192
+ - You can name-drop teammates when handing off ("Maya should look at the auth here").
193
+ - If nothing concerns you, a brief "nothing from me" or a short acknowledgment is fine.
194
+ - If you have a concern, name it specifically and suggest a direction.
195
+ - No markdown formatting. No bullet lists. No headings. Just a message.
196
+ - Emojis: use one only if it genuinely fits. Default to none.
197
+ - Never start with "Great question", "Of course", "I hope this helps", or similar.
198
+ - Never say "as an AI" or break character.
199
+ ${isFinalRound ? '- Final round: be decisive. State your position clearly.' : ''}
182
200
 
183
- Write ONLY your message, nothing else. Do not include your name or any prefix.`;
201
+ Write ONLY your message. No name prefix, no labels.`;
184
202
  }
185
203
  /**
186
204
  * Call the AI provider to generate an agent contribution.
@@ -240,14 +258,106 @@ async function callAIForContribution(persona, config, contributionPrompt) {
240
258
  }
241
259
  return `[${persona.name}: No AI provider configured]`;
242
260
  }
261
+ const CANNED_PHRASE_PREFIXES = [
262
+ /^great question[,.! ]*/i,
263
+ /^of course[,.! ]*/i,
264
+ /^certainly[,.! ]*/i,
265
+ /^you['’]re absolutely right[,.! ]*/i,
266
+ /^i hope this helps[,.! ]*/i,
267
+ ];
268
+ function limitEmojiCount(text, maxEmojis) {
269
+ let seen = 0;
270
+ return text.replace(/[\p{Extended_Pictographic}]/gu, (m) => {
271
+ seen += 1;
272
+ return seen <= maxEmojis ? m : '';
273
+ });
274
+ }
275
+ function isFacialEmoji(char) {
276
+ return /[\u{1F600}-\u{1F64F}\u{1F910}-\u{1F92F}\u{1F970}-\u{1F97A}]/u.test(char);
277
+ }
278
+ function applyEmojiPolicy(text, allowEmoji, allowNonFacialEmoji) {
279
+ if (!allowEmoji) {
280
+ return text.replace(/[\p{Extended_Pictographic}]/gu, '');
281
+ }
282
+ const emojis = Array.from(text.matchAll(/[\p{Extended_Pictographic}]/gu)).map((m) => m[0]);
283
+ if (emojis.length === 0)
284
+ return text;
285
+ const chosenFacial = emojis.find((e) => isFacialEmoji(e));
286
+ const chosen = chosenFacial ?? (allowNonFacialEmoji ? emojis[0] : null);
287
+ if (!chosen) {
288
+ return text.replace(/[\p{Extended_Pictographic}]/gu, '');
289
+ }
290
+ let kept = false;
291
+ return text.replace(/[\p{Extended_Pictographic}]/gu, (e) => {
292
+ if (!kept && e === chosen) {
293
+ kept = true;
294
+ return e;
295
+ }
296
+ return '';
297
+ });
298
+ }
299
+ function trimToSentences(text, maxSentences) {
300
+ const parts = text
301
+ .split(/(?<=[.!?])\s+/)
302
+ .map((s) => s.trim())
303
+ .filter(Boolean);
304
+ if (parts.length <= maxSentences)
305
+ return text.trim();
306
+ return parts.slice(0, maxSentences).join(' ').trim();
307
+ }
308
+ export function humanizeSlackReply(raw, options = {}) {
309
+ const { allowEmoji = true, allowNonFacialEmoji = true, maxSentences = MAX_HUMANIZED_SENTENCES, } = options;
310
+ let text = raw.trim();
311
+ if (!text)
312
+ return text;
313
+ // Remove markdown formatting artifacts that look templated in chat.
314
+ text = text
315
+ .replace(/^#{1,6}\s+/gm, '')
316
+ .replace(/^\s*[-*]\s+/gm, '')
317
+ .replace(/\*\*(.*?)\*\*/g, '$1')
318
+ .replace(/\s+/g, ' ')
319
+ .trim();
320
+ // Strip common assistant-y openers.
321
+ for (const pattern of CANNED_PHRASE_PREFIXES) {
322
+ text = text.replace(pattern, '').trim();
323
+ }
324
+ text = applyEmojiPolicy(text, allowEmoji, allowNonFacialEmoji);
325
+ text = limitEmojiCount(text, 1);
326
+ text = trimToSentences(text, maxSentences);
327
+ if (text.length > 260) {
328
+ text = `${text.slice(0, 257).trimEnd()}...`;
329
+ }
330
+ return text;
331
+ }
332
+ function buildCurrentCliInvocation(args) {
333
+ const cliEntry = process.argv[1];
334
+ if (!cliEntry)
335
+ return null;
336
+ return [...process.execArgv, cliEntry, ...args];
337
+ }
338
+ function formatCommandForLog(bin, args) {
339
+ return [bin, ...args].map((part) => JSON.stringify(part)).join(' ');
340
+ }
243
341
  export class DeliberationEngine {
244
342
  _slackClient;
245
343
  _config;
246
344
  _humanResumeTimers = new Map();
345
+ _emojiCadenceCounter = new Map();
247
346
  constructor(slackClient, config) {
248
347
  this._slackClient = slackClient;
249
348
  this._config = config;
250
349
  }
350
+ _humanizeForPost(channel, threadTs, persona, raw) {
351
+ const key = `${channel}:${threadTs}:${persona.id}`;
352
+ const count = (this._emojiCadenceCounter.get(key) ?? 0) + 1;
353
+ this._emojiCadenceCounter.set(key, count);
354
+ // Human cadence:
355
+ // - emoji roughly every 3rd message by same persona in same thread
356
+ // - non-facial emoji much rarer (roughly every 9th message)
357
+ const allowEmoji = count % 3 === 0;
358
+ const allowNonFacialEmoji = count % 9 === 0;
359
+ return humanizeSlackReply(raw, { allowEmoji, allowNonFacialEmoji, maxSentences: 2 });
360
+ }
251
361
  /**
252
362
  * Start a new discussion thread for a trigger event.
253
363
  * Posts the opening message and kicks off the first round of contributions.
@@ -352,7 +462,8 @@ export class DeliberationEngine {
352
462
  message = `[Contribution from ${persona.name} unavailable — AI provider not configured]`;
353
463
  }
354
464
  if (message) {
355
- await this._slackClient.postAsAgent(discussion.channelId, message, persona, discussion.threadTs);
465
+ const finalMessage = this._humanizeForPost(discussion.channelId, discussion.threadTs, persona, message);
466
+ await this._slackClient.postAsAgent(discussion.channelId, finalMessage, persona, discussion.threadTs);
356
467
  repos.slackDiscussion.addParticipant(discussionId, persona.id);
357
468
  await sleep(MESSAGE_DELAY_MS);
358
469
  }
@@ -383,7 +494,7 @@ export class DeliberationEngine {
383
494
  const updated = innerRepos.slackDiscussion.getById(discussion.id);
384
495
  if (!updated || updated.status !== 'active')
385
496
  return;
386
- await this._slackClient.postAsAgent(channel, "Picking back up let me summarize where we are and continue. 🏗️", carlos, threadTs);
497
+ await this._slackClient.postAsAgent(channel, "Ok, picking this back up. Let me see where we landed.", carlos, threadTs);
387
498
  await sleep(MESSAGE_DELAY_MS);
388
499
  await this._evaluateConsensus(discussion.id, {
389
500
  type: discussion.triggerType,
@@ -421,9 +532,10 @@ export class DeliberationEngine {
421
532
  message = '';
422
533
  }
423
534
  if (message) {
424
- await this._slackClient.postAsAgent(discussion.channelId, message, persona, discussion.threadTs);
535
+ const finalMessage = this._humanizeForPost(discussion.channelId, discussion.threadTs, persona, message);
536
+ await this._slackClient.postAsAgent(discussion.channelId, finalMessage, persona, discussion.threadTs);
425
537
  repos.slackDiscussion.addParticipant(discussionId, persona.id);
426
- historyText = historyText ? `${historyText}\n---\n${message}` : message;
538
+ historyText = historyText ? `${historyText}\n---\n${finalMessage}` : finalMessage;
427
539
  await sleep(MESSAGE_DELAY_MS);
428
540
  }
429
541
  }
@@ -449,19 +561,21 @@ export class DeliberationEngine {
449
561
  // Get thread history and let Carlos evaluate
450
562
  const history = await this._slackClient.getChannelHistory(discussion.channelId, discussion.threadTs, 20);
451
563
  const historyText = history.map(m => m.text).join('\n---\n');
452
- const consensusPrompt = `You are ${carlos.name}, ${carlos.role}.
453
-
454
- Review this discussion thread and decide: are we ready to ship, do we need another round of review, or do we need a human?
564
+ const consensusPrompt = `You are ${carlos.name}, ${carlos.role}. You're wrapping up a team discussion.
455
565
 
456
566
  Thread:
457
567
  ${historyText}
458
568
 
459
- Round: ${discussion.round} of ${MAX_ROUNDS}
569
+ Round: ${discussion.round}/${MAX_ROUNDS}
570
+
571
+ Make the call. Are we done, do we need another pass, or does a human need to weigh in?
572
+
573
+ Respond with EXACTLY one of these formats (include the prefix):
574
+ - APPROVE: [short closing message in your voice — e.g., "Clean. Let's ship it."]
575
+ - CHANGES: [what specifically still needs work — be concrete, not vague]
576
+ - HUMAN: [why this needs a human decision — be specific about what's ambiguous]
460
577
 
461
- Respond with ONLY one of:
462
- - APPROVE: [your short closing message, e.g., "LGTM 👍 Ship it 🚀"]
463
- - CHANGES: [summary of what still needs to change — be specific]
464
- - HUMAN: [why you need a human decision]`;
578
+ Write the prefix and your message. Nothing else.`;
465
579
  let decision;
466
580
  try {
467
581
  decision = await callAIForContribution(carlos, this._config, consensusPrompt);
@@ -470,14 +584,14 @@ Respond with ONLY one of:
470
584
  decision = 'HUMAN: AI evaluation failed — needs manual review';
471
585
  }
472
586
  if (decision.startsWith('APPROVE')) {
473
- const message = decision.replace(/^APPROVE:\s*/, '').trim() || 'Ship it 🚀';
587
+ const message = decision.replace(/^APPROVE:\s*/, '').trim() || 'Clean. Ship it.';
474
588
  await this._slackClient.postAsAgent(discussion.channelId, message, carlos, discussion.threadTs);
475
589
  repos.slackDiscussion.updateStatus(discussionId, 'consensus', 'approved');
476
590
  return;
477
591
  }
478
592
  if (decision.startsWith('CHANGES') && discussion.round < MAX_ROUNDS) {
479
593
  const changes = decision.replace(/^CHANGES:\s*/, '').trim();
480
- await this._slackClient.postAsAgent(discussion.channelId, `One more pass needed:\n${changes}`, carlos, discussion.threadTs);
594
+ await this._slackClient.postAsAgent(discussion.channelId, changes, carlos, discussion.threadTs);
481
595
  await sleep(MESSAGE_DELAY_MS);
482
596
  // Increment round and start another contribution round, then loop back.
483
597
  const nextRound = discussion.round + 1;
@@ -491,7 +605,7 @@ Respond with ONLY one of:
491
605
  if (decision.startsWith('CHANGES') && discussion.round >= MAX_ROUNDS) {
492
606
  // Max rounds reached — set changes_requested and optionally trigger PR refinement
493
607
  const changesSummary = decision.replace(/^CHANGES:\s*/, '').trim();
494
- await this._slackClient.postAsAgent(discussion.channelId, "3 rounds inshipping what we have. Ship it 🚀", carlos, discussion.threadTs);
608
+ await this._slackClient.postAsAgent(discussion.channelId, `We've been at this for ${MAX_ROUNDS} rounds. Sending it through with the remaining notes Dev can address them in the next pass.`, carlos, discussion.threadTs);
495
609
  repos.slackDiscussion.updateStatus(discussionId, 'consensus', 'changes_requested');
496
610
  if (discussion.triggerType === 'pr_review') {
497
611
  await this.triggerPRRefinement(discussionId, changesSummary, discussion.triggerRef).catch(e => console.warn('PR refinement trigger failed:', e));
@@ -499,7 +613,10 @@ Respond with ONLY one of:
499
613
  return;
500
614
  }
501
615
  // HUMAN or fallback
502
- await this._slackClient.postAsAgent(discussion.channelId, "This one needs a human call. Flagging for review. 🚩", carlos, discussion.threadTs);
616
+ const humanReason = decision.replace(/^HUMAN:\s*/, '').trim();
617
+ await this._slackClient.postAsAgent(discussion.channelId, humanReason
618
+ ? `Need a human on this one — ${humanReason}`
619
+ : 'This needs a human call. Flagging it.', carlos, discussion.threadTs);
503
620
  repos.slackDiscussion.updateStatus(discussionId, 'blocked', 'human_needed');
504
621
  return;
505
622
  }
@@ -515,31 +632,36 @@ Respond with ONLY one of:
515
632
  return;
516
633
  const personas = repos.agentPersona.getActive();
517
634
  const carlos = findCarlos(personas) ?? personas[0];
518
- const dev = findDev(personas) ?? personas[0];
635
+ const actor = carlos?.name ?? 'Night Watch';
519
636
  if (carlos) {
520
- await this._slackClient.postAsAgent(discussion.channelId, `Sending feedback to the reviewer agent. Changes needed:\n${changesSummary}`, carlos, discussion.threadTs);
637
+ await this._slackClient.postAsAgent(discussion.channelId, `Sending PR #${prNumber} back through with the notes.`, carlos, discussion.threadTs);
521
638
  await sleep(MESSAGE_DELAY_MS);
522
639
  }
523
640
  // Set NW_SLACK_FEEDBACK and trigger reviewer
524
641
  const feedback = JSON.stringify({ discussionId, prNumber, changes: changesSummary });
642
+ const invocationArgs = buildCurrentCliInvocation(['review']);
643
+ if (!invocationArgs) {
644
+ console.warn(`[slack][job] triggerPRRefinement reviewer spawn failed via ${actor} pr=${prNumber}: CLI entry path unavailable`);
645
+ if (carlos) {
646
+ await this._slackClient.postAsAgent(discussion.channelId, `Can't start the reviewer right now — runtime issue. Will retry.`, carlos, discussion.threadTs);
647
+ }
648
+ return;
649
+ }
650
+ console.log(`[slack][job] triggerPRRefinement reviewer spawn via ${actor} pr=${prNumber} cmd=${formatCommandForLog(process.execPath, invocationArgs)}`);
525
651
  // Spawn the reviewer as a detached process
526
652
  const { spawn } = await import('child_process');
527
- const reviewer = spawn(process.execPath, [process.argv[1], 'review', '--pr', prNumber], {
653
+ const reviewer = spawn(process.execPath, invocationArgs, {
528
654
  detached: true,
529
655
  stdio: 'ignore',
530
- env: { ...process.env, NW_SLACK_FEEDBACK: feedback },
656
+ env: { ...process.env, NW_SLACK_FEEDBACK: feedback, NW_TARGET_PR: prNumber },
531
657
  });
532
658
  reviewer.unref();
533
- // Post update
534
- if (dev) {
535
- await this._slackClient.postAsAgent(discussion.channelId, `Reviewer agent kicked off for PR #${prNumber} 🔨 Will post back when done.`, dev, discussion.threadTs);
536
- }
537
659
  }
538
660
  /**
539
661
  * Reply as a persona in any Slack thread — no formal discussion required.
540
662
  * Used when someone @mentions a persona outside of a Night Watch discussion.
541
663
  */
542
- async replyAsAgent(channel, threadTs, incomingText, persona) {
664
+ async replyAsAgent(channel, threadTs, incomingText, persona, projectContext) {
543
665
  let history = [];
544
666
  try {
545
667
  history = await this._slackClient.getChannelHistory(channel, threadTs, 10);
@@ -549,10 +671,18 @@ Respond with ONLY one of:
549
671
  }
550
672
  const historyText = history.map((m) => m.text).join('\n---\n');
551
673
  const prompt = `You are ${persona.name}, ${persona.role}.\n` +
552
- (persona.soul?.whoIAm ? `About you: ${persona.soul.whoIAm}\n\n` : '') +
553
- (historyText ? `Thread context:\n${historyText}\n\n` : '') +
554
- `Someone just said: "${incomingText}"\n\n` +
555
- `Reply concisely in your own voice. Keep it under 3 sentences unless detail is clearly needed.`;
674
+ `Your teammates: Dev (implementer), Carlos (tech lead), Maya (security), Priya (QA).\n\n` +
675
+ (projectContext ? `Project context: ${projectContext}\n\n` : '') +
676
+ (historyText ? `Thread so far:\n${historyText}\n\n` : '') +
677
+ `Latest message: "${incomingText}"\n\n` +
678
+ `Respond in your own voice. This is Slack — keep it to 1-2 sentences.\n` +
679
+ `- Talk like a colleague, not a bot. No "Great question", "Of course", or "I hope this helps".\n` +
680
+ `- You can tag teammates by name if someone else should weigh in.\n` +
681
+ `- No markdown formatting, headings, or bullet lists.\n` +
682
+ `- Emojis: one max, only if it fits naturally. Default to none.\n` +
683
+ `- If the question is outside your domain, say so briefly and point to the right person.\n` +
684
+ `- If you disagree, say why in one line. If you agree, keep it short.\n\n` +
685
+ `Write only your reply. No name prefix.`;
556
686
  let message;
557
687
  try {
558
688
  message = await callAIForContribution(persona, this._config, prompt);
@@ -561,7 +691,51 @@ Respond with ONLY one of:
561
691
  message = `[Reply from ${persona.name} unavailable — AI provider not configured]`;
562
692
  }
563
693
  if (message) {
564
- await this._slackClient.postAsAgent(channel, message, persona, threadTs);
694
+ await this._slackClient.postAsAgent(channel, this._humanizeForPost(channel, threadTs, persona, message), persona, threadTs);
695
+ }
696
+ }
697
+ /**
698
+ * Generate and post a proactive message from a persona.
699
+ * Used by the interaction listener when a channel has been idle.
700
+ * The persona shares an observation, question, or suggestion based on
701
+ * project context and roadmap state — in their own voice.
702
+ */
703
+ async postProactiveMessage(channel, persona, projectContext, roadmapContext) {
704
+ const prompt = `You are ${persona.name}, ${persona.role}.\n` +
705
+ `Your teammates: Dev (implementer), Carlos (tech lead), Maya (security), Priya (QA).\n\n` +
706
+ `You're posting an unprompted message in the team's Slack channel. ` +
707
+ `The channel has been quiet — you want to share something useful, not just fill silence.\n\n` +
708
+ (projectContext ? `Project context: ${projectContext}\n\n` : '') +
709
+ (roadmapContext ? `Roadmap/PRD status:\n${roadmapContext}\n\n` : '') +
710
+ `Write a SHORT proactive message (1-2 sentences) that does ONE of these:\n` +
711
+ `- Question a roadmap priority or ask if something should be reordered\n` +
712
+ `- Flag something you've been thinking about from your domain (security concern, test gap, architectural question, implementation idea)\n` +
713
+ `- Suggest an improvement or raise a "have we thought about..." question\n` +
714
+ `- Share a concrete observation about the current state of the project\n` +
715
+ `- Offer to kick off a task: "I can run a review on X if nobody's on it"\n\n` +
716
+ `Rules:\n` +
717
+ `- Stay in your lane. Only bring up things relevant to your expertise.\n` +
718
+ `- Be specific — name the feature, file, or concern. No vague "we should think about things."\n` +
719
+ `- Sound like a teammate dropping a thought in chat, not making an announcement.\n` +
720
+ `- No markdown, headings, bullets. Just a message.\n` +
721
+ `- No "Great question", "Just checking in", or "Hope everyone is doing well."\n` +
722
+ `- Emojis: one max, only if natural. Default to none.\n` +
723
+ `- If you genuinely have nothing useful to say, write exactly: SKIP\n\n` +
724
+ `Write only your message. No name prefix.`;
725
+ let message;
726
+ try {
727
+ message = await callAIForContribution(persona, this._config, prompt);
728
+ }
729
+ catch {
730
+ return; // Silently skip — proactive messages are optional
731
+ }
732
+ if (!message || message.trim().toUpperCase() === 'SKIP') {
733
+ return;
734
+ }
735
+ const dummyTs = `${Date.now()}`;
736
+ const finalMessage = this._humanizeForPost(channel, dummyTs, persona, message);
737
+ if (finalMessage) {
738
+ await this._slackClient.postAsAgent(channel, finalMessage, persona);
565
739
  }
566
740
  }
567
741
  }