buildline 0.8.0 → 0.8.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/package.json +1 -1
- package/src/features.js +2 -1
- package/templates/bin/hook.mjs +17 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buildline",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Install an AI software factory into any project. A spec-driven pipeline for Claude Code (and any AGENTS.md-compatible agent), for developers, designers and product managers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/features.js
CHANGED
|
@@ -214,7 +214,8 @@ export function readActivity(root, limit = 200) {
|
|
|
214
214
|
* Claude Code), a feature whose files changed in the last two minutes counts as worked on.
|
|
215
215
|
*/
|
|
216
216
|
export function liveState(feature, sessions, at = Date.now()) {
|
|
217
|
-
|
|
217
|
+
// A finished feature has nothing live, even if an old session is still tied to it.
|
|
218
|
+
const mine = feature.status === 'done' ? [] : sessions.filter((s) => s.feature === feature.id);
|
|
218
219
|
const working = mine.filter((s) => s.state === 'working');
|
|
219
220
|
const recent = at - new Date(feature.updated || 0) < 2 * 60e3;
|
|
220
221
|
const waiting = mine.find((s) => s.state === 'waiting');
|
package/templates/bin/hook.mjs
CHANGED
|
@@ -113,6 +113,17 @@ function promptDetail(prompt) {
|
|
|
113
113
|
return done ? oneLine(done[1], 90) : oneLine(prompt, 60);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/** A feature.mjs command that changes a feature's state, e.g. "feature.mjs task 3 T1 pass". */
|
|
117
|
+
function actedOn(command) {
|
|
118
|
+
const m = /feature\.mjs\s+(?:move|task|log|block|unblock|approve)\s+(\d{1,4})\b/.exec(String(command || ''));
|
|
119
|
+
return m ? m[1].padStart(3, '0') : null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isDone(id) {
|
|
123
|
+
const dir = fs.existsSync(featuresDir) ? fs.readdirSync(featuresDir).find((d) => d.startsWith(`${id}-`)) : null;
|
|
124
|
+
return dir ? readJson(path.join(featuresDir, dir, 'feature.json'))?.status === 'done' : false;
|
|
125
|
+
}
|
|
126
|
+
|
|
116
127
|
function detail(input) {
|
|
117
128
|
const ti = input.tool_input || {};
|
|
118
129
|
if (ti.file_path) return path.basename(ti.file_path);
|
|
@@ -189,12 +200,15 @@ function heartbeat(input, event) {
|
|
|
189
200
|
const prompt = input.prompt || '';
|
|
190
201
|
const station = /\/buildline-([a-z]+)/.exec(prompt)?.[1];
|
|
191
202
|
const ti = input.tool_input || {};
|
|
192
|
-
// A session stays on the feature it was started for; reading another feature's files doesn't move it
|
|
193
|
-
|
|
203
|
+
// A session stays on the feature it was started for; reading another feature's files doesn't move it,
|
|
204
|
+
// recording progress on one does. It lets go once its feature is done or the conversation is cleared,
|
|
205
|
+
// so a reopened session doesn't keep flagging a finished feature.
|
|
206
|
+
const bound = prev.feature && !isDone(prev.feature) && !(event === 'start' && input.source === 'clear') ? prev.feature : null;
|
|
207
|
+
const feature = featureRef(prompt) || actedOn(ti.command) || bound || featureRef(ti.file_path) || featureRef(ti.command) || featureRef(ti.prompt) || null;
|
|
194
208
|
const next = {
|
|
195
209
|
...prev,
|
|
196
210
|
feature,
|
|
197
|
-
station: station || prev.station || null,
|
|
211
|
+
station: station || (feature === prev.feature ? prev.station : null) || null,
|
|
198
212
|
model: input.model?.id || input.model || prev.model || null,
|
|
199
213
|
...nextState(prev, input, event),
|
|
200
214
|
lastAt: at,
|