@pi-unipi/milestone 2.3.0 → 2.4.1

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 (3) hide show
  1. package/hooks.ts +9 -15
  2. package/package.json +2 -2
  3. package/coexist.ts +0 -114
package/hooks.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
10
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
- import { MILESTONE_DIRS, safeMtimeMs, tryRead } from "@pi-unipi/core";
11
+ import { MILESTONE_DIRS, UNIPI_EVENTS, safeMtimeMs, tryRead } from "@pi-unipi/core";
12
12
  import { parseMilestones, getProgressSummary, updateItemStatus } from "./milestone.js";
13
13
 
14
14
  /** Track when the session started for diffing modified files */
@@ -167,19 +167,7 @@ export function registerSessionEndHook(pi: ExtensionAPI): void {
167
167
  }
168
168
  });
169
169
 
170
- // Listen for WORKFLOW_END events
171
- pi.on("input", (event) => {
172
- // Check if this is a unipi event emission for WORKFLOW_END
173
- // The input event fires for tool calls; we need to detect when
174
- // the workflow ends. We'll use the events system instead.
175
- return undefined;
176
- });
177
-
178
- // Use tool_result to detect workflow end
179
- // Actually, we should listen for the UNIPI_EVENTS.WORKFLOW_END via pi.events
180
- // But the ExtensionAPI doesn't expose pi.events.on() directly.
181
- // Instead, we'll hook into session_shutdown to do a final sync.
182
- pi.on("session_shutdown", () => {
170
+ const syncModifiedDocs = () => {
183
171
  const cwd = process.cwd();
184
172
  const milestonesPath = path.join(cwd, MILESTONE_DIRS.MILESTONES);
185
173
 
@@ -204,5 +192,11 @@ export function registerSessionEndHook(pi: ExtensionAPI): void {
204
192
  updateItemStatus(milestonesPath, phase, text, true);
205
193
  }
206
194
  }
207
- });
195
+ };
196
+
197
+ // Workflow emits this once its follow-up agent loop drains.
198
+ pi.events.on(UNIPI_EVENTS.WORKFLOW_END, syncModifiedDocs);
199
+
200
+ // Final fallback for changes made outside a workflow or before event wiring.
201
+ pi.on("session_shutdown", syncModifiedDocs);
208
202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/milestone",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "Lifecycle layer for project-level goals — MILESTONES.md tracking, session hooks, auto-sync",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -29,7 +29,7 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@pi-unipi/core": "2.3.0"
32
+ "@pi-unipi/core": "2.4.1"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@earendil-works/pi-coding-agent": "^0.80.0",
package/coexist.ts DELETED
@@ -1,114 +0,0 @@
1
- /**
2
- * @pi-unipi/milestone — Coexist triggers
3
- *
4
- * Hooks into workflow skill completions to offer milestone integration.
5
- * Non-blocking — if MILESTONES.md doesn't exist, triggers silently skip.
6
- */
7
-
8
- import * as path from "node:path";
9
- import { MILESTONE_DIRS, tryRead } from "@pi-unipi/core";
10
- import { parseMilestones, updateItemStatus, writeMilestones } from "./milestone.js";
11
- import type { MilestoneDoc } from "./types.js";
12
-
13
- /**
14
- * After brainstorm completes: check if new spec items map to milestones.
15
- * Offers to mark matching items as planned.
16
- */
17
- export function onBrainstormComplete(specPath: string): void {
18
- const cwd = process.cwd();
19
- const milestonesPath = path.join(cwd, MILESTONE_DIRS.MILESTONES);
20
-
21
- // Silently skip if no MILESTONES.md
22
- if (!tryRead(milestonesPath)) return;
23
-
24
- const specContent = tryRead(specPath);
25
- if (!specContent) return;
26
-
27
- // Extract checklist items from the new spec
28
- const specItems: string[] = [];
29
- for (const line of specContent.split("\n")) {
30
- const match = line.match(/^-\s+\[([ xX])\]\s+(.+)$/);
31
- if (match) {
32
- specItems.push(match[2].trim().toLowerCase());
33
- }
34
- }
35
-
36
- if (specItems.length === 0) return;
37
-
38
- // Check against milestones
39
- const doc = parseMilestones(milestonesPath);
40
- const matched: string[] = [];
41
-
42
- for (const phase of doc.phases) {
43
- for (const item of phase.items) {
44
- const normalized = item.text.toLowerCase().trim();
45
- if (specItems.includes(normalized) && !item.checked) {
46
- matched.push(`"${item.text}" in ${phase.name}`);
47
- }
48
- }
49
- }
50
-
51
- if (matched.length > 0) {
52
- // Removed console.log — milestone matches are informational.
53
- // Use /unipi:milestone-update to sync manually.
54
- }
55
- }
56
-
57
- /**
58
- * After plan completes: check if plan tasks map to milestone items.
59
- * Logs matching items for awareness.
60
- */
61
- export function onPlanComplete(planPath: string): void {
62
- const cwd = process.cwd();
63
- const milestonesPath = path.join(cwd, MILESTONE_DIRS.MILESTONES);
64
-
65
- // Silently skip if no MILESTONES.md
66
- if (!tryRead(milestonesPath)) return;
67
-
68
- const planContent = tryRead(planPath);
69
- if (!planContent) return;
70
-
71
- // Extract task names from plan (### Task N — Name pattern)
72
- const planTasks: string[] = [];
73
- for (const line of planContent.split("\n")) {
74
- const match = line.match(/^###\s+Task\s+\d+\s*[—–-]\s*(.+)$/);
75
- if (match) {
76
- planTasks.push(match[1].trim().toLowerCase());
77
- }
78
- }
79
-
80
- if (planTasks.length === 0) return;
81
-
82
- // Check against milestones
83
- const doc = parseMilestones(milestonesPath);
84
- const matched: string[] = [];
85
-
86
- for (const phase of doc.phases) {
87
- for (const item of phase.items) {
88
- const normalized = item.text.toLowerCase().trim();
89
- // Check if any plan task contains the milestone item text or vice versa
90
- for (const task of planTasks) {
91
- if (task.includes(normalized) || normalized.includes(task)) {
92
- matched.push(`"${item.text}" → plan task`);
93
- break;
94
- }
95
- }
96
- }
97
- }
98
-
99
- if (matched.length > 0) {
100
- // Removed console.log — plan-milestone matches are informational.
101
- }
102
- }
103
-
104
- /**
105
- * After consolidate: reference milestone sync that already happened.
106
- */
107
- export function onConsolidate(): void {
108
- const cwd = process.cwd();
109
- const milestonesPath = path.join(cwd, MILESTONE_DIRS.MILESTONES);
110
-
111
- if (!tryRead(milestonesPath)) return;
112
-
113
- // Removed console.log — milestone auto-sync is silent.
114
- }