open-coleslaw 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3810,6 +3810,126 @@ connect();
3810
3810
 
3811
3811
  // src/dashboard/state-bridge.ts
3812
3812
  import { EventEmitter as EventEmitter2 } from "events";
3813
+
3814
+ // src/dashboard/minutes-hydrator.ts
3815
+ import { readdir, readFile } from "fs/promises";
3816
+ import { join as join5 } from "path";
3817
+ var MINUTES_DIR = "docs/open-coleslaw";
3818
+ async function hydratePastMeetings(projectPath, limit = 20) {
3819
+ const dir = join5(projectPath, MINUTES_DIR);
3820
+ let files;
3821
+ try {
3822
+ files = await readdir(dir);
3823
+ } catch {
3824
+ return [];
3825
+ }
3826
+ const minutesFiles = files.filter((f) => f.endsWith(".md")).filter((f) => f !== "INDEX.md").filter((f) => !f.startsWith(".")).sort().reverse();
3827
+ const threads = [];
3828
+ for (const filename of minutesFiles) {
3829
+ if (threads.length >= limit) break;
3830
+ try {
3831
+ const content = await readFile(join5(dir, filename), "utf-8");
3832
+ const parsed = parseMinutesMarkdown(filename, content);
3833
+ if (parsed) threads.push(parsed);
3834
+ } catch (err) {
3835
+ logger.warn(
3836
+ `minutes-hydrator: failed to parse ${filename}: ${err instanceof Error ? err.message : String(err)}`
3837
+ );
3838
+ }
3839
+ }
3840
+ return threads;
3841
+ }
3842
+ function parseMinutesMarkdown(filename, md) {
3843
+ const topic = grabField(md, "Topic") || firstH1(md) || filename.replace(/\.md$/, "");
3844
+ const meetingId = grabField(md, "MeetingId") || grabField(md, "meetingId") || `hydrated:${filename}`;
3845
+ const date = grabField(md, "Date") || inferDateFromFilename(filename) || "";
3846
+ const type = inferTypeFromFilename(filename);
3847
+ const participants = parseParticipants(md);
3848
+ const decisions = extractBulletedSection(md, "Decisions");
3849
+ const actionItems = extractBulletedSection(md, "Action Items");
3850
+ const agenda = extractNumberedSection(md, "Agenda");
3851
+ const startedAt = date ? Date.parse(date) || Date.now() : Date.now();
3852
+ return {
3853
+ meetingId,
3854
+ meetingType: type,
3855
+ topic,
3856
+ agenda,
3857
+ participants,
3858
+ status: "completed",
3859
+ phase: "minutes-generation",
3860
+ comments: [],
3861
+ mvps: [],
3862
+ decisions,
3863
+ actionItems,
3864
+ startedAt,
3865
+ completedAt: startedAt
3866
+ };
3867
+ }
3868
+ function grabField(md, name) {
3869
+ const re = new RegExp(
3870
+ `^\\s*-?\\s*\\*{0,2}\\s*${escapeRegex(name)}\\s*\\*{0,2}\\s*:\\s*\\*{0,2}\\s*(.+?)\\s*\\*{0,2}\\s*$`,
3871
+ "im"
3872
+ );
3873
+ const m = md.match(re);
3874
+ if (!m) return null;
3875
+ return m[1].replace(/^\**/, "").replace(/\**$/, "").trim();
3876
+ }
3877
+ function firstH1(md) {
3878
+ const m = md.match(/^#\s+(.+)$/m);
3879
+ if (!m) return null;
3880
+ return m[1].replace(/^Meeting Minutes\s*[—-]\s*/i, "").trim();
3881
+ }
3882
+ function parseParticipants(md) {
3883
+ const field = grabField(md, "Participants");
3884
+ if (!field) return [];
3885
+ return field.replace(/\*+/g, "").split(/[,、]/).map((s) => s.trim()).filter(Boolean);
3886
+ }
3887
+ function extractBulletedSection(md, heading) {
3888
+ const lines = md.split("\n");
3889
+ const out = [];
3890
+ let inSection = false;
3891
+ for (const line of lines) {
3892
+ const h = line.match(/^#{2,3}\s+(.+)$/);
3893
+ if (h) {
3894
+ inSection = h[1].trim().toLowerCase().startsWith(heading.toLowerCase());
3895
+ continue;
3896
+ }
3897
+ if (!inSection) continue;
3898
+ const m = line.match(/^\s*[-*]\s+(?:\[[ xX]\]\s+)?(.*\S)\s*$/);
3899
+ if (m) out.push(m[1]);
3900
+ }
3901
+ return out;
3902
+ }
3903
+ function extractNumberedSection(md, heading) {
3904
+ const lines = md.split("\n");
3905
+ const out = [];
3906
+ let inSection = false;
3907
+ for (const line of lines) {
3908
+ const h = line.match(/^#{2,3}\s+(.+)$/);
3909
+ if (h) {
3910
+ inSection = h[1].trim().toLowerCase().startsWith(heading.toLowerCase());
3911
+ continue;
3912
+ }
3913
+ if (!inSection) continue;
3914
+ const m = line.match(/^\s*\d+\.\s+(.+\S)\s*$/);
3915
+ if (m) out.push(m[1]);
3916
+ }
3917
+ return out;
3918
+ }
3919
+ function inferTypeFromFilename(filename) {
3920
+ if (filename.includes("_kickoff_")) return "kickoff";
3921
+ if (filename.includes("_verify-retry_") || filename.includes("-verify-retry")) return "verify-retry";
3922
+ return "design";
3923
+ }
3924
+ function inferDateFromFilename(filename) {
3925
+ const m = filename.match(/^(\d{4}-\d{2}-\d{2})_/);
3926
+ return m ? m[1] : null;
3927
+ }
3928
+ function escapeRegex(s) {
3929
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3930
+ }
3931
+
3932
+ // src/dashboard/state-bridge.ts
3813
3933
  var MAX_PAST_MEETINGS = 20;
3814
3934
  var EVENT_DEBOUNCE_MS = 100;
3815
3935
  var StateBridge = class extends EventEmitter2 {
@@ -3852,6 +3972,20 @@ var StateBridge = class extends EventEmitter2 {
3852
3972
  this.projects.set(info.projectPath, state);
3853
3973
  this.sessionToProject.set(info.sessionId, info.projectPath);
3854
3974
  logger.info(`Project registered: ${displayName} (${info.projectPath})`);
3975
+ hydratePastMeetings(info.projectPath).then((past) => {
3976
+ if (past.length === 0) return;
3977
+ const current = this.projects.get(info.projectPath);
3978
+ if (!current) return;
3979
+ current.pastMeetings = past;
3980
+ logger.info(
3981
+ `Hydrated ${past.length} past meeting(s) for ${displayName}`
3982
+ );
3983
+ this.emit("broadcast", JSON.stringify(this.getSnapshot()));
3984
+ }).catch((err) => {
3985
+ logger.warn(
3986
+ `Failed to hydrate past meetings for ${displayName}: ${err instanceof Error ? err.message : String(err)}`
3987
+ );
3988
+ });
3855
3989
  this.emit(
3856
3990
  "broadcast",
3857
3991
  JSON.stringify({
@@ -4084,13 +4218,13 @@ var DashboardClient = class {
4084
4218
 
4085
4219
  // src/dashboard/comment-queue.ts
4086
4220
  import { appendFile, mkdir } from "fs/promises";
4087
- import { join as join5 } from "path";
4221
+ import { join as join6 } from "path";
4088
4222
  var DIR = "docs/open-coleslaw";
4089
4223
  var PENDING = ".pending-comments.jsonl";
4090
4224
  async function enqueueComment(projectPath, entry) {
4091
- const dir = join5(projectPath, DIR);
4225
+ const dir = join6(projectPath, DIR);
4092
4226
  await mkdir(dir, { recursive: true });
4093
- const file = join5(dir, PENDING);
4227
+ const file = join6(dir, PENDING);
4094
4228
  const line = JSON.stringify(entry) + "\n";
4095
4229
  await appendFile(file, line, "utf8");
4096
4230
  }