@pi-unipi/milestone 2.4.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.
- package/hooks.ts +9 -15
- package/package.json +2 -2
- 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
|
-
|
|
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.4.
|
|
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.4.
|
|
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
|
-
}
|