Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
11 changes: 11 additions & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,17 @@ catalog:
target: ../../examples/mock-org/cycles.yaml
rules:
- allow: [Cycle, Saga]
# Guild Hall dogfood: a mock scaffolder Template tagged guild-hall so the
# Actions panel has something to render (the guildhall/ vocabularies
# above it are deliberately NOT ingested; this one is).
- type: file
target: ../../examples/mock-org/guildhall/actions/charter-practice.template.yaml
rules:
- allow: [Template]
- type: file
target: ../../examples/mock-org/guildhall/actions/guild-actions.template.yaml
rules:
- allow: [Template]

## Uncomment these lines to add more example data
# - type: url
Expand Down
398 changes: 398 additions & 0 deletions docs/plans/2026-07-21-gildi-crest-and-guilds-plan.md

Large diffs are not rendered by default.

246 changes: 246 additions & 0 deletions docs/plans/2026-07-21-gildi-full-page-shell-plan.md

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions examples/mock-org/cycles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# season, on the software side of the two-family model.
apiVersion: siliconsaga.org/v1alpha1
kind: Cycle
metadata: { name: tracking-2026-2, description: 'Parcel Tracking release 2026.2' }
metadata: { name: tracking-2026-2, title: 'Parcel Tracking 2026.2', description: 'Parcel Tracking release 2026.2' }
spec:
type: release
timeframe: { start: '2026-04-01', end: '2026-06-30' }
Expand All @@ -17,20 +17,36 @@ apiVersion: siliconsaga.org/v1alpha1
kind: Cycle
metadata:
name: dependency-scanning-drive
description: 'Drive: dependency scanning green on every service, Ravenline and Foxholm both (paved-road adoption push, module 1.4)'
title: Dependency scanning drive
description: 'Every service green on dependency scanning, Ravenline and Foxholm both — the paved-road adoption push for module 1.4.'
spec:
type: drive
timeframe: { start: '2026-05-01', end: '2026-07-31' }
of: group:default/security-gildi
owner: group:default/security-gildi
---
# A second drive — the Platform guild's operational-readiness push — so the band
# shows more than one campaign at a time.
apiVersion: siliconsaga.org/v1alpha1
kind: Cycle
metadata:
name: operational-readiness-drive
title: Operational readiness drive
description: 'Every production service with a linked rollback runbook and an SLO, before Q4.'
spec:
type: drive
timeframe: { start: '2026-07-01', end: '2026-09-30' }
of: group:default/platform-gildi
owner: group:default/platform-gildi
---
# The drive's own mid-run report — a Saga narrated by the drive's runner while
# the Cycle is still open. Gives the adoption story a narrator in-catalog.
apiVersion: siliconsaga.org/v1alpha1
kind: Saga
metadata:
name: saga-dependency-scanning-drive
description: 'The Saga of the Dependency Scanning Drive (mid-run report)'
title: The Dependency Scanning Drive
description: "Astrid's mid-run report — three of five services green, twelve days left; the paved road sells itself when the fix is smaller than the excuse."
annotations:
siliconsaga.org/saga-doc: ./sagas/dependency-scanning-drive.md
spec:
Expand All @@ -49,7 +65,8 @@ apiVersion: siliconsaga.org/v1alpha1
kind: Saga
metadata:
name: saga-tracking-2026-2
description: 'The Saga of Parcel Tracking 2026.2'
title: Parcel Tracking 2026.2 — Retrospective
description: "Runa's release retrospective — what shipped, the May 14th incident, and what the next cycle carries forward."
annotations:
siliconsaga.org/saga-doc: ./sagas/tracking-2026-2.md
spec:
Expand Down
21 changes: 21 additions & 0 deletions examples/mock-org/guildhall/actions/charter-practice.template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: charter-a-practice
title: Charter a practice
description: Stand up a new practice and its guild — the Guild Hall dogfooding its own model.
tags: [guild-hall]
spec:
type: guildhall-action
owner: group:default/team-devex
parameters:
- title: New practice
properties:
practice: { title: Practice name, type: string, minLength: 1 }
required: [practice]
steps:
- id: log
name: Plan
action: debug:log
input:
message: 'Would charter the ${{ parameters.practice }} practice and stand up its guild.'
48 changes: 48 additions & 0 deletions examples/mock-org/guildhall/actions/guild-actions.template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Two more Guild Hall dogfood actions (mock — debug:log only), tagged guild-hall
# so the Actions panel shows the full set: New guild / Charter a practice /
# Start a drive. Real scaffolding is future work; these log their plan.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: establish-a-guild
title: Establish a guild
description: Form a new guild (a typed Group) to steward a craft or an aspect — the Guild Hall dogfooding its own model.
tags: [guild-hall]
spec:
type: guildhall-action
owner: group:default/team-devex
parameters:
- title: New guild
properties:
guild: { title: Guild name, type: string, minLength: 1 }
stewards: { title: 'Stewards (aspect:<id> or craft:<id>)', type: string, minLength: 1 }
required: [guild, stewards]
steps:
- id: log
name: Plan
action: debug:log
input:
message: 'Would establish the ${{ parameters.guild }} guild stewarding ${{ parameters.stewards }}.'
---
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: start-a-drive
title: Start a drive
description: Launch a time-bound adoption drive (a Cycle) to push a practice across the fleet — the Guild Hall dogfooding its own model.
tags: [guild-hall]
spec:
type: guildhall-action
owner: group:default/team-devex
parameters:
- title: New drive
properties:
drive: { title: Drive name, type: string, minLength: 1 }
guild: { title: Owning guild, type: string, minLength: 1 }
required: [drive, guild]
steps:
- id: log
name: Plan
action: debug:log
input:
message: 'Would start the ${{ parameters.drive }} drive owned by ${{ parameters.guild }}.'
41 changes: 36 additions & 5 deletions examples/mock-org/org.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ metadata:
description: Ravenline — parcel-logistics SaaS (mock org, Guildhall demo)
spec:
type: organization
children: [rl-engineering, foxholm, security-gildi, release-captains-gildi]
children: [rl-engineering, foxholm, security-gildi, release-captains-gildi, platform-gildi, incident-commanders-gildi]
---
apiVersion: backstage.io/v1alpha1
kind: Group
Expand Down Expand Up @@ -62,6 +62,7 @@ apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: security-gildi
title: Security guild
description: The Security guild — stewards the security aspect (aspect-aligned gildi)
annotations:
siliconsaga.org/stewards: 'aspect:security'
Expand All @@ -79,6 +80,7 @@ apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: release-captains-gildi
title: Release Captains
description: The Release Captains guild — stewards the release-captain craft (craft-aligned gildi)
annotations:
siliconsaga.org/stewards: 'craft:release-captain'
Expand All @@ -87,6 +89,35 @@ spec:
parent: ravenline
children: []
---
# Aspect-aligned gildi around the (steward-less until now) operational-readiness
# aspect — gives the guilds grid and the drives band a second concern to show.
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: platform-gildi
title: Platform guild
description: The Platform guild — stewards operational readiness (rollback runbooks, SLOs, on-call health) (aspect-aligned gildi)
annotations:
siliconsaga.org/stewards: 'aspect:operational-readiness'
spec:
type: guild
parent: ravenline
children: []
---
# Craft-aligned gildi around the incident-commander craft (guildhall/crafts.yaml).
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: incident-commanders-gildi
title: Incident Commanders
description: The Incident Commanders guild — stewards the incident-commander craft (craft-aligned gildi)
annotations:
siliconsaga.org/stewards: 'craft:incident-commander'
spec:
type: guild
parent: ravenline
children: []
---
# People — membership carries both team and gildi affiliation via memberOf.
# Skill profiles are NOT entity fields: see guildhall/skills.yaml.
apiVersion: backstage.io/v1alpha1
Expand All @@ -111,13 +142,13 @@ spec: { memberOf: [team-tracking, security-gildi] }
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata: { name: sigrid, description: 'Shipping engineer' }
spec: { memberOf: [team-shipping] }
metadata: { name: sigrid, description: 'Shipping engineer, incident commander' }
spec: { memberOf: [team-shipping, incident-commanders-gildi] }
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata: { name: egil, description: 'Platform engineer' }
spec: { memberOf: [team-platform] }
metadata: { name: egil, description: 'Platform engineer, on-call lead' }
spec: { memberOf: [team-platform, platform-gildi] }
---
apiVersion: backstage.io/v1alpha1
kind: User
Expand Down
1 change: 1 addition & 0 deletions examples/mock-org/repos/security-aspect/catalog-info.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: security-practice
title: Security practice
description: 'The Security practice: its aspect module — standard, paved road, adoption templates, and remediation vísar'
annotations:
siliconsaga.org/aspect: 'security'
Expand Down
3 changes: 2 additions & 1 deletion examples/mtl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ apiVersion: siliconsaga.org/v1alpha1
kind: Saga
metadata:
name: saga-soccer-2026-spring
description: 'The Saga of MTL Soccer, Spring 2026'
title: MTL Soccer, Spring 2026
description: 'A youth soccer season, narrated — the community-side twin of the software retrospectives.'
annotations:
siliconsaga.org/saga-doc: ./sagas/soccer-2026-spring.md
spec:
Expand Down
21 changes: 21 additions & 0 deletions plugins/gildi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# @siliconsaga/plugin-gildi

