Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7ac4224
feat(inventory): add where-used reverse lookup for fragments, widgets…
claude Apr 29, 2026
516f9aa
feat: deepen liferay where-used inventory evidence
mordonez Apr 29, 2026
ca35164
refactor: deepen page evidence builder
mordonez Apr 29, 2026
d59c75a
refactor: deepen inventory evidence and url seams
mordonez Apr 29, 2026
70f1aa5
test: cover journal resolver and tighten display page errors
mordonez Apr 29, 2026
0b057c6
fix: preserve journal refs across groups
mordonez Apr 29, 2026
f621be8
fix: merge duplicate journal article refs
mordonez Apr 29, 2026
32f57bc
docs: document where-used discovery workflow
mordonez Apr 29, 2026
fb93e9f
fix: restore regular page fragment article extraction
mordonez Apr 29, 2026
5a94e8f
chore: merge origin main
mordonez Apr 30, 2026
8548e66
fix: address copilot review suggestions
mordonez Apr 30, 2026
847dcc2
Merge branch 'main' into claude/resource-page-inventory-egHu5
mordonez May 11, 2026
d619237
fix: improve clarity and formatting in Liferay skill documentation
mordonez May 11, 2026
69a81cc
feat(output): write utf-8 cli output safely
mordonez May 11, 2026
a141555
feat(inventory): capture rendered journal content evidence
mordonez May 11, 2026
3e91195
feat(inventory): extend where-used planning and mcp access
mordonez May 11, 2026
ab4214e
feat(dashboard): refine activity and maintenance panels
mordonez May 12, 2026
5827ddc
fix(env): rollback compose startup on cancel
mordonez May 12, 2026
d2fd7a0
refactor(cli): write output directly to stdio
mordonez May 12, 2026
4634d63
refactor(inventory): extract where-used selection helpers
mordonez May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Use these terms consistently in code, docs, tests, and architecture discussions.
- **Worktree**: An isolated branch checkout, usually under `.worktrees/<name>`, optionally with its own local runtime state.
- **Preflight**: A cached check that validates the API surfaces and credentials needed before longer portal or resource workflows.
- **Inventory**: Structured discovery of portal state, such as sites, pages, structures, and templates.
- **Page evidence**: Normalized searchable references found while inspecting a Page, such as Fragment, Widget, Portlet, Structure, Template, Journal article, or Display Page article evidence.
- **Portal resource**: A Liferay content artifact managed as a stable CLI workflow: structures, templates, ADTs, and fragments.
- **Resource workflow**: A file-based `ldev resource` operation for reading, exporting, importing, or syncing portal resources.
- **Resource migration**: A structured workflow for changing journal structures while preserving or cleaning up existing content.
Expand Down Expand Up @@ -82,12 +83,12 @@ ldev resource migration-init --site /global --structure BASIC

Every command should fit one phase of the operational loop:

| Phase | Purpose | Representative commands |
| --- | --- | --- |
| Understand | Resolve project, runtime, and portal state. | `ldev context`, `ldev status`, `ldev portal inventory ...` |
| Diagnose | Localize a failure. | `ldev doctor`, `ldev logs diagnose`, `ldev osgi diag <bundle>` |
| Fix | Apply the smallest safe local change. | `ldev deploy module`, `ldev resource import-* --check-only`, then a deliberate mutation |
| Verify | Prove the result with fresh evidence. | `ldev portal check`, `ldev portal inventory ... --json`, `ldev resource structure/template/adt`, `ldev logs diagnose --since 5m` |
| Phase | Purpose | Representative commands |
| ---------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Understand | Resolve project, runtime, and portal state. | `ldev context`, `ldev status`, `ldev portal inventory ...` |
| Diagnose | Localize a failure. | `ldev doctor`, `ldev logs diagnose`, `ldev osgi diag <bundle>` |
| Fix | Apply the smallest safe local change. | `ldev deploy module`, `ldev resource import-* --check-only`, then a deliberate mutation |
| Verify | Prove the result with fresh evidence. | `ldev portal check`, `ldev portal inventory ... --json`, `ldev resource structure/template/adt`, `ldev logs diagnose --since 5m` |

