@kynetic-ai/spec 0.10.0 → 0.11.0
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/agent-runtime/dispatch.d.ts +31 -0
- package/dist/agent-runtime/dispatch.d.ts.map +1 -1
- package/dist/agent-runtime/dispatch.js +102 -33
- package/dist/agent-runtime/dispatch.js.map +1 -1
- package/dist/agent-runtime/invocation.js +1 -1
- package/dist/agent-runtime/invocation.js.map +1 -1
- package/dist/cli/commands/meta.d.ts.map +1 -1
- package/dist/cli/commands/meta.js +10 -1
- package/dist/cli/commands/meta.js.map +1 -1
- package/dist/daemon/routes/agent-dispatch.ts +9 -2
- package/dist/daemon/routes/items.ts +22 -0
- package/dist/daemon/routes/meta.ts +141 -1
- package/dist/daemon/routes/plans.ts +147 -0
- package/dist/daemon/routes/sessions.ts +180 -0
- package/dist/daemon/routes/tasks.ts +198 -0
- package/dist/daemon/routes/validation.ts +1 -1
- package/dist/daemon/server.ts +22 -1
- package/dist/parser/alignment.d.ts.map +1 -1
- package/dist/parser/alignment.js +4 -2
- package/dist/parser/alignment.js.map +1 -1
- package/dist/schema/meta.d.ts +15 -0
- package/dist/schema/meta.d.ts.map +1 -1
- package/dist/schema/meta.js +1 -0
- package/dist/schema/meta.js.map +1 -1
- package/dist/sessions/store.d.ts +14 -0
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +26 -0
- package/dist/sessions/store.js.map +1 -1
- package/dist/web-ui/_app/immutable/assets/0.BJaYkGW2.css +1 -0
- package/dist/web-ui/_app/immutable/assets/9.SzGLxi4x.css +1 -0
- package/dist/web-ui/_app/immutable/chunks/-lc0BifF.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/62JVKtnb.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/8RBjHMN1.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/B5LJFxqa.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/B5wTVqxm.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/B6VSmczZ.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/B8a0xDxR.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BEOQc37C.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BHtYorjv.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BJ0JX3ea.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BMuCqDX8.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/{i-XnOIX0.js → BP352uRn.js} +1 -1
- package/dist/web-ui/_app/immutable/chunks/BUZujXJ2.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BVA9Exy-.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BWET-efb.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BXkNecpt.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BYzrIfX8.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BkOJ8DkV.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BpuwufMc.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BwMO4RrG.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/BysXJlZb.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/C076q4JN.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/C33JaVbg.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/CGtqifKp.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/CHDZZ7OG.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/CPPfDSei.js +5 -0
- package/dist/web-ui/_app/immutable/chunks/CUir3f4J.js +60 -0
- package/dist/web-ui/_app/immutable/chunks/Cncwi6fQ.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/CrCIbn0C.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/CwELQvbx.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/D3vxvonu.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/D6TVmR9T.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/D7LTux4W.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/{D28BF5MJ.js → D82RulSH.js} +1 -1
- package/dist/web-ui/_app/immutable/chunks/D9QNBZM2.js +2 -0
- package/dist/web-ui/_app/immutable/chunks/DAMmvwn4.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DAh4Wfku.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DAx07bEQ.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DBYE9jOd.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DOno4cA2.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DQA8NZIH.js +2 -0
- package/dist/web-ui/_app/immutable/chunks/DRfPm2bo.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DhQhksaB.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DjG7s6hm.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DjcCz-PU.js +2 -0
- package/dist/web-ui/_app/immutable/chunks/DkltRNvh.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DlaTnPKL.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/DvA-KON-.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/{D1ArdqNb.js → DxCk-KHc.js} +1 -1
- package/dist/web-ui/_app/immutable/chunks/DzO4hlg9.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/{BCkp8Hs8.js → Eo4gF7ih.js} +1 -1
- package/dist/web-ui/_app/immutable/chunks/ExCq5swK.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/T3zZGv51.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/XZumBYeP.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/_ySfNjkF.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/iEtR5cV6.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/k_Qegko0.js +1 -0
- package/dist/web-ui/_app/immutable/chunks/pE6cYWlS.js +1 -0
- package/dist/web-ui/_app/immutable/entry/app.Cgu6uKeS.js +2 -0
- package/dist/web-ui/_app/immutable/entry/start.9XifnLoB.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/0.DISwcKSK.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/1.Cx2Ufqp1.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/10.C3z8ijXL.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/11.DZdIjZmM.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/12.FsIGfAOa.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/13.DZoFwagf.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/14.DaIzDKbQ.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/15.BYyt4XWF.js +2 -0
- package/dist/web-ui/_app/immutable/nodes/16.CQkSqpOe.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/2.Bkf_j2UJ.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/3.kaMCurJG.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/4.BSsFPTHG.js +2 -0
- package/dist/web-ui/_app/immutable/nodes/5.CpPlcCEZ.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/6.BN4FqQmY.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/7.9kBYIZik.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/8.BuijtZ6B.js +1 -0
- package/dist/web-ui/_app/immutable/nodes/9.C-Weba8R.js +1 -0
- package/dist/web-ui/_app/version.json +1 -1
- package/dist/web-ui/index.html +14 -11
- package/package.json +3 -2
- package/plugin/.claude-plugin/marketplace.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/web-ui/_app/immutable/assets/0.BxCxvrZR.css +0 -1
- package/dist/web-ui/_app/immutable/chunks/B-CZR0q8.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/B1IR5Su5.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/B_Cvvtc4.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/BtFaGGII.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/Bu8JVsCH.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/C87u-CNA.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/CrFkBTYp.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/D6RtLpzL.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/D7FHSgx2.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/DBXrsxZQ.js +0 -2
- package/dist/web-ui/_app/immutable/chunks/Da_hHMuA.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/Do6LchSF.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/DoNPtcAw.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/DtUbXRZz.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/DyFPRlLl.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/DzAP8lRM.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/DzVXElzN.js +0 -2
- package/dist/web-ui/_app/immutable/chunks/aoPBFken.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/laxtrUO3.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/q1nIWgqB.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/sTLbk5Nm.js +0 -1
- package/dist/web-ui/_app/immutable/chunks/vwKgQu5P.js +0 -5
- package/dist/web-ui/_app/immutable/entry/app.BCwMcqnT.js +0 -2
- package/dist/web-ui/_app/immutable/entry/start.wKCQH-tt.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/0.CjGVMG74.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/1.B6_AIPan.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/2.q4oCS7Ws.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/3.rTKZf9o2.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/4.DVIDRu1d.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/5.8PtPXIOd.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/6.ZZrTemy_.js +0 -1
- package/dist/web-ui/_app/immutable/nodes/7.IP-gxCxi.js +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plans API Routes
|
|
3
|
+
*
|
|
4
|
+
* REST endpoints for plan operations:
|
|
5
|
+
* - GET /api/plans - list plans with status filter and task progress
|
|
6
|
+
* - GET /api/plans/:ref - get single plan with content
|
|
7
|
+
*
|
|
8
|
+
* AC Coverage:
|
|
9
|
+
* - @ui-plans-view ac-1: Plan list with title, status, dates, linked counts, progress
|
|
10
|
+
* - @ui-plans-view ac-2: Plan detail with content for expand/detail view
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Elysia, t } from 'elysia';
|
|
14
|
+
import {
|
|
15
|
+
initContext,
|
|
16
|
+
loadPlans,
|
|
17
|
+
loadAllTasks,
|
|
18
|
+
findPlanByRef,
|
|
19
|
+
type LoadedPlan,
|
|
20
|
+
} from '../../parser/index.js';
|
|
21
|
+
import type { PlanSummary, PlanDetail } from '@kynetic-ai/shared';
|
|
22
|
+
|
|
23
|
+
interface PlansRouteOptions {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build a task status lookup map from loaded tasks.
|
|
27
|
+
*/
|
|
28
|
+
function buildTaskStatusMap(tasks: Array<{ _ulid: string; slugs: string[]; status: string }>) {
|
|
29
|
+
const tasksByRef = new Map<string, { status: string }>();
|
|
30
|
+
for (const task of tasks) {
|
|
31
|
+
tasksByRef.set(task._ulid, { status: task.status });
|
|
32
|
+
for (const slug of task.slugs) {
|
|
33
|
+
tasksByRef.set(slug, { status: task.status });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return tasksByRef;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compute task progress for a plan's derived tasks.
|
|
41
|
+
*/
|
|
42
|
+
function computeTaskProgress(
|
|
43
|
+
derivedTasks: string[],
|
|
44
|
+
tasksByRef: Map<string, { status: string }>
|
|
45
|
+
) {
|
|
46
|
+
const progress = { total: 0, completed: 0, in_progress: 0, pending: 0, blocked: 0 };
|
|
47
|
+
for (const ref of derivedTasks) {
|
|
48
|
+
const cleanRef = ref.startsWith('@') ? ref.slice(1) : ref;
|
|
49
|
+
const task = tasksByRef.get(cleanRef);
|
|
50
|
+
if (task) {
|
|
51
|
+
progress.total++;
|
|
52
|
+
if (task.status === 'completed') progress.completed++;
|
|
53
|
+
else if (task.status === 'in_progress' || task.status === 'pending_review' || task.status === 'needs_work') progress.in_progress++;
|
|
54
|
+
else if (task.status === 'blocked') progress.blocked++;
|
|
55
|
+
else progress.pending++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return progress;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Map a loaded plan to a PlanSummary.
|
|
63
|
+
*/
|
|
64
|
+
function toPlanSummary(
|
|
65
|
+
plan: LoadedPlan,
|
|
66
|
+
tasksByRef: Map<string, { status: string }>
|
|
67
|
+
): PlanSummary {
|
|
68
|
+
return {
|
|
69
|
+
_ulid: plan._ulid,
|
|
70
|
+
slugs: plan.slugs,
|
|
71
|
+
title: plan.title,
|
|
72
|
+
status: plan.status,
|
|
73
|
+
created_at: plan.created_at,
|
|
74
|
+
approved_at: plan.approved_at ?? undefined,
|
|
75
|
+
completed_at: plan.completed_at ?? undefined,
|
|
76
|
+
derived_specs: plan.derived_specs,
|
|
77
|
+
derived_tasks: plan.derived_tasks,
|
|
78
|
+
spec_count: plan.derived_specs.length,
|
|
79
|
+
task_count: plan.derived_tasks.length,
|
|
80
|
+
task_progress: computeTaskProgress(plan.derived_tasks, tasksByRef),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createPlansRoutes(options: PlansRouteOptions = {}) {
|
|
85
|
+
return new Elysia({ prefix: '/api/plans' })
|
|
86
|
+
// AC: @ui-plans-view ac-1 - List plans with progress
|
|
87
|
+
.get(
|
|
88
|
+
'/',
|
|
89
|
+
async ({ query, projectContext }) => {
|
|
90
|
+
const ctx = await initContext(projectContext.path);
|
|
91
|
+
const plans = await loadPlans(ctx);
|
|
92
|
+
const tasks = await loadAllTasks(ctx);
|
|
93
|
+
const tasksByRef = buildTaskStatusMap(tasks);
|
|
94
|
+
|
|
95
|
+
// Apply status filter
|
|
96
|
+
let filtered: LoadedPlan[] = plans;
|
|
97
|
+
if (query.status) {
|
|
98
|
+
const statusFilters = Array.isArray(query.status) ? query.status : [query.status];
|
|
99
|
+
filtered = filtered.filter((plan) => statusFilters.includes(plan.status));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sort by created_at descending (newest first)
|
|
103
|
+
const sorted = [...filtered].sort(
|
|
104
|
+
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// AC: @ui-plans-view ac-1 - Compute progress for each plan
|
|
108
|
+
const items: PlanSummary[] = sorted.map((plan) => toPlanSummary(plan, tasksByRef));
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
items,
|
|
112
|
+
total: items.length,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
query: t.Object({
|
|
117
|
+
status: t.Optional(t.Union([t.String(), t.Array(t.String())])),
|
|
118
|
+
}),
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
// AC: @ui-plans-view ac-2 - Get single plan with content (lazy-loaded by UI on expand)
|
|
122
|
+
.get(
|
|
123
|
+
'/:ref',
|
|
124
|
+
async ({ params, error: errorResponse, projectContext }) => {
|
|
125
|
+
const ctx = await initContext(projectContext.path);
|
|
126
|
+
const plan = await findPlanByRef(ctx, params.ref);
|
|
127
|
+
|
|
128
|
+
if (!plan) {
|
|
129
|
+
return errorResponse(404, {
|
|
130
|
+
error: 'not_found',
|
|
131
|
+
message: `Plan reference "${params.ref}" not found`,
|
|
132
|
+
suggestion: 'Use kspec plan list to find valid plan references',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const tasks = await loadAllTasks(ctx);
|
|
137
|
+
const tasksByRef = buildTaskStatusMap(tasks);
|
|
138
|
+
|
|
139
|
+
const detail: PlanDetail = {
|
|
140
|
+
...toPlanSummary(plan, tasksByRef),
|
|
141
|
+
content: plan.content,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return detail;
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session API Routes
|
|
3
|
+
*
|
|
4
|
+
* REST endpoints for session data:
|
|
5
|
+
* - GET /api/sessions - list sessions with summaries
|
|
6
|
+
* - GET /api/sessions/:id - get session metadata and detail
|
|
7
|
+
* - GET /api/sessions/:id/events - get session events from events.jsonl
|
|
8
|
+
*
|
|
9
|
+
* AC Coverage:
|
|
10
|
+
* - @ui-session-stream ac-1: Session events as structured blocks
|
|
11
|
+
* - @ui-session-stream ac-4: Session metadata, spec context, budget for context panel
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Elysia, t } from 'elysia';
|
|
15
|
+
import {
|
|
16
|
+
getSession,
|
|
17
|
+
listSessions,
|
|
18
|
+
readEvents,
|
|
19
|
+
deduplicatePhasedToolCalls,
|
|
20
|
+
getSessionLogSummary,
|
|
21
|
+
getAllSessionLogSummaries,
|
|
22
|
+
resolveSessionId,
|
|
23
|
+
getBudget,
|
|
24
|
+
} from '../../sessions/store.js';
|
|
25
|
+
import {
|
|
26
|
+
initContext,
|
|
27
|
+
loadAllTasks,
|
|
28
|
+
loadAllItems,
|
|
29
|
+
ReferenceIndex,
|
|
30
|
+
} from '../../parser/index.js';
|
|
31
|
+
|
|
32
|
+
export function createSessionRoutes() {
|
|
33
|
+
return new Elysia({ prefix: '/api/sessions' })
|
|
34
|
+
|
|
35
|
+
// List all sessions with summaries
|
|
36
|
+
.get('/', async ({ projectContext }) => {
|
|
37
|
+
const ctx = await initContext(projectContext.path);
|
|
38
|
+
const summaries = await getAllSessionLogSummaries(ctx.specDir);
|
|
39
|
+
|
|
40
|
+
// Sort by started_at descending (most recent first)
|
|
41
|
+
summaries.sort((a, b) =>
|
|
42
|
+
new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
items: summaries,
|
|
47
|
+
total: summaries.length,
|
|
48
|
+
};
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Get single session metadata
|
|
52
|
+
// AC: @ui-session-stream ac-4 — Includes spec context, budget, and task info
|
|
53
|
+
.get('/:id', async ({ params, error: errorResponse, projectContext }) => {
|
|
54
|
+
const ctx = await initContext(projectContext.path);
|
|
55
|
+
|
|
56
|
+
// Resolve session ID (supports prefix matching)
|
|
57
|
+
const resolution = await resolveSessionId(ctx.specDir, params.id);
|
|
58
|
+
if (!resolution.ok) {
|
|
59
|
+
if (resolution.error === 'ambiguous') {
|
|
60
|
+
return errorResponse(400, {
|
|
61
|
+
error: 'ambiguous_id',
|
|
62
|
+
message: `Ambiguous session ID: ${params.id} matches ${resolution.matches.length} sessions`,
|
|
63
|
+
suggestion: 'Provide a longer prefix to uniquely identify the session',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return errorResponse(404, {
|
|
67
|
+
error: 'not_found',
|
|
68
|
+
message: `Session not found: ${params.id}`,
|
|
69
|
+
suggestion: 'Use GET /api/sessions to list available sessions',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const detail = await getSessionLogSummary(ctx.specDir, resolution.id);
|
|
74
|
+
if (!detail) {
|
|
75
|
+
return errorResponse(404, {
|
|
76
|
+
error: 'not_found',
|
|
77
|
+
message: `Session not found: ${params.id}`,
|
|
78
|
+
suggestion: 'Use GET /api/sessions to list available sessions',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const metadata = await getSession(ctx.specDir, resolution.id);
|
|
83
|
+
|
|
84
|
+
// AC: @ui-session-stream ac-4 — Resolve spec context from task's spec_ref
|
|
85
|
+
let spec_context: {
|
|
86
|
+
spec_ref: string;
|
|
87
|
+
title: string;
|
|
88
|
+
acceptance_criteria: Array<{ id: string; description: string }>;
|
|
89
|
+
} | null = null;
|
|
90
|
+
|
|
91
|
+
if (metadata?.task_id) {
|
|
92
|
+
try {
|
|
93
|
+
const tasks = await loadAllTasks(ctx);
|
|
94
|
+
const items = await loadAllItems(ctx);
|
|
95
|
+
const index = new ReferenceIndex(tasks, items);
|
|
96
|
+
const taskResult = index.resolve(metadata.task_id);
|
|
97
|
+
if (taskResult.ok) {
|
|
98
|
+
const task = taskResult.item as { spec_ref?: string };
|
|
99
|
+
if (task.spec_ref) {
|
|
100
|
+
const specResult = index.resolve(task.spec_ref);
|
|
101
|
+
if (specResult.ok) {
|
|
102
|
+
const specItem = specResult.item as {
|
|
103
|
+
title: string;
|
|
104
|
+
acceptance_criteria?: Array<{ description?: string; given?: string }>;
|
|
105
|
+
};
|
|
106
|
+
spec_context = {
|
|
107
|
+
spec_ref: task.spec_ref,
|
|
108
|
+
title: specItem.title,
|
|
109
|
+
acceptance_criteria: (specItem.acceptance_criteria ?? []).map((ac, i) => ({
|
|
110
|
+
id: `ac-${i + 1}`,
|
|
111
|
+
description: ac.description ?? ac.given ?? '',
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// Non-critical — spec context is optional
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// AC: @ui-session-stream ac-4 — Include budget info
|
|
123
|
+
let budget: { max_per_cycle: number; started_this_cycle: number } | null = null;
|
|
124
|
+
try {
|
|
125
|
+
budget = await getBudget(ctx.specDir, resolution.id);
|
|
126
|
+
} catch {
|
|
127
|
+
// No budget configured — that's fine
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
...detail,
|
|
132
|
+
task_id: metadata?.task_id,
|
|
133
|
+
agent_id: metadata?.agent_id,
|
|
134
|
+
trigger: metadata?.trigger ?? 'legacy',
|
|
135
|
+
spec_context,
|
|
136
|
+
budget,
|
|
137
|
+
};
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Get session events
|
|
141
|
+
.get('/:id/events', async ({ params, query, error: errorResponse, projectContext }) => {
|
|
142
|
+
const ctx = await initContext(projectContext.path);
|
|
143
|
+
|
|
144
|
+
const resolution = await resolveSessionId(ctx.specDir, params.id);
|
|
145
|
+
if (!resolution.ok) {
|
|
146
|
+
if (resolution.error === 'ambiguous') {
|
|
147
|
+
return errorResponse(400, {
|
|
148
|
+
error: 'ambiguous_id',
|
|
149
|
+
message: `Ambiguous session ID: ${params.id} matches ${resolution.matches.length} sessions`,
|
|
150
|
+
suggestion: 'Provide a longer prefix to uniquely identify the session',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return errorResponse(404, {
|
|
154
|
+
error: 'not_found',
|
|
155
|
+
message: `Session not found: ${params.id}`,
|
|
156
|
+
suggestion: 'Use GET /api/sessions to list available sessions',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let events = await readEvents(ctx.specDir, resolution.id);
|
|
161
|
+
|
|
162
|
+
// Deduplicate phased tool calls
|
|
163
|
+
events = deduplicatePhasedToolCalls(events);
|
|
164
|
+
|
|
165
|
+
// Filter by since_seq if provided (for incremental loading)
|
|
166
|
+
const sinceSeq = query.since_seq !== undefined ? parseInt(query.since_seq, 10) : undefined;
|
|
167
|
+
if (sinceSeq !== undefined && !isNaN(sinceSeq)) {
|
|
168
|
+
events = events.filter(e => e.seq > sinceSeq);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
events,
|
|
173
|
+
total: events.length,
|
|
174
|
+
};
|
|
175
|
+
}, {
|
|
176
|
+
query: t.Object({
|
|
177
|
+
since_seq: t.Optional(t.String()),
|
|
178
|
+
}),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* - ac-5: GET /api/tasks/:ref resolves via ReferenceIndex
|
|
15
15
|
* - ac-6: POST /api/tasks/:ref/start transitions state
|
|
16
16
|
* - ac-7: POST /api/tasks/:ref/note appends note
|
|
17
|
+
* - @ui-task-board ac-6: POST /api/tasks/:ref/submit transitions to pending_review
|
|
18
|
+
* - @ui-task-board ac-6: POST /api/tasks/:ref/complete transitions to completed
|
|
19
|
+
* - @ui-task-board ac-6: POST /api/tasks/:ref/block transitions to blocked
|
|
17
20
|
*/
|
|
18
21
|
|
|
19
22
|
import { Elysia, t } from 'elysia';
|
|
@@ -21,6 +24,7 @@ import {
|
|
|
21
24
|
initContext,
|
|
22
25
|
loadAllTasks,
|
|
23
26
|
loadAllItems,
|
|
27
|
+
loadPlans,
|
|
24
28
|
ReferenceIndex,
|
|
25
29
|
createNote,
|
|
26
30
|
saveTask,
|
|
@@ -72,6 +76,31 @@ export function createTasksRoutes(options: TasksRouteOptions) {
|
|
|
72
76
|
);
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
// Automation filter — filter by automation eligibility status
|
|
80
|
+
if (query.automation) {
|
|
81
|
+
filtered = filtered.filter((task) => task.automation === query.automation);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Plan filter — show only tasks derived from a given plan
|
|
85
|
+
if (query.plan) {
|
|
86
|
+
const plans = await loadPlans(ctx);
|
|
87
|
+
const plan = plans.find(
|
|
88
|
+
(p) => p._ulid === query.plan || p.slugs.includes(query.plan!)
|
|
89
|
+
);
|
|
90
|
+
if (plan) {
|
|
91
|
+
const derivedRefs = new Set(
|
|
92
|
+
plan.derived_tasks.map((r) => (r.startsWith('@') ? r.slice(1) : r))
|
|
93
|
+
);
|
|
94
|
+
filtered = filtered.filter(
|
|
95
|
+
(task) =>
|
|
96
|
+
derivedRefs.has(task._ulid) ||
|
|
97
|
+
task.slugs.some((s) => derivedRefs.has(s))
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
filtered = [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
75
104
|
// AC: @api-contract ac-4 - Pagination
|
|
76
105
|
const total = filtered.length;
|
|
77
106
|
const offset = Number(query.offset) || 0;
|
|
@@ -85,12 +114,14 @@ export function createTasksRoutes(options: TasksRouteOptions) {
|
|
|
85
114
|
_ulid: task._ulid,
|
|
86
115
|
slugs: task.slugs,
|
|
87
116
|
title: task.title,
|
|
117
|
+
type: task.type || 'task',
|
|
88
118
|
status: task.status,
|
|
89
119
|
priority: task.priority,
|
|
90
120
|
spec_ref: task.spec_ref,
|
|
91
121
|
meta_ref: task.meta_ref,
|
|
92
122
|
tags: task.tags,
|
|
93
123
|
depends_on: task.depends_on || [],
|
|
124
|
+
automation: task.automation,
|
|
94
125
|
notes_count: task.notes?.length || 0,
|
|
95
126
|
todos_count: task.todos?.length || 0,
|
|
96
127
|
started_at: task.started_at,
|
|
@@ -111,6 +142,8 @@ export function createTasksRoutes(options: TasksRouteOptions) {
|
|
|
111
142
|
status: t.Optional(t.Union([t.String(), t.Array(t.String())])),
|
|
112
143
|
type: t.Optional(t.Union([t.String(), t.Array(t.String())])),
|
|
113
144
|
tag: t.Optional(t.Union([t.String(), t.Array(t.String())])),
|
|
145
|
+
automation: t.Optional(t.String()),
|
|
146
|
+
plan: t.Optional(t.String()),
|
|
114
147
|
limit: t.Optional(t.String()),
|
|
115
148
|
offset: t.Optional(t.String()),
|
|
116
149
|
}),
|
|
@@ -150,19 +183,31 @@ export function createTasksRoutes(options: TasksRouteOptions) {
|
|
|
150
183
|
}
|
|
151
184
|
|
|
152
185
|
// AC: @api-contract ac-5 - Return full task with notes, todos, dependencies
|
|
186
|
+
// AC: @ui-task-board ac-3 - Include type, description, blocked_by, vcs_refs, plan_ref, session_ref
|
|
153
187
|
return {
|
|
154
188
|
_ulid: task._ulid,
|
|
155
189
|
slugs: task.slugs,
|
|
156
190
|
title: task.title,
|
|
191
|
+
type: task.type || 'task',
|
|
157
192
|
status: task.status,
|
|
158
193
|
priority: task.priority,
|
|
159
194
|
spec_ref: task.spec_ref,
|
|
160
195
|
meta_ref: task.meta_ref,
|
|
161
196
|
tags: task.tags,
|
|
162
197
|
description: task.description,
|
|
198
|
+
derivation: task.derivation,
|
|
163
199
|
depends_on: task.depends_on,
|
|
200
|
+
blocked_by: task.blocked_by || [],
|
|
201
|
+
context: task.context || [],
|
|
202
|
+
vcs_refs: (task.vcs_refs || []).map((v) =>
|
|
203
|
+
typeof v === 'string' ? v : v.type ? `${v.type}:${v.ref}` : v.ref
|
|
204
|
+
),
|
|
205
|
+
plan_ref: task.plan_ref,
|
|
206
|
+
session_ref: task.session_id,
|
|
164
207
|
notes: task.notes,
|
|
208
|
+
notes_count: task.notes?.length || 0,
|
|
165
209
|
todos: task.todos,
|
|
210
|
+
todos_count: task.todos?.length || 0,
|
|
166
211
|
started_at: task.started_at,
|
|
167
212
|
completed_at: task.completed_at,
|
|
168
213
|
cancelled_at: task.cancelled_at,
|
|
@@ -323,5 +368,158 @@ export function createTasksRoutes(options: TasksRouteOptions) {
|
|
|
323
368
|
content: t.String(),
|
|
324
369
|
}),
|
|
325
370
|
}
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
// AC: @ui-task-board ac-6 - Submit task for review
|
|
374
|
+
.post(
|
|
375
|
+
'/:ref/submit',
|
|
376
|
+
async ({ params, error: errorResponse, projectContext }) => {
|
|
377
|
+
const ctx = await initContext(projectContext.path);
|
|
378
|
+
const tasks = await loadAllTasks(ctx);
|
|
379
|
+
const items = await loadAllItems(ctx);
|
|
380
|
+
const index = new ReferenceIndex(tasks, items);
|
|
381
|
+
|
|
382
|
+
const result = index.resolve(params.ref);
|
|
383
|
+
if (!result.ok) {
|
|
384
|
+
return errorResponse(404, {
|
|
385
|
+
error: 'not_found',
|
|
386
|
+
message: `Task reference "${params.ref}" not found`,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const task = tasks.find((t) => t._ulid === result.ulid);
|
|
391
|
+
if (!task) {
|
|
392
|
+
return errorResponse(404, {
|
|
393
|
+
error: 'not_found',
|
|
394
|
+
message: `Reference "${params.ref}" is not a task`,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (task.status !== 'in_progress' && task.status !== 'needs_work') {
|
|
399
|
+
return errorResponse(409, {
|
|
400
|
+
error: 'invalid_transition',
|
|
401
|
+
message: `Cannot submit task with status "${task.status}". Must be in_progress or needs_work.`,
|
|
402
|
+
current: task.status,
|
|
403
|
+
valid_transitions: ['pending_review'],
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const updatedTask: LoadedTask = { ...task, status: 'pending_review' };
|
|
408
|
+
await saveTask(ctx, updatedTask);
|
|
409
|
+
await syncSpecImplementationStatus(ctx, updatedTask, tasks, items, index);
|
|
410
|
+
await commitIfShadow(ctx.shadow, `task: submit ${params.ref}`);
|
|
411
|
+
|
|
412
|
+
pubsub.broadcast('tasks:updates', 'task_updated', {
|
|
413
|
+
ref: params.ref,
|
|
414
|
+
ulid: task._ulid,
|
|
415
|
+
action: 'submit',
|
|
416
|
+
status: 'pending_review',
|
|
417
|
+
}, projectContext.path);
|
|
418
|
+
|
|
419
|
+
return updatedTask;
|
|
420
|
+
},
|
|
421
|
+
{ params: t.Object({ ref: t.String() }) }
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
// AC: @ui-task-board ac-6 - Complete task
|
|
425
|
+
.post(
|
|
426
|
+
'/:ref/complete',
|
|
427
|
+
async ({ params, body, error: errorResponse, projectContext }) => {
|
|
428
|
+
const ctx = await initContext(projectContext.path);
|
|
429
|
+
const tasks = await loadAllTasks(ctx);
|
|
430
|
+
const items = await loadAllItems(ctx);
|
|
431
|
+
const index = new ReferenceIndex(tasks, items);
|
|
432
|
+
|
|
433
|
+
const result = index.resolve(params.ref);
|
|
434
|
+
if (!result.ok) {
|
|
435
|
+
return errorResponse(404, {
|
|
436
|
+
error: 'not_found',
|
|
437
|
+
message: `Task reference "${params.ref}" not found`,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const task = tasks.find((t) => t._ulid === result.ulid);
|
|
442
|
+
if (!task) {
|
|
443
|
+
return errorResponse(404, {
|
|
444
|
+
error: 'not_found',
|
|
445
|
+
message: `Reference "${params.ref}" is not a task`,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const updatedTask: LoadedTask = {
|
|
450
|
+
...task,
|
|
451
|
+
status: 'completed',
|
|
452
|
+
completed_at: new Date().toISOString(),
|
|
453
|
+
closed_reason: body.reason,
|
|
454
|
+
};
|
|
455
|
+
await saveTask(ctx, updatedTask);
|
|
456
|
+
await syncSpecImplementationStatus(ctx, updatedTask, tasks, items, index);
|
|
457
|
+
await commitIfShadow(ctx.shadow, `task: complete ${params.ref}`);
|
|
458
|
+
|
|
459
|
+
pubsub.broadcast('tasks:updates', 'task_updated', {
|
|
460
|
+
ref: params.ref,
|
|
461
|
+
ulid: task._ulid,
|
|
462
|
+
action: 'complete',
|
|
463
|
+
status: 'completed',
|
|
464
|
+
}, projectContext.path);
|
|
465
|
+
|
|
466
|
+
return updatedTask;
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
params: t.Object({ ref: t.String() }),
|
|
470
|
+
body: t.Object({ reason: t.String() }),
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
// AC: @ui-task-board ac-6 - Block task
|
|
475
|
+
.post(
|
|
476
|
+
'/:ref/block',
|
|
477
|
+
async ({ params, body, error: errorResponse, projectContext }) => {
|
|
478
|
+
const ctx = await initContext(projectContext.path);
|
|
479
|
+
const tasks = await loadAllTasks(ctx);
|
|
480
|
+
const items = await loadAllItems(ctx);
|
|
481
|
+
const index = new ReferenceIndex(tasks, items);
|
|
482
|
+
|
|
483
|
+
const result = index.resolve(params.ref);
|
|
484
|
+
if (!result.ok) {
|
|
485
|
+
return errorResponse(404, {
|
|
486
|
+
error: 'not_found',
|
|
487
|
+
message: `Task reference "${params.ref}" not found`,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const task = tasks.find((t) => t._ulid === result.ulid);
|
|
492
|
+
if (!task) {
|
|
493
|
+
return errorResponse(404, {
|
|
494
|
+
error: 'not_found',
|
|
495
|
+
message: `Reference "${params.ref}" is not a task`,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const author = getAuthor(ctx.config?.identity?.author);
|
|
500
|
+
const note = createNote(`Blocked: ${body.reason}`, author);
|
|
501
|
+
|
|
502
|
+
const updatedTask: LoadedTask = {
|
|
503
|
+
...task,
|
|
504
|
+
status: 'blocked',
|
|
505
|
+
notes: [...(task.notes || []), note],
|
|
506
|
+
};
|
|
507
|
+
await saveTask(ctx, updatedTask);
|
|
508
|
+
await syncSpecImplementationStatus(ctx, updatedTask, tasks, items, index);
|
|
509
|
+
await commitIfShadow(ctx.shadow, `task: block ${params.ref}`);
|
|
510
|
+
|
|
511
|
+
pubsub.broadcast('tasks:updates', 'task_updated', {
|
|
512
|
+
ref: params.ref,
|
|
513
|
+
ulid: task._ulid,
|
|
514
|
+
action: 'block',
|
|
515
|
+
status: 'blocked',
|
|
516
|
+
}, projectContext.path);
|
|
517
|
+
|
|
518
|
+
return updatedTask;
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
params: t.Object({ ref: t.String() }),
|
|
522
|
+
body: t.Object({ reason: t.String() }),
|
|
523
|
+
}
|
|
326
524
|
);
|
|
327
525
|
}
|
|
@@ -218,7 +218,7 @@ export function createValidationRoutes(options: ValidationRouteOptions = {}) {
|
|
|
218
218
|
refWarnings: result.refWarnings,
|
|
219
219
|
orphans: result.orphans,
|
|
220
220
|
completenessWarnings: result.completenessWarnings,
|
|
221
|
-
traitCycles: result.
|
|
221
|
+
traitCycles: result.traitCycleErrors,
|
|
222
222
|
};
|
|
223
223
|
})
|
|
224
224
|
|
package/dist/daemon/server.ts
CHANGED
|
@@ -27,6 +27,8 @@ import { createValidationRoutes } from './routes/validation';
|
|
|
27
27
|
import { createProjectsRoutes } from './routes/projects';
|
|
28
28
|
import { createTriageRoutes } from './routes/triage';
|
|
29
29
|
import { createAgentDispatchRoutes, getDispatchEngine, stopAllEngines } from './routes/agent-dispatch';
|
|
30
|
+
import { createSessionRoutes } from './routes/sessions';
|
|
31
|
+
import { createPlansRoutes } from './routes/plans';
|
|
30
32
|
import { join } from 'path';
|
|
31
33
|
|
|
32
34
|
export interface ServerOptions {
|
|
@@ -240,6 +242,12 @@ export async function createServer(options: ServerOptions) {
|
|
|
240
242
|
// AC: @multi-directory-daemon ac-28, ac-29, ac-30 - Projects management endpoints
|
|
241
243
|
.use(createProjectsRoutes({ projectManager: projectContextManager }))
|
|
242
244
|
|
|
245
|
+
// AC: @ui-session-stream ac-1, ac-4 - Session data endpoints
|
|
246
|
+
.use(createSessionRoutes())
|
|
247
|
+
|
|
248
|
+
// AC: @ui-plans-view ac-1 - Plans data endpoints
|
|
249
|
+
.use(createPlansRoutes())
|
|
250
|
+
|
|
243
251
|
// AC: @agent-dispatch-engine ac-4 - Agent dispatch API endpoints
|
|
244
252
|
// AC: @daemon-agent-dispatch ac-3, ac-4 - Pass pubsub for WebSocket broadcast on invocation events
|
|
245
253
|
.use(createAgentDispatchRoutes({ pubsub: pubsubManager }))
|
|
@@ -355,7 +363,20 @@ export async function createServer(options: ServerOptions) {
|
|
|
355
363
|
|
|
356
364
|
// SPA fallback routes for client-side routing
|
|
357
365
|
// These catch paths like /tasks, /items, /inbox that don't have static files
|
|
358
|
-
const spaRoutes = [
|
|
366
|
+
const spaRoutes = [
|
|
367
|
+
'/tasks', '/tasks/*',
|
|
368
|
+
'/items', '/items/*',
|
|
369
|
+
'/inbox',
|
|
370
|
+
'/observations',
|
|
371
|
+
'/triage',
|
|
372
|
+
'/validate',
|
|
373
|
+
'/sessions', '/sessions/*',
|
|
374
|
+
'/agents',
|
|
375
|
+
'/specs',
|
|
376
|
+
'/workflows',
|
|
377
|
+
'/plans',
|
|
378
|
+
'/settings',
|
|
379
|
+
];
|
|
359
380
|
for (const route of spaRoutes) {
|
|
360
381
|
app.get(route, () => Bun.file(indexHtmlPath));
|
|
361
382
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alignment.d.ts","sourceRoot":"","sources":["../../src/parser/alignment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAO1E;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,oBAAoB,CAAC;IACpC,cAAc,EAAE,oBAAoB,CAAC;IACrC,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,eAAe,GAAG,iBAAiB,GAAG,sBAAsB,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;;GAGG;AACH,qBAAa,cAAc;IACzB,+CAA+C;IAC/C,OAAO,CAAC,WAAW,CAA+B;IAElD,0CAA0C;IAC1C,OAAO,CAAC,UAAU,CAA6B;IAE/C,6BAA6B;IAC7B,OAAO,CAAC,SAAS,CAAqC;IAEtD,wBAAwB;IACxB,OAAO,CAAC,KAAK,CAAiC;IAE9C;;OAEG;gBACS,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE;IAkBxD;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAa1C;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE;IAO/C;;OAEG;IACH,cAAc,CACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,cAAc,GAAG,SAAS;IAW7B;;OAEG;IACH,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB;
|
|
1
|
+
{"version":3,"file":"alignment.d.ts","sourceRoot":"","sources":["../../src/parser/alignment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAO1E;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,oBAAoB,CAAC;IACpC,cAAc,EAAE,oBAAoB,CAAC;IACrC,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,eAAe,GAAG,iBAAiB,GAAG,sBAAsB,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;;GAGG;AACH,qBAAa,cAAc;IACzB,+CAA+C;IAC/C,OAAO,CAAC,WAAW,CAA+B;IAElD,0CAA0C;IAC1C,OAAO,CAAC,UAAU,CAA6B;IAE/C,6BAA6B;IAC7B,OAAO,CAAC,SAAS,CAAqC;IAEtD,wBAAwB;IACxB,OAAO,CAAC,KAAK,CAAiC;IAE9C;;OAEG;gBACS,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE;IAkBxD;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAa1C;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE;IAO/C;;OAEG;IACH,cAAc,CACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,cAAc,GAAG,SAAS;IAW7B;;OAEG;IACH,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB;IAiC/D;;OAEG;IACH,wBAAwB,CACtB,QAAQ,EAAE,MAAM,GACf,yBAAyB,GAAG,SAAS;IA4BxC;;OAEG;IACH,qBAAqB,IAAI,gBAAgB,EAAE;IAsD3C;;OAEG;IACH,6BAA6B,IAAI,yBAAyB,EAAE;IAW5D;;OAEG;IACH,QAAQ,IAAI;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACvB;CAyBF;AAMD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,oBAAoB,CAAC;IACrC,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,UAAU,EAAE,EACtB,QAAQ,EAAE,cAAc,EAAE,EAC1B,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA6C5B"}
|
package/dist/parser/alignment.js
CHANGED
|
@@ -92,13 +92,15 @@ export class AlignmentIndex {
|
|
|
92
92
|
return "not_started";
|
|
93
93
|
}
|
|
94
94
|
// Check task statuses
|
|
95
|
-
const
|
|
95
|
+
const hasActiveWork = tasks.some((t) => t.status === "in_progress" ||
|
|
96
|
+
t.status === "pending_review" ||
|
|
97
|
+
t.status === "needs_work");
|
|
96
98
|
const allCompleted = tasks.every((t) => t.status === "completed");
|
|
97
99
|
const someCompleted = tasks.some((t) => t.status === "completed");
|
|
98
100
|
if (allCompleted) {
|
|
99
101
|
return "implemented";
|
|
100
102
|
}
|
|
101
|
-
if (
|
|
103
|
+
if (hasActiveWork || someCompleted) {
|
|
102
104
|
return "in_progress";
|
|
103
105
|
}
|
|
104
106
|
return "not_started";
|