The **Guild Hall** — a curated overview of the practice layer for this Backstage instance: the guilds, their practices and aspects, active drives, and recent sagas. Built on Backstage's new frontend system.

## Status

A page at `/guild-hall` with a sidebar entry. Guilds, Drives, Chronicle, and Actions all render. See `docs/plans/2026-07-20-gildi-guildhall-hub-design.md` in the leidangr repo for the full design.

## Install

Registered in the app's `features` (see `packages/app/src/App.tsx`):

import gildiPlugin from '@siliconsaga/plugin-gildi';
// ...
features: [/* … */, gildiPlugin]

## Development

ws exec leidangr corepack yarn workspace @siliconsaga/plugin-gildi test

This package is developed inside the leidangr instance for now; its package name (`@siliconsaga/*`) is scoped for later extraction to a standalone plugin repo.
6 changes: 5 additions & 1 deletion plugins/gildi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@
"clean": "backstage-cli package clean"
},
"dependencies": {
"@backstage/catalog-model": "^1.9.0",
"@backstage/core-components": "^0.18.11",
"@backstage/core-plugin-api": "^1.12.7",
"@backstage/frontend-plugin-api": "^0.17.2",
"@backstage/plugin-catalog-react": "^3.1.0",
"@backstage/ui": "^0.16.0",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"react-router-dom": "^6.30.2"
"react-router-dom": "^6.30.2",
"react-use": "^17.6.1"
},
"peerDependencies": {
"react": "^17 || ^18",
Expand Down
55 changes: 55 additions & 0 deletions plugins/gildi/src/actions/ActionsPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { screen } from '@testing-library/react';
import { renderInTestApp, TestApiProvider } from '@backstage/frontend-test-utils';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { ActionsPanel } from './ActionsPanel';

const catalogApi = {
getEntities: async ({ filter }: any) => {
if (filter.kind === 'Template') {
return {
items: [
{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
name: 'charter-a-practice',
title: 'Charter a practice',
description: 'Stand up a new practice and its guild — the Guild Hall dogfooding its own model.',
tags: ['guild-hall'],
},
spec: { type: 'guildhall-action', owner: 'group:default/team-devex' },
},
{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
name: 'establish-a-guild',
namespace: 'custom',
title: 'Establish a guild',
tags: ['guild-hall'],
},
spec: { type: 'guildhall-action', owner: 'group:default/team-devex' },
},
],
};
}
return { items: [] };
},
};