Resource verification must be read-after-write. Do not treat log output as sufficient proof that a portal resource changed correctly.

Expand Down
7 changes: 7 additions & 0 deletions docs/agentic/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,18 @@ ldev portal inventory sites --json
ldev portal inventory pages --site /global --json
ldev portal inventory page --url /home --json
ldev portal inventory structures --site /global --with-templates --json
ldev portal inventory where-used --type structure --key BASIC --site /global --json
```

For structure/template work, `inventory structures --with-templates` is the
right first call.

For impact analysis, use `inventory where-used` after the resource key is known.
It gives agents a task-shaped answer to “which Pages use this fragment, widget,
Structure, Template, or ADT?” before a mutation is proposed.

Prefer the scoped form with `--site` whenever the Site is already known.

## Decision route

The agent layer follows this rule:
Expand Down
37 changes: 37 additions & 0 deletions docs/commands/discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,43 @@ List web content templates for a site.
ldev portal inventory templates --site /global --json
```

## `ldev portal inventory where-used`

Reverse lookup for portal resources. Use it when you already know the fragment,
widget, Structure, Template, or ADT key and need to answer the practical
question: which Pages use it?

```bash
ldev portal inventory where-used --type fragment --key card-hero --site /guest
ldev portal inventory where-used --type widget --key com_liferay_journal_content_web_portlet_JournalContentPortlet --site /guest
ldev portal inventory where-used --type structure --key BASIC --site /facultat-farmacia-alimentacio
ldev portal inventory where-used --type adt --key UB_ADT_STUDIES_SEARCH --site /global
ldev portal inventory where-used --type template --key NEWS_TEMPLATE --site /global --include-private --json
```

Use `where-used` after discovery has identified the resource you care about.
It scans candidate Pages, extracts normalized Page evidence, and returns only
the Pages whose evidence matches the requested resource.

Prefer `--site` whenever you already know the owning Site. Without it,
`where-used` scans every accessible Site and can take much longer.

Options:

- `--type <fragment|widget|structure|template|adt>` — resource type to trace
- `--key <value>` — resource key to look up; repeatable
- `--site <friendlyUrl>` — limit the scan to one Site instead of all accessible Sites
- `--widget-type <value>` — required when an ADT key is ambiguous across widget types
- `--class-name <value>` — optional ADT disambiguator for the owning class
- `--include-private` — include private layouts in addition to public Pages
- `--max-depth <n>` — recursion depth for page hierarchy scans
- `--concurrency <n>` — concurrent page inspections
- `--page-size <n>` — page size for candidate collection APIs

This is especially useful before changing a shared portal resource: it gives you
read-before-write impact analysis without opening the UI or guessing where a
resource might be referenced.

## `ldev portal audit`

