@shepai/cli 1.142.1-pr455.16bd4d2 → 1.142.1-pr456.4815c0e
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/src/presentation/cli/commands/feat/ls.command.d.ts +2 -44
- package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/feat/ls.command.js +82 -182
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/server-reference-manifest.json +3 -3
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +3 -3
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2138fa7e._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__29580090._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__29580090._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__3ef34e4c._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c094882b._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c094882b._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__d48c5b11._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__d48c5b11._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__dac5dbf1._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__dac5dbf1._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fae8b355._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fae8b355._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0c5f56e3._.js +2 -2
- package/web/.next/server/chunks/ssr/_0c5f56e3._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_1b719e7f._.js +1 -1
- package/web/.next/server/chunks/ssr/_1b719e7f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_c904beb8._.js → _25bf0e1e._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_c904beb8._.js.map → _25bf0e1e._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_37e8548b._.js +1 -1
- package/web/.next/server/chunks/ssr/_37e8548b._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_55d763e2._.js +1 -1
- package/web/.next/server/chunks/ssr/_55d763e2._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_6256a985._.js +1 -1
- package/web/.next/server/chunks/ssr/_6256a985._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_64bdfc6f._.js +2 -2
- package/web/.next/server/chunks/ssr/_64bdfc6f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_7dca1882._.js +1 -1
- package/web/.next/server/chunks/ssr/_7dca1882._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_c2b8e868._.js → _a0581dfe._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_c2b8e868._.js.map → _a0581dfe._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_a9f57758._.js +1 -1
- package/web/.next/server/chunks/ssr/_b71645b4._.js +1 -1
- package/web/.next/server/chunks/ssr/_b71645b4._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_d8575088._.js +1 -1
- package/web/.next/server/chunks/ssr/_d8575088._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_f39a1adb._.js +1 -1
- package/web/.next/server/chunks/ssr/_f39a1adb._.js.map +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{src_presentation_web_8207e252._.js → src_presentation_web_0a969330._.js} +2 -2
- package/web/.next/server/chunks/ssr/{src_presentation_web_8207e252._.js.map → src_presentation_web_0a969330._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_open-ide_ts_baaca5d5._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +44 -44
- package/web/.next/static/chunks/{12e3ff09fc17f052.js → 150be18b2e22579b.js} +1 -1
- package/web/.next/static/chunks/{a7f51b1734e55a52.js → 1c4615ffc1bd17e7.js} +1 -1
- package/web/.next/static/chunks/{80b1513ba8f15d69.js → 37ab5e2cc4fee798.js} +1 -1
- package/web/.next/static/chunks/{65085b062e182aa4.js → 429214b117fdfa73.js} +1 -1
- package/web/.next/static/chunks/{0a6be91d8d53e3fd.js → 572d68596475a241.js} +1 -1
- package/web/.next/static/chunks/{9c2e38efcd95fe72.js → 72233f413937cfa4.js} +2 -2
- package/web/.next/static/chunks/{3aa226c0e32ce07c.js → 7c40090bba456bb8.js} +1 -1
- package/web/.next/static/chunks/7d85079a5183cb2f.js +1 -0
- package/web/.next/static/chunks/{1330778ac8908e69.js → 7e4d968a89424069.js} +1 -1
- package/web/.next/static/chunks/{9a16838aae04976b.js → 8ac2894f50f28083.js} +1 -1
- package/web/.next/static/chunks/{b0a40414ed362492.js → 9141dfc732c8bdf8.js} +1 -1
- package/web/.next/static/chunks/{6eb17d2ce8a9aa4f.js → e5d4f58b743c4afc.js} +1 -1
- package/web/.next/static/chunks/b88100de43de46a4.js +0 -1
- /package/web/.next/static/{rDu9PslAKyILjx18WKTCI → xdAS5ZV-0s-dRFsBntoHN}/_buildManifest.js +0 -0
- /package/web/.next/static/{rDu9PslAKyILjx18WKTCI → xdAS5ZV-0s-dRFsBntoHN}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{rDu9PslAKyILjx18WKTCI → xdAS5ZV-0s-dRFsBntoHN}/_ssgManifest.js +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Feature List Command
|
|
3
3
|
*
|
|
4
|
-
* Lists features in a
|
|
5
|
-
*
|
|
4
|
+
* Lists features in a formatted list with optional filtering.
|
|
5
|
+
* Derives real-time status from the agent run (not stale Feature.lifecycle).
|
|
6
6
|
*
|
|
7
7
|
* Usage: shep feat ls [options]
|
|
8
8
|
*
|
|
@@ -11,47 +11,5 @@
|
|
|
11
11
|
* $ shep feat ls --repo /path/to/project
|
|
12
12
|
*/
|
|
13
13
|
import { Command } from 'commander';
|
|
14
|
-
import type { Feature, AgentRun, PhaseTiming, Repository } from '../../../../../packages/core/src/domain/generated/output.js';
|
|
15
|
-
/** Convert a date value (any type from domain) to a timestamp for sorting. */
|
|
16
|
-
export declare function toTimestamp(val: unknown): number;
|
|
17
|
-
interface Entry {
|
|
18
|
-
feature: Feature;
|
|
19
|
-
run: AgentRun | null;
|
|
20
|
-
phases: PhaseTiming[];
|
|
21
|
-
}
|
|
22
|
-
interface TreeNode {
|
|
23
|
-
entry: Entry;
|
|
24
|
-
children: TreeNode[];
|
|
25
|
-
}
|
|
26
|
-
interface RepoGroup {
|
|
27
|
-
repoPath: string;
|
|
28
|
-
repoName: string;
|
|
29
|
-
repoCreatedAt: number;
|
|
30
|
-
roots: TreeNode[];
|
|
31
|
-
}
|
|
32
|
-
interface FlatRow {
|
|
33
|
-
entry: Entry;
|
|
34
|
-
parentIsLast: boolean[];
|
|
35
|
-
isLast: boolean;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Build a recursive feature tree within a single repo group.
|
|
39
|
-
* Features with a parentId that matches another feature in the group become children.
|
|
40
|
-
* All levels are sorted by createdAt descending.
|
|
41
|
-
*/
|
|
42
|
-
export declare function buildTree(entries: Entry[]): TreeNode[];
|
|
43
|
-
/**
|
|
44
|
-
* Group entries by repositoryPath and sort repos by createdAt desc.
|
|
45
|
-
* Uses the Repository entity's createdAt if available; falls back to the
|
|
46
|
-
* newest feature's createdAt in that repo group.
|
|
47
|
-
*/
|
|
48
|
-
export declare function groupByRepo(entries: Entry[], repos: Repository[]): RepoGroup[];
|
|
49
|
-
/** Flatten a tree into rows with prefix context for rendering. */
|
|
50
|
-
export declare function flattenTree(nodes: TreeNode[], parentIsLast: boolean[]): FlatRow[];
|
|
51
|
-
/** Build the tree-drawing prefix string for a given depth context. */
|
|
52
|
-
export declare function buildTreePrefix(parentIsLast: boolean[], isLast: boolean): string;
|
|
53
|
-
/** Render a single feature row as a formatted string. */
|
|
54
|
-
export declare function renderFeatureRow(entry: Entry, treePrefix: string): string;
|
|
55
14
|
export declare function createLsCommand(): Command;
|
|
56
|
-
export {};
|
|
57
15
|
//# sourceMappingURL=ls.command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkLpC,wBAAgB,eAAe,IAAI,OAAO,CAwGzC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Feature List Command
|
|
3
3
|
*
|
|
4
|
-
* Lists features in a
|
|
5
|
-
*
|
|
4
|
+
* Lists features in a formatted list with optional filtering.
|
|
5
|
+
* Derives real-time status from the agent run (not stale Feature.lifecycle).
|
|
6
6
|
*
|
|
7
7
|
* Usage: shep feat ls [options]
|
|
8
8
|
*
|
|
@@ -14,7 +14,7 @@ import path from 'node:path';
|
|
|
14
14
|
import { Command } from 'commander';
|
|
15
15
|
import { container } from '../../../../../packages/core/src/infrastructure/di/container.js';
|
|
16
16
|
import { ListFeaturesUseCase } from '../../../../../packages/core/src/application/use-cases/features/list-features.use-case.js';
|
|
17
|
-
import { colors, symbols, messages,
|
|
17
|
+
import { colors, symbols, messages, renderListView } from '../../ui/index.js';
|
|
18
18
|
/** Map graph node names to human-readable phase labels (active). */
|
|
19
19
|
const NODE_TO_PHASE = {
|
|
20
20
|
analyze: 'Analyzing',
|
|
@@ -33,10 +33,21 @@ const NODE_TO_REVIEW = {
|
|
|
33
33
|
implement: 'Review Merge',
|
|
34
34
|
merge: 'Review Merge',
|
|
35
35
|
};
|
|
36
|
+
/** Status priority for sorting (lower = higher priority). */
|
|
37
|
+
const STATUS_PRIORITY = {
|
|
38
|
+
running: 0,
|
|
39
|
+
pending: 0,
|
|
40
|
+
waiting_approval: 1,
|
|
41
|
+
failed: 2,
|
|
42
|
+
interrupted: 2,
|
|
43
|
+
completed: 3,
|
|
44
|
+
};
|
|
36
45
|
/**
|
|
37
46
|
* Derive the display status from the agent run.
|
|
47
|
+
* Returns the current graph node as a phase label with an activity indicator.
|
|
38
48
|
*/
|
|
39
49
|
function formatStatus(feature, run) {
|
|
50
|
+
// Blocked features show "Waiting" — the parent relationship is conveyed by tree indentation
|
|
40
51
|
if (feature.lifecycle === 'Archived') {
|
|
41
52
|
return `${colors.muted(symbols.dotEmpty)} ${colors.muted('Archived')}`;
|
|
42
53
|
}
|
|
@@ -113,6 +124,7 @@ function formatElapsed(run, phaseTimings) {
|
|
|
113
124
|
const totalMs = phaseTimings.reduce((sum, pt) => {
|
|
114
125
|
if (pt.durationMs != null)
|
|
115
126
|
return sum + Number(pt.durationMs);
|
|
127
|
+
// Phase still running — add live delta
|
|
116
128
|
if (pt.startedAt)
|
|
117
129
|
return sum + (now - new Date(pt.startedAt).getTime());
|
|
118
130
|
return sum;
|
|
@@ -120,6 +132,7 @@ function formatElapsed(run, phaseTimings) {
|
|
|
120
132
|
const isRunning = run?.status === 'running' || run?.status === 'pending';
|
|
121
133
|
return isRunning ? colors.info(formatDuration(totalMs)) : colors.muted(formatDuration(totalMs));
|
|
122
134
|
}
|
|
135
|
+
// Fallback to run timestamps if no phase timings
|
|
123
136
|
if (!run?.startedAt)
|
|
124
137
|
return '';
|
|
125
138
|
const started = new Date(run.startedAt).getTime();
|
|
@@ -147,149 +160,11 @@ function formatGates(feature) {
|
|
|
147
160
|
const push = feature.push ? colors.accent('■') : colors.muted('□');
|
|
148
161
|
return `${gate(allowPrd)} ${gate(allowPlan)} ${gate(allowMerge)} ${push}`;
|
|
149
162
|
}
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
if (!
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
return new Date(val).getTime();
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
return 0;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/** Strip ANSI escape sequences to get visible character count. */
|
|
162
|
-
function stripAnsi(text) {
|
|
163
|
-
// eslint-disable-next-line no-control-regex
|
|
164
|
-
return text.replace(/\x1B\[[0-9;]*m/g, '');
|
|
165
|
-
}
|
|
166
|
-
/** Pad a string that may contain ANSI codes to a given visible width. */
|
|
167
|
-
function ansiPad(text, width) {
|
|
168
|
-
const visible = stripAnsi(text).length;
|
|
169
|
-
if (visible >= width)
|
|
170
|
-
return text;
|
|
171
|
-
return text + ' '.repeat(width - visible);
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Build a recursive feature tree within a single repo group.
|
|
175
|
-
* Features with a parentId that matches another feature in the group become children.
|
|
176
|
-
* All levels are sorted by createdAt descending.
|
|
177
|
-
*/
|
|
178
|
-
export function buildTree(entries) {
|
|
179
|
-
const byId = new Map();
|
|
180
|
-
for (const e of entries)
|
|
181
|
-
byId.set(e.feature.id, e);
|
|
182
|
-
const childrenByParent = new Map();
|
|
183
|
-
const rootEntries = [];
|
|
184
|
-
for (const e of entries) {
|
|
185
|
-
const pid = e.feature.parentId;
|
|
186
|
-
if (pid && byId.has(pid)) {
|
|
187
|
-
const list = childrenByParent.get(pid) ?? [];
|
|
188
|
-
list.push(e);
|
|
189
|
-
childrenByParent.set(pid, list);
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
rootEntries.push(e);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function sortDesc(arr) {
|
|
196
|
-
return [...arr].sort((a, b) => toTimestamp(b.feature.createdAt) - toTimestamp(a.feature.createdAt));
|
|
197
|
-
}
|
|
198
|
-
function buildNodes(arr) {
|
|
199
|
-
return sortDesc(arr).map((e) => ({
|
|
200
|
-
entry: e,
|
|
201
|
-
children: buildNodes(childrenByParent.get(e.feature.id) ?? []),
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
return buildNodes(rootEntries);
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Group entries by repositoryPath and sort repos by createdAt desc.
|
|
208
|
-
* Uses the Repository entity's createdAt if available; falls back to the
|
|
209
|
-
* newest feature's createdAt in that repo group.
|
|
210
|
-
*/
|
|
211
|
-
export function groupByRepo(entries, repos) {
|
|
212
|
-
const repoByPath = new Map();
|
|
213
|
-
for (const r of repos) {
|
|
214
|
-
repoByPath.set(r.path.replace(/\\/g, '/'), r);
|
|
215
|
-
}
|
|
216
|
-
const groupMap = new Map();
|
|
217
|
-
for (const e of entries) {
|
|
218
|
-
const repoPath = e.feature.repositoryPath.replace(/\\/g, '/');
|
|
219
|
-
const list = groupMap.get(repoPath) ?? [];
|
|
220
|
-
list.push(e);
|
|
221
|
-
groupMap.set(repoPath, list);
|
|
222
|
-
}
|
|
223
|
-
const groups = [];
|
|
224
|
-
for (const [repoPath, groupEntries] of groupMap) {
|
|
225
|
-
const repo = repoByPath.get(repoPath);
|
|
226
|
-
const repoCreatedAt = repo
|
|
227
|
-
? toTimestamp(repo.createdAt)
|
|
228
|
-
: Math.max(...groupEntries.map((e) => toTimestamp(e.feature.createdAt)));
|
|
229
|
-
groups.push({
|
|
230
|
-
repoPath,
|
|
231
|
-
repoName: path.basename(repoPath),
|
|
232
|
-
repoCreatedAt,
|
|
233
|
-
roots: buildTree(groupEntries),
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
groups.sort((a, b) => b.repoCreatedAt - a.repoCreatedAt);
|
|
237
|
-
return groups;
|
|
238
|
-
}
|
|
239
|
-
/** Flatten a tree into rows with prefix context for rendering. */
|
|
240
|
-
export function flattenTree(nodes, parentIsLast) {
|
|
241
|
-
const result = [];
|
|
242
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
243
|
-
const node = nodes[i];
|
|
244
|
-
const isLast = i === nodes.length - 1;
|
|
245
|
-
result.push({ entry: node.entry, parentIsLast, isLast });
|
|
246
|
-
if (node.children.length > 0) {
|
|
247
|
-
result.push(...flattenTree(node.children, [...parentIsLast, isLast]));
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return result;
|
|
251
|
-
}
|
|
252
|
-
/** Build the tree-drawing prefix string for a given depth context. */
|
|
253
|
-
export function buildTreePrefix(parentIsLast, isLast) {
|
|
254
|
-
let prefix = '';
|
|
255
|
-
for (const wasLast of parentIsLast) {
|
|
256
|
-
prefix += wasLast ? ' ' : '│ ';
|
|
257
|
-
}
|
|
258
|
-
prefix += isLast ? '└─ ' : '├─ ';
|
|
259
|
-
return prefix;
|
|
260
|
-
}
|
|
261
|
-
// Column widths for feature rows
|
|
262
|
-
const FIRST_COL_WIDTH = 44; // tree prefix + id + 2 spaces + name
|
|
263
|
-
const STATUS_WIDTH = 22;
|
|
264
|
-
const GATES_WIDTH = 10;
|
|
265
|
-
const ELAPSED_WIDTH = 9;
|
|
266
|
-
/** Render a single feature row as a formatted string. */
|
|
267
|
-
export function renderFeatureRow(entry, treePrefix) {
|
|
268
|
-
const { feature, run, phases } = entry;
|
|
269
|
-
const shortId = colors.muted(feature.id.slice(0, 8));
|
|
270
|
-
const prefixPlusId = `${treePrefix}${shortId} `;
|
|
271
|
-
const prefixPlusIdLen = stripAnsi(prefixPlusId).length;
|
|
272
|
-
const nameMax = Math.max(FIRST_COL_WIDTH - prefixPlusIdLen, 8);
|
|
273
|
-
const name = truncate(feature.name, nameMax);
|
|
274
|
-
const firstCol = ansiPad(prefixPlusId + name, FIRST_COL_WIDTH);
|
|
275
|
-
const status = ansiPad(formatStatus(feature, run), STATUS_WIDTH);
|
|
276
|
-
const gates = ansiPad(formatGates(feature), GATES_WIDTH);
|
|
277
|
-
const elapsed = ansiPad(formatElapsed(run, phases), ELAPSED_WIDTH);
|
|
278
|
-
const done = formatDone(run);
|
|
279
|
-
return ` ${firstCol} ${status} ${gates} ${elapsed} ${done}`;
|
|
280
|
-
}
|
|
281
|
-
/** Count total features across all repo groups. */
|
|
282
|
-
function countFeatures(groups) {
|
|
283
|
-
let count = 0;
|
|
284
|
-
function countNodes(nodes) {
|
|
285
|
-
for (const n of nodes) {
|
|
286
|
-
count++;
|
|
287
|
-
countNodes(n.children);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
for (const g of groups)
|
|
291
|
-
countNodes(g.roots);
|
|
292
|
-
return count;
|
|
163
|
+
/** Get sort priority for a run status (lower = shown first). */
|
|
164
|
+
function getStatusPriority(run) {
|
|
165
|
+
if (!run)
|
|
166
|
+
return 4;
|
|
167
|
+
return STATUS_PRIORITY[run.status] ?? 4;
|
|
293
168
|
}
|
|
294
169
|
export function createLsCommand() {
|
|
295
170
|
return new Command('ls')
|
|
@@ -302,54 +177,79 @@ export function createLsCommand() {
|
|
|
302
177
|
const useCase = container.resolve(ListFeaturesUseCase);
|
|
303
178
|
const runRepo = container.resolve('IAgentRunRepository');
|
|
304
179
|
const phaseRepo = container.resolve('IPhaseTimingRepository');
|
|
305
|
-
const repoRepo = container.resolve('IRepositoryRepository');
|
|
306
180
|
const filters = {
|
|
307
181
|
...(options.repo && { repositoryPath: options.repo }),
|
|
308
182
|
...(options.includeDeleted && { includeDeleted: true }),
|
|
309
183
|
...(options.showArchived && { includeArchived: true }),
|
|
310
184
|
};
|
|
311
185
|
const features = await useCase.execute(Object.keys(filters).length > 0 ? filters : undefined);
|
|
312
|
-
// Load agent runs
|
|
313
|
-
const [runs, timings
|
|
186
|
+
// Load agent runs and phase timings for all features in parallel
|
|
187
|
+
const [runs, timings] = await Promise.all([
|
|
314
188
|
Promise.all(features.map((f) => f.agentRunId ? runRepo.findById(f.agentRunId) : Promise.resolve(null))),
|
|
315
189
|
Promise.all(features.map((f) => phaseRepo.findByFeatureId(f.id))),
|
|
316
|
-
repoRepo.list(),
|
|
317
190
|
]);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
run: runs[i],
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
191
|
+
// Pair features with runs/timings and sort by status priority
|
|
192
|
+
const paired = features
|
|
193
|
+
.map((feature, i) => ({ feature, run: runs[i], phases: timings[i] }))
|
|
194
|
+
.sort((a, b) => getStatusPriority(a.run) - getStatusPriority(b.run));
|
|
195
|
+
const childrenByParent = new Map();
|
|
196
|
+
const roots = [];
|
|
197
|
+
for (const entry of paired) {
|
|
198
|
+
const pid = entry.feature.parentId;
|
|
199
|
+
if (pid && entry.feature.lifecycle === 'Blocked') {
|
|
200
|
+
const list = childrenByParent.get(pid) ?? [];
|
|
201
|
+
list.push(entry);
|
|
202
|
+
childrenByParent.set(pid, list);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
roots.push(entry);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Flatten: parent then its blocked children
|
|
209
|
+
const ordered = [];
|
|
210
|
+
for (const entry of roots) {
|
|
211
|
+
ordered.push({ entry, indent: false });
|
|
212
|
+
const kids = childrenByParent.get(entry.feature.id);
|
|
213
|
+
if (kids) {
|
|
214
|
+
for (const kid of kids) {
|
|
215
|
+
ordered.push({ entry: kid, indent: true });
|
|
216
|
+
}
|
|
217
|
+
childrenByParent.delete(entry.feature.id);
|
|
218
|
+
}
|
|
330
219
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
// Column headers
|
|
336
|
-
const h1 = colors.muted('NAME'.padEnd(FIRST_COL_WIDTH));
|
|
337
|
-
const h2 = colors.muted('STATUS'.padEnd(STATUS_WIDTH));
|
|
338
|
-
const h3 = colors.muted('R P M ↑'.padEnd(GATES_WIDTH));
|
|
339
|
-
const h4 = colors.muted('ELAPSED'.padEnd(ELAPSED_WIDTH));
|
|
340
|
-
const h5 = colors.muted('DONE');
|
|
341
|
-
lines.push(` ${h1} ${h2} ${h3} ${h4} ${h5}`);
|
|
342
|
-
for (const group of groups) {
|
|
343
|
-
lines.push('');
|
|
344
|
-
lines.push(` ${fmt.heading(group.repoName)} ${colors.muted(group.repoPath)}`);
|
|
345
|
-
const flatRows = flattenTree(group.roots, []);
|
|
346
|
-
for (const { entry, parentIsLast, isLast } of flatRows) {
|
|
347
|
-
const treePrefix = buildTreePrefix(parentIsLast, isLast);
|
|
348
|
-
lines.push(renderFeatureRow(entry, treePrefix));
|
|
220
|
+
// Append orphaned blocked children (parent not in current list)
|
|
221
|
+
for (const kids of childrenByParent.values()) {
|
|
222
|
+
for (const kid of kids) {
|
|
223
|
+
ordered.push({ entry: kid, indent: false });
|
|
349
224
|
}
|
|
350
225
|
}
|
|
351
|
-
|
|
352
|
-
|
|
226
|
+
const rows = ordered.map(({ entry: { feature, run, phases }, indent }) => {
|
|
227
|
+
const repo = path.basename(feature.repositoryPath);
|
|
228
|
+
const prefix = indent ? `${colors.muted('└')} ` : '';
|
|
229
|
+
return [
|
|
230
|
+
prefix + feature.id.slice(0, 8),
|
|
231
|
+
prefix + truncate(feature.name, indent ? 28 : 30),
|
|
232
|
+
formatStatus(feature, run),
|
|
233
|
+
colors.muted(repo),
|
|
234
|
+
formatGates(feature),
|
|
235
|
+
formatElapsed(run, phases),
|
|
236
|
+
formatDone(run),
|
|
237
|
+
];
|
|
238
|
+
});
|
|
239
|
+
renderListView({
|
|
240
|
+
title: 'Features',
|
|
241
|
+
columns: [
|
|
242
|
+
{ label: 'ID', width: 10 },
|
|
243
|
+
{ label: 'Name', width: 32 },
|
|
244
|
+
{ label: 'Status', width: 21 },
|
|
245
|
+
{ label: 'Repo', width: 20 },
|
|
246
|
+
{ label: 'R P M ↑', width: 10 },
|
|
247
|
+
{ label: 'Elapsed', width: 10 },
|
|
248
|
+
{ label: 'Done', width: 12 },
|
|
249
|
+
],
|
|
250
|
+
rows,
|
|
251
|
+
emptyMessage: 'No features found',
|
|
252
|
+
});
|
|
353
253
|
}
|
|
354
254
|
catch (error) {
|
|
355
255
|
const err = error instanceof Error ? error : new Error(String(error));
|