@shepai/cli 1.142.1 → 1.144.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/src/presentation/cli/commands/feat/ls.command.d.ts +44 -2
- package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/feat/ls.command.js +182 -82
- 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 +2 -2
- package/web/.next/required-server-files.json +2 -2
- 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/_01046927._.js +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/_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/_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/{_be90dd43._.js → _c64f06d5._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_be90dd43._.js.map → _c64f06d5._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/{_05b4c375._.js → _c67ad133._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_05b4c375._.js.map → _c67ad133._.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__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/chunks/ssr/{src_presentation_web_93e5fd0d._.js → src_presentation_web_dd85ad88._.js} +2 -2
- package/web/.next/server/chunks/ssr/{src_presentation_web_93e5fd0d._.js.map → src_presentation_web_dd85ad88._.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/{3cd98a2f7a8a33a8.js → 0bce83a11c7a9383.js} +1 -1
- package/web/.next/static/chunks/{7fe2bb39ee56a00a.js → 1867fdb7f2c6035f.js} +1 -1
- package/web/.next/static/chunks/{6b099ae0735b3c21.js → 21502baa33ad728a.js} +2 -2
- package/web/.next/static/chunks/{09084b604a4022a2.js → 53478ed65db63030.js} +1 -1
- package/web/.next/static/chunks/{f7251d9aebce001c.js → 60c6c3c30fba3b7c.js} +1 -1
- package/web/.next/static/chunks/{8b7ba729b53257a1.js → 683ec435a34ca112.js} +1 -1
- package/web/.next/static/chunks/{41d77b193b5df1b7.js → 88300dbc7c91abb2.js} +1 -1
- package/web/.next/static/chunks/{86afa5ddfcbbbf94.js → 8cc1aea0d82835be.js} +1 -1
- package/web/.next/static/chunks/{9f57fc63be1397a3.js → 904cdf4c47654f32.js} +1 -1
- package/web/.next/static/chunks/9e3e916a22d1121f.js +1 -0
- package/web/.next/static/chunks/{a05f5913e43443d6.js → de0704c3e73118cf.js} +1 -1
- package/web/.next/static/chunks/{d5475a9f1eed113d.js → ecfbc35ad11c83bb.js} +1 -1
- package/web/.next/static/chunks/d2eb3a9e6fa8cdad.js +0 -1
- /package/web/.next/static/{Z9eZhnwItIVi_t0WGZSA4 → rsS9eOHzCHbGCCwBS1fRh}/_buildManifest.js +0 -0
- /package/web/.next/static/{Z9eZhnwItIVi_t0WGZSA4 → rsS9eOHzCHbGCCwBS1fRh}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{Z9eZhnwItIVi_t0WGZSA4 → rsS9eOHzCHbGCCwBS1fRh}/_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 hierarchical tree view: repo → feature → child → child…
|
|
5
|
+
* Repos and features are ordered by creation date descending.
|
|
6
6
|
*
|
|
7
7
|
* Usage: shep feat ls [options]
|
|
8
8
|
*
|
|
@@ -11,5 +11,47 @@
|
|
|
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;
|
|
14
55
|
export declare function createLsCommand(): Command;
|
|
56
|
+
export {};
|
|
15
57
|
//# 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;AAMpC,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAyJ/F,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOhD;AAiBD,UAAU,KAAK;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,UAAU,QAAQ;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAgCtD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,SAAS,EAAE,CA8B9E;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAWjF;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,YAAY,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAOhF;AAQD,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAazE;AAeD,wBAAgB,eAAe,IAAI,OAAO,CAiFzC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Feature List Command
|
|
3
3
|
*
|
|
4
|
-
* Lists features in a
|
|
5
|
-
*
|
|
4
|
+
* Lists features in a hierarchical tree view: repo → feature → child → child…
|
|
5
|
+
* Repos and features are ordered by creation date descending.
|
|
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, fmt } 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,21 +33,10 @@ 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
|
-
};
|
|
45
36
|
/**
|
|
46
37
|
* Derive the display status from the agent run.
|
|
47
|
-
* Returns the current graph node as a phase label with an activity indicator.
|
|
48
38
|
*/
|
|
49
39
|
function formatStatus(feature, run) {
|
|
50
|
-
// Blocked features show "Waiting" — the parent relationship is conveyed by tree indentation
|
|
51
40
|
if (feature.lifecycle === 'Archived') {
|
|
52
41
|
return `${colors.muted(symbols.dotEmpty)} ${colors.muted('Archived')}`;
|
|
53
42
|
}
|
|
@@ -124,7 +113,6 @@ function formatElapsed(run, phaseTimings) {
|
|
|
124
113
|
const totalMs = phaseTimings.reduce((sum, pt) => {
|
|
125
114
|
if (pt.durationMs != null)
|
|
126
115
|
return sum + Number(pt.durationMs);
|
|
127
|
-
// Phase still running — add live delta
|
|
128
116
|
if (pt.startedAt)
|
|
129
117
|
return sum + (now - new Date(pt.startedAt).getTime());
|
|
130
118
|
return sum;
|
|
@@ -132,7 +120,6 @@ function formatElapsed(run, phaseTimings) {
|
|
|
132
120
|
const isRunning = run?.status === 'running' || run?.status === 'pending';
|
|
133
121
|
return isRunning ? colors.info(formatDuration(totalMs)) : colors.muted(formatDuration(totalMs));
|
|
134
122
|
}
|
|
135
|
-
// Fallback to run timestamps if no phase timings
|
|
136
123
|
if (!run?.startedAt)
|
|
137
124
|
return '';
|
|
138
125
|
const started = new Date(run.startedAt).getTime();
|
|
@@ -160,11 +147,149 @@ function formatGates(feature) {
|
|
|
160
147
|
const push = feature.push ? colors.accent('■') : colors.muted('□');
|
|
161
148
|
return `${gate(allowPrd)} ${gate(allowPlan)} ${gate(allowMerge)} ${push}`;
|
|
162
149
|
}
|
|
163
|
-
/**
|
|
164
|
-
function
|
|
165
|
-
if (!
|
|
166
|
-
return
|
|
167
|
-
|
|
150
|
+
/** Convert a date value (any type from domain) to a timestamp for sorting. */
|
|
151
|
+
export function toTimestamp(val) {
|
|
152
|
+
if (!val)
|
|
153
|
+
return 0;
|
|
154
|
+
try {
|
|
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;
|
|
168
293
|
}
|
|
169
294
|
export function createLsCommand() {
|
|
170
295
|
return new Command('ls')
|
|
@@ -177,79 +302,54 @@ export function createLsCommand() {
|
|
|
177
302
|
const useCase = container.resolve(ListFeaturesUseCase);
|
|
178
303
|
const runRepo = container.resolve('IAgentRunRepository');
|
|
179
304
|
const phaseRepo = container.resolve('IPhaseTimingRepository');
|
|
305
|
+
const repoRepo = container.resolve('IRepositoryRepository');
|
|
180
306
|
const filters = {
|
|
181
307
|
...(options.repo && { repositoryPath: options.repo }),
|
|
182
308
|
...(options.includeDeleted && { includeDeleted: true }),
|
|
183
309
|
...(options.showArchived && { includeArchived: true }),
|
|
184
310
|
};
|
|
185
311
|
const features = await useCase.execute(Object.keys(filters).length > 0 ? filters : undefined);
|
|
186
|
-
// Load agent runs
|
|
187
|
-
const [runs, timings] = await Promise.all([
|
|
312
|
+
// Load agent runs, phase timings, and repos in parallel
|
|
313
|
+
const [runs, timings, repos] = await Promise.all([
|
|
188
314
|
Promise.all(features.map((f) => f.agentRunId ? runRepo.findById(f.agentRunId) : Promise.resolve(null))),
|
|
189
315
|
Promise.all(features.map((f) => phaseRepo.findByFeatureId(f.id))),
|
|
316
|
+
repoRepo.list(),
|
|
190
317
|
]);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
}
|
|
318
|
+
const entries = features.map((feature, i) => ({
|
|
319
|
+
feature,
|
|
320
|
+
run: runs[i],
|
|
321
|
+
phases: timings[i],
|
|
322
|
+
}));
|
|
323
|
+
const groups = groupByRepo(entries, repos);
|
|
324
|
+
const total = countFeatures(groups);
|
|
325
|
+
if (total === 0) {
|
|
326
|
+
messages.newline();
|
|
327
|
+
messages.info('No features found');
|
|
328
|
+
messages.newline();
|
|
329
|
+
return;
|
|
219
330
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
331
|
+
const lines = [];
|
|
332
|
+
lines.push('');
|
|
333
|
+
lines.push(` ${fmt.heading(`Features (${total})`)}`);
|
|
334
|
+
lines.push('');
|
|
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));
|
|
224
349
|
}
|
|
225
350
|
}
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
});
|
|
351
|
+
lines.push('');
|
|
352
|
+
console.log(lines.join('\n'));
|
|
253
353
|
}
|
|
254
354
|
catch (error) {
|
|
255
355
|
const err = error instanceof Error ? error : new Error(String(error));
|