Minimal runtime audit of accessible site metadata and API reachability. Defaults to JSON.
Expand Down
13 changes: 13 additions & 0 deletions docs/core-concepts/discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ ldev portal inventory sites --json
ldev portal inventory pages --site /global --json
ldev portal inventory page --url /home --json
ldev portal inventory structures --site /global --with-templates --json
ldev portal inventory templates --site /global --json
ldev portal inventory where-used --type structure --key BASIC --site /global --json
```

Each one consolidates information that the Headless API only returns in
Expand All @@ -42,6 +44,17 @@ fragments. Highlights:
- `inventory structures --with-templates` — structures enriched with their
associated templates in one call, the right starting point for
structure/template work
- `inventory templates` — template inventory when you already know the site and
need template-specific identifiers
- `inventory where-used` — impact analysis for a known fragment, widget,
structure, template, or ADT before proposing a mutation

For structure/template incidents, prefer `inventory structures --with-templates`
as the first step: it returns both in one call, so you can route directly to
the matching `resource export-*` or `resource import-*` command.

When possible, add `--site` so the scan stays scoped to one Site instead of all
accessible Sites.

## Preflight: fail fast before long flows

Expand Down
22 changes: 22 additions & 0 deletions docs/workflows/explore-portal.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Use it when:
- you want a fast inventory of sites and pages
- you need structured output for automation
- an agent needs context before changing anything
- you need to know which Pages use a shared portal resource before changing it

## Start with sites

Expand Down Expand Up @@ -126,12 +127,33 @@ The benefit is twofold:
- an AI agent gets in one tool call what would otherwise need several, with
matching JSON shape across MCP and CLI

## Reverse lookup from a resource

Once you know the resource key, `where-used` gives you the part that the UI is
usually bad at: impact analysis across Pages.

```bash
ldev portal inventory where-used --type fragment --key card-hero --site /guest --json
ldev portal inventory where-used --type structure --key BASIC --site /guest --json
ldev portal inventory where-used --type adt --key UB_ADT_STUDIES_SEARCH --site /global --json
```

Prefer the scoped form with `--site` unless you really need a cross-site scan.

Use it for questions like:

- which Pages contain this Fragment
- which Pages render Journal content through this widget
- which Pages depend on this Structure or Template
- which Pages are tied to this ADT before I edit it

## Typical discovery flow

```bash
ldev portal inventory sites --json
ldev portal inventory pages --site /global --json
ldev portal inventory page --url /home --json
ldev portal inventory where-used --type structure --key BASIC --site /global --json
```

End with the exact page, site and route context you need before you change
Expand Down
121 changes: 116 additions & 5 deletions src/commands/liferay/inventory.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import {
formatLiferayInventoryTemplates,
runLiferayInventoryTemplates,
} from '../../features/liferay/inventory/liferay-inventory-templates.js';
import {
formatLiferayInventoryWhereUsed,
runLiferayInventoryWhereUsed,
type WhereUsedResourceType,
} from '../../features/liferay/inventory/liferay-inventory-where-used.js';
import {formatLiferayPreflight, runLiferayPreflight} from '../../features/liferay/liferay-preflight.js';

function collect(value: string, previous: string[]): string[] {
Expand Down Expand Up @@ -77,6 +82,22 @@ type InventoryPreflightCommandOptions = {
forceRefresh?: boolean;
};

type InventoryWhereUsedCommandOptions = {
type: string;
key: string[];
site: string[];
excludeSite: string[];
widgetType?: string;
className?: string;
includePrivate?: boolean;
siteLimit?: string;
siteOrder?: string;
plan?: boolean;
maxDepth: string;
concurrency: string;
pageSize: string;
};

export function createInventoryCommands(parent: Command): void {
const inventory = new Command('inventory')
.helpGroup('Discovery:')
Expand All @@ -88,11 +109,12 @@ export function createInventoryCommands(parent: Command): void {
Use these commands to discover IDs, URLs and keys before running export or import workflows.

Commands:
sites List accessible sites
pages List site pages
page Inspect one page
structures List journal structures
templates List web content templates
sites List accessible sites
pages List site pages
page Inspect one page
structures List journal structures
templates List web content templates
where-used Reverse lookup: pages that contain a fragment/widget/structure/template/adt
`,
);

Expand Down Expand Up @@ -313,6 +335,95 @@ Notes:
),
);