describe('ActionsPanel', () => {
it('renders a curated action as a button linking to its Create page', async () => {
await renderInTestApp(
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
<ActionsPanel />
</TestApiProvider>,
);

const link = (await screen.findByText('Charter a practice')).closest('a');
expect(link).toHaveAttribute('href', '/create/templates/default/charter-a-practice');

// a non-default namespace is preserved in the Create link
const nsLink = (await screen.findByText('Establish a guild')).closest('a');
expect(nsLink).toHaveAttribute('href', '/create/templates/custom/establish-a-guild');
});
});
40 changes: 40 additions & 0 deletions plugins/gildi/src/actions/ActionsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { ReactNode } from 'react';
import { Typography } from '@material-ui/core';
import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline';
import { LinkButton, Progress, ResponseErrorPanel } from '@backstage/core-components';
import { useActions } from './useActions';

export function ActionsPanel() {
const { actions, loading, error } = useActions();
let body: ReactNode;
if (loading) {
body = <Progress />;
} else if (error) {
body = <ResponseErrorPanel error={error} />;
} else if (actions.length === 0) {
body = <Typography variant="body2" color="textSecondary">No actions yet.</Typography>;
} else {
body = (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 8 }}>
{actions.map(a => (
<LinkButton
key={a.createHref}
to={a.createHref}
size="small"
variant="outlined"
startIcon={<AddCircleOutlineIcon fontSize="small" />}
title={a.description}
>
{a.title}
</LinkButton>
))}
</div>
);
}
return (
<div>
<Typography variant="overline" color="textSecondary">Actions</Typography>
{body}
</div>
);
}
Loading