addOutputFormatOption(
inventory
.command('where-used')
.description('Reverse-lookup: list every page that contains a given fragment, widget, structure, template or ADT')
.requiredOption('--type <type>', 'Resource type: fragment | widget | portlet | structure | template | adt')
.option(
'--key <key>',
'Resource key to look up (repeat for OR-search across multiple keys)',
collect,
[] as string[],
)
.option(
'--site <site>',
'Limit lookup to one or more sites (repeatable; defaults to scanning all accessible sites)',
collect,
[] as string[],
)
.option(
'--exclude-site <site>',
'Exclude a site when scanning all accessible sites (repeatable)',
collect,
[] as string[],
)
.option('--widget-type <widgetType>', 'ADT widget type filter used only when --type adt')
.option('--class-name <className>', 'ADT class name filter used only when --type adt')
.option('--include-private', 'Also scan private layouts')
.option('--site-limit <n>', 'Maximum number of sites to scan when --site is not provided')
.option(
'--site-order <order>',
'Site prioritization: site | name | content (content is most useful for template|structure lookups)',
'site',
)
.option('--plan', 'Show the selected site scan plan and exit without inspecting pages')
.option('--max-depth <maxDepth>', 'Maximum page tree recursion depth', '12')
.option('--concurrency <n>', 'Parallel page fetches per site', '4')
.option('--page-size <pageSize>', 'Headless page size for site listings', '200')
.addHelpText(
'after',
`
Examples:
ldev portal inventory where-used --type fragment --key card-hero --site /guest
ldev portal inventory where-used --type fragment --key card-hero --site /guest --site /global
ldev portal inventory where-used --type widget --key com_liferay_journal_content_web_portlet_JournalContentPortlet --site /guest
ldev portal inventory where-used --type structure --key BASIC --site /facultat-farmacia-alimentacio
ldev portal inventory where-used --type adt --key UB_ADT_STUDIES_SEARCH --site /global
ldev portal inventory where-used --type template --key NEWS_TEMPLATE --site /global --include-private --json
ldev portal inventory where-used --type template --key UB_TPL_DESTACATS_MULTIMEDIA --site-order content --site-limit 10 --plan
ldev portal inventory where-used --type template --key UB_TPL_DESTACATS_MULTIMEDIA --site-order content --site-limit 10 --exclude-site /global

Notes:
- Prefer one or more --site values when you already know the owning sites; scanning all accessible sites can take much longer.
- Without --site, use --site-order content plus --site-limit to prioritize the largest content sites first for template|structure lookups.
- For fragment|widget|portlet|adt, keep the default site order unless you have a specific reason to rank by content volume.
- The lookup walks the same data exposed by 'inventory page' so any reference visible there can be matched.
- --key may be repeated to OR-match several keys in a single pass.
- For widget/portlet lookups both the widgetName and the full portletId are matched.
- For ADT lookups the key is resolved through the ADT catalog first, then matched by widget displayStyle on pages.
- --plan resolves and prints the site scan order without fetching page inventories.
- Pages that fail to load (e.g. permission errors) are reported under failedPages without aborting the run.
`,
),
).action(
createFormattedAction(
async (context, options: InventoryWhereUsedCommandOptions) => {
const parsedMaxDepth = Number.parseInt(options.maxDepth, 10);
const parsedConcurrency = Number.parseInt(options.concurrency, 10);
const parsedPageSize = Number.parseInt(options.pageSize, 10);
const parsedSiteLimit = options.siteLimit !== undefined ? Number.parseInt(options.siteLimit, 10) : undefined;

return runLiferayInventoryWhereUsed(context.config, {
type: options.type as WhereUsedResourceType,
keys: options.key,
...(options.site.length > 0 ? {sites: options.site} : {}),
excludeSites: options.excludeSite,
widgetType: options.widgetType,
className: options.className,
includePrivate: Boolean(options.includePrivate),
...(parsedSiteLimit !== undefined ? {siteLimit: parsedSiteLimit} : {}),
...(options.siteOrder ? {siteOrder: options.siteOrder} : {}),
plan: Boolean(options.plan),
maxDepth: Number.isFinite(parsedMaxDepth) ? parsedMaxDepth : 12,
concurrency: Number.isFinite(parsedConcurrency) ? parsedConcurrency : 4,
pageSize: Number.isFinite(parsedPageSize) ? parsedPageSize : 200,
});
},
{text: formatLiferayInventoryWhereUsed},
),
);

addOutputFormatOption(
inventory
.command('preflight')
Expand Down
Loading
Loading