Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 11 additions & 19 deletions .github/workflows/project-meta-sync.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Projects • Add & Sync meta from labels

on:
push:
branches: [develop]
issues:
types: [opened, edited, labeled, unlabeled, reopened, closed]
pull_request:
Expand All @@ -11,11 +9,11 @@ on:
[
opened,
edited,
synchronize,
labeled,
unlabeled,
reopened,
ready_for_review,
synchronize,
closed,
]

Expand Down Expand Up @@ -91,22 +89,13 @@ jobs:
- name: Collect labels from item
id: collect-labels
if: steps.preflight.outputs.enabled == 'true'
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "issues" ]; then
NUMBER=${{ github.event.issue.number }}
gh issue view "$NUMBER" --json labels --jq '.labels[].name' > /tmp/labels.txt
else
NUMBER=${{ github.event.pull_request.number }}
gh pr view "$NUMBER" --json labels --jq '.labels[].name' > /tmp/labels.txt
fi
{
echo 'labels<<EOF'
cat /tmp/labels.txt
echo EOF
} >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const labels = context.payload.issue?.labels || context.payload.pull_request?.labels || [];
const labelNames = labels.map((label) => label.name).filter(Boolean).join('\n');
core.setOutput('labels', labelNames);
Comment on lines +96 to +98

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read live labels before updating project fields

For opened/edited/synchronize runs, this now uses the webhook snapshot rather than the item's current labels. Since labeling.yml runs independently on the same events and later label events also trigger this workflow, an older run that captured no labels can finish after the label-triggered run and overwrite the project fields with fallback/default values. Please fetch the current labels at update time or add per-item concurrency/cancellation before deriving fields.

Useful? React with 👍 / 👎.


- name: Derive Status/Priority/Type from canonical issue fields
id: derive
Expand All @@ -119,6 +108,9 @@ jobs:
PR_MERGED: ${{ github.event.pull_request.merged }}
ITEM_CREATED_AT: ${{ github.event.issue.created_at || github.event.pull_request.created_at }}
ITEM_MILESTONE_DUE_ON: ${{ github.event.issue.milestone.due_on || github.event.pull_request.milestone.due_on }}
ITEM_TITLE: ${{ github.event.issue.title || github.event.pull_request.title }}
ITEM_BODY: ${{ github.event.issue.body || github.event.pull_request.body }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref || '' }}
ISSUE_FIELDS_CONFIG: .github/issue-fields.yml

- name: Update project fields
Expand Down
3 changes: 2 additions & 1 deletion agents/project-meta-sync.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ permissions:
- github:issues
metadata:
guardrails: Compatibility only. Do not treat this spec as the active contract;
use the workflow and helper scripts referenced above.
use the workflow and helper scripts referenced above. The live contract keeps
issue labels, issue types, and project fields in sync via project-meta sync.
---
6 changes: 3 additions & 3 deletions docs/AUTOMATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
file_type: "documentation"
title: "Automation & Workflows"
description: "Strategy, governance, and workflow documentation for GitHub automation in LightSpeed repositories."
version: "v1.0.4"
last_updated: "2026-06-18"
version: "v1.0.5"
last_updated: "2026-06-19"
owners: ["LightSpeedWP Team"]
tags: ["automation", "workflows", "governance", "agents"]
status: "active"
Expand Down Expand Up @@ -75,7 +75,7 @@ If your project allows hotfixes directly to `main`, ensure validation workflows
| **metadata-governance.yml** | issues / pull_request_target | Apply assignee, milestone, and relationship metadata | issue-pr-metadata.cjs |
| **planner.yml** | develop | Post merge-readiness checklists and exit criteria to PRs | planner.agent.js |
| **reviewer.yml** | develop | Automated PR review and quality feedback | reviewer.agent.js |
| **project-meta-sync.yml** | push / issues / pull_request | Sync project board fields from labels and kickoff metadata | derive-project-fields.cjs |
| **project-meta-sync.yml** | issues / pull_request | Sync project board fields from labels, title/body fallbacks, and kickoff metadata | derive-project-fields.cjs |
| **checklist-finalisation.yml** | issues.closed / pull_request_target.closed | Final checklist sync for completed issues and merged PRs | workflow backstop |
| **release.yml** | main | Versioning, changelog generation, tagging, and release notes | release.agent.js |
| **reporting.yml** | develop | Generate metrics and activity reports | reporting.agent.js |
Expand Down
7 changes: 6 additions & 1 deletion docs/CANONICAL_CONFIGS_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
file_type: documentation
title: Canonical Config File Interdependencies Guide
description: Canonical reference for how labels.yml, issue-types.yml, labeler.yml, and issue-fields.yml interact from issue creation through automation completion.
version: v1.0.3
version: v1.0.4
created_date: "2026-06-03"
last_updated: "2026-06-19"
authors:
Expand Down Expand Up @@ -34,6 +34,10 @@ This guide documents the canonical relationship between:

It also maps end-to-end data flow from issue/PR creation to automation completion.

Project metadata sync is label-driven, but it also treats issue type selection
and safe content/branch fallbacks as first-class signals so project fields can
catch up when labels are added after creation.
Comment on lines +37 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing comma in compound sentence.

Line 37–39 joins two independent clauses with "so", but a comma is needed before the conjunction. The sentence currently reads: "…first-class signals so project fields can catch up…" but should read: "…first-class signals, so project fields can catch up…"

🔧 Proposed fix
-Project metadata sync is label-driven, but it also treats issue type selection
-and safe content/branch fallbacks as first-class signals so project fields can
-catch up when labels are added after creation.
+Project metadata sync is label-driven, but it also treats issue type selection
+and safe content/branch fallbacks as first-class signals, so project fields can
+catch up when labels are added after creation.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Project metadata sync is label-driven, but it also treats issue type selection
and safe content/branch fallbacks as first-class signals so project fields can
catch up when labels are added after creation.
Project metadata sync is label-driven, but it also treats issue type selection
and safe content/branch fallbacks as first-class signals, so project fields can
catch up when labels are added after creation.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~38-~38: Possible missing comma found.
Context: ...content/branch fallbacks as first-class signals so project fields can catch up when lab...

(AI_HYDRA_LEO_MISSING_COMMA)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/CANONICAL_CONFIGS_GUIDE.md` around lines 37 - 39, In the section
describing project metadata sync (lines 37-39 in CANONICAL_CONFIGS_GUIDE.md),
add a comma before the conjunction "so" in the compound sentence. The text
currently reads "first-class signals so project fields can catch up" but should
be corrected to "first-class signals, so project fields can catch up" to
properly punctuate the join between two independent clauses.

Source: Linters/SAST tools


## Canonical Roles

| File | Canonical Role | Primary Consumers |
Expand Down Expand Up @@ -89,6 +93,7 @@ sequenceDiagram
2. `status:*` and `priority:*` mappings in `.github/issue-fields.yml` must resolve to labels defined in `.github/labels.yml`.
3. `.github/labeler.yml` may only emit canonical labels defined in `.github/labels.yml`.
4. `.github/issue-types.yml` display types should map to canonical `type:*` labels that can be projected into project field Type values.
5. `project-meta-sync.yml` should reprocess label changes so late `type:*` labels or corrected issue types update the project field projection.

## Current Risks Observed

Expand Down
7 changes: 4 additions & 3 deletions docs/GITHUB_PROJECT_OPERATIONS_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,14 @@ Template labels must remain canonical and pass:
- `Priority`
- `Type`
- `Effort`
- `Start date` and `Target date` only when `status:ready` or `status:in-progress` is present
- `Start date` and `Target date` when `status:ready` or `status:in-progress` is present

Derivation source notes:

- `Status`, `Priority`, and `Type` are mapped from labels via `.github/issue-fields.yml` mappings.
- `Effort` uses the configured default from canonical issue-fields configuration.
- `Start date` and `Target date` stay empty at creation time and are only populated after kickoff-ready metadata is present (`status:ready` or `status:in-progress`).
- `Type` and `Priority` now fall back to safe content-based inference when labels are missing or late.
- `Start date` and `Target date` are populated when kickoff-ready metadata is present (`status:ready` or `status:in-progress`) and the workflow reprocesses label changes so fields can catch up after triage.
- Metadata governance for assignees, milestones, and relationships is handled separately by `.github/workflows/metadata-governance.yml`.
Comment on lines 88 to 92

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing comma in compound sentence (line ~91).

The clause "the workflow reprocesses label changes so fields can catch up after triage" needs a comma before "so" to properly join two independent clauses. It should read: "…label changes, so fields can catch up…"

🔧 Proposed fix
-- `Type` and `Priority` now fall back to safe content-based inference when labels are missing or late.
-- `Start date` and `Target date` are populated when kickoff-ready metadata is present (`status:ready` or `status:in-progress`) and the workflow reprocesses label changes so fields can catch up after triage.
+- `Type` and `Priority` now fall back to safe content-based inference when labels are missing or late.
+- `Start date` and `Target date` are populated when kickoff-ready metadata is present (`status:ready` or `status:in-progress`), and the workflow reprocesses label changes, so fields can catch up after triage.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `Status`, `Priority`, and `Type` are mapped from labels via `.github/issue-fields.yml` mappings.
- `Effort` uses the configured default from canonical issue-fields configuration.
- `Start date` and `Target date` stay empty at creation time and are only populated after kickoff-ready metadata is present (`status:ready` or `status:in-progress`).
- `Type` and `Priority` now fall back to safe content-based inference when labels are missing or late.
- `Start date` and `Target date` are populated when kickoff-ready metadata is present (`status:ready` or `status:in-progress`) and the workflow reprocesses label changes so fields can catch up after triage.
- Metadata governance for assignees, milestones, and relationships is handled separately by `.github/workflows/metadata-governance.yml`.
- `Status`, `Priority`, and `Type` are mapped from labels via `.github/issue-fields.yml` mappings.
- `Effort` uses the configured default from canonical issue-fields configuration.
- `Type` and `Priority` now fall back to safe content-based inference when labels are missing or late.
- `Start date` and `Target date` are populated when kickoff-ready metadata is present (`status:ready` or `status:in-progress`), and the workflow reprocesses label changes, so fields can catch up after triage.
- Metadata governance for assignees, milestones, and relationships is handled separately by `.github/workflows/metadata-governance.yml`.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~88-~88: Loose punctuation mark.
Context: ...nt Derivation source notes: - Status, Priority, and Type are mapped from ...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~88-~88: The official name of this software platform is spelled with a capital “H”.
Context: ..., and Type are mapped from labels via .github/issue-fields.yml mappings. - Effort ...

(GITHUB)


[uncategorized] ~91-~91: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...d the workflow reprocesses label changes so fields can catch up after triage. - Met...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~92-~92: The official name of this software platform is spelled with a capital “H”.
Context: ... relationships is handled separately by .github/workflows/metadata-governance.yml. Cu...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/GITHUB_PROJECT_OPERATIONS_SPEC.md` around lines 88 - 92, In the bullet
point about "Start date" and "Target date" population, locate the text "the
workflow reprocesses label changes so fields can catch up after triage" and add
a comma before the word "so" to properly join the two independent clauses. The
corrected text should read "the workflow reprocesses label changes, so fields
can catch up after triage".

Source: Linters/SAST tools


Current preflight conditions must be satisfied before sync runs:
Expand All @@ -98,7 +99,7 @@ Current preflight conditions must be satisfied before sync runs:

Safe automation boundary:

- Active write path is limited to the five core derived fields plus kickoff-aware date handling.
- Active write path covers the five core derived fields plus kickoff-aware date handling.
- Additional direct issue-field writes are out of scope until a dedicated follow-up verification approves extension.

Verification reference: `.github/reports/audits/2026-06-07-private-project-issue-field-write-verification-879.md`.
Expand Down
6 changes: 3 additions & 3 deletions docs/ISSUE_CREATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
title: GitHub Issue Creation Guide
description: How to create well-formed issues, select templates, and trigger automation
file_type: documentation
version: "1.0.5"
version: "1.0.6"
created_date: "2026-05-31"
last_updated: "2026-06-18"
last_updated: "2026-06-19"
author: Claude Code
maintainer: Ash Shaw
owners:
Expand Down Expand Up @@ -122,7 +122,7 @@ Click **Submit new issue**. Your issue is now visible to the team and ready for
- Unified labeling agent applies canonicalization, one-hot enforcement, defaults, and content-based type detection
- PR automation remains stronger due to branch/file signals available in PR context
- `metadata-governance.yml` automatically adds new issues and PRs to the configured project, assigns the requester when possible, attaches or creates an appropriate milestone, and records relationships when they are present in the body
- `project-meta-sync.yml` keeps the project fields in sync and leaves `Start date` and `Target date` empty until work is explicitly marked `status:ready` or `status:in-progress`
- `project-meta-sync.yml` keeps the project fields in sync, re-runs on label changes, and backfills `Status`, `Priority`, `Type`, `Effort`, `Start date`, and `Target date` from canonical labels plus safe fallbacks

### ⚠️ Practical Implication

Expand Down
13 changes: 8 additions & 5 deletions docs/ISSUE_FIELDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
title: Issue Fields Specification
description: Canonical specification for GitHub organization issue fields, type mappings, and project automation configuration
file_type: documentation
version: v1.0.5
version: v1.0.6
created_date: '2026-05-31'
last_updated: '2026-06-18'
last_updated: '2026-06-19'
authors:
- LightSpeed Team
maintainer: LightSpeed Team
Expand Down Expand Up @@ -436,10 +436,13 @@ The following project field features are enabled across all LightSpeed repositor

Managed via `.github/workflows/project-meta-sync.yml` automation.

Current live write boundary (verified 2026-06-07):
Current workflow contract:

- Workflow writes `Status`, `Priority`, `Type`, `Effort`, and `Start date`.
- `Target date` remains part of canonical config and governance but is currently deferred from automated writes.
- Workflow writes `Status`, `Priority`, `Type`, `Effort`, `Start date`, and `Target date`.
- `Start date` and `Target date` are only populated when kickoff metadata is present.
- If labels arrive after creation, the sync workflow reprocesses the item and backfills
`Type` and `Priority` from canonical labels, issue type intent, or safe content
fallbacks.

Verification record: `.github/reports/audits/2026-06-07-private-project-issue-field-write-verification-879.md`.

Expand Down
6 changes: 3 additions & 3 deletions docs/WORKFLOW_COORDINATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Workflow Coordination Patterns"
description: "Canonical reference for GitHub Actions workflow patterns: always-run vs. agent-triggered, coordination between agents and workflows, and orchestration strategies."
created_date: "2026-05-28"
last_updated: "2026-06-18"
version: "v1.1.2"
last_updated: "2026-06-19"
version: "v1.1.3"
file_type: "documentation"
maintainer: "LightSpeed Team"
tags: ["workflows", "automation", "agents", "coordination", "ci-cd"]
Expand Down Expand Up @@ -49,7 +49,7 @@ Always-run workflows trigger automatically on push/PR events without agent invol
| `issues.yml` | issue opened/edited | Validate issue templates | ❌ No (validation only) |
| `meta.yml` | PR opened/issues | Apply frontmatter validation | ❌ No (metadata only) |
| `metadata-governance.yml` | issues / pull_request_target | Assign assignees, milestones, and relationship metadata | ❌ No (metadata only) |
| `project-meta-sync.yml` | push / issues / pull_request | Sync GitHub Project board fields | ❌ No (metadata only) |
| `project-meta-sync.yml` | issues / pull_request | Sync GitHub Project board fields | ❌ No (metadata only) |
| `readme-regen.yml` | push/PR on `.md` files | Validate/regenerate README indices | ❌ No (informational) |

### When to Use
Expand Down
10 changes: 9 additions & 1 deletion instructions/project-meta-sync.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Deprecated compatibility guidance for the legacy project meta sync
entrypoint. The active contract lives in the project-meta-sync and
metadata-governance workflows plus their helper scripts.
version: v1.1
last_updated: '2026-05-29'
last_updated: '2026-06-19'
owners:
- LightSpeed Engineering
tags:
Expand All @@ -23,4 +23,12 @@ apply_to:
- .github/workflows/metadata-governance.yml
- scripts/agents/includes/derive-project-fields.cjs
- scripts/agents/includes/issue-pr-metadata.cjs

## Active Contract Summary

- Keep `status:*`, `priority:*`, and `type:*` labels canonical and one-hot.
- Treat issue types as the source of truth for canonical `type:*` intent and let
`project-meta-sync.yml` project that intent into GitHub Project fields.
- Allow the workflow to re-run on label changes so late-applied labels and issue
type corrections catch up in the project board.
Comment on lines +29 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move the summary outside the frontmatter block

These bullets are still before the closing ---, so YAML treats them as additional apply_to entries instead of rendered instruction content. That pollutes the instruction metadata with prose targets and hides the new contract summary from readers/processors expecting it in the Markdown body; close the frontmatter before the ## Active Contract Summary section.

Useful? React with 👍 / 👎.

---
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const fs = require("fs");
const os = require("os");
const path = require("path");
const yaml = require("js-yaml");
const { deriveProjectFieldValues } = require("../derive-project-fields.cjs");
const {
deriveProjectFieldValues,
run,
} = require("../derive-project-fields.cjs");

const issueFieldsConfig = yaml.load(
fs.readFileSync(
Expand Down Expand Up @@ -48,4 +52,81 @@ describe("derive-project-fields.cjs", () => {
targetDate: "2026-07-18",
});
});

test("falls back to PR defaults when labels are missing", () => {
const result = deriveProjectFieldValues({
cfg: issueFieldsConfig,
labels: [],
eventName: "pull_request",
eventAction: "opened",
title: "Refine metadata sync behaviour",
body: "Improve how project metadata is allocated.",
});

expect(result).toMatchObject({
status: "In review",
priority: "Normal",
type: "Chore",
startDate: "",
targetDate: "",
});
});

test("infers type and priority from content when labels are missing", () => {
const result = deriveProjectFieldValues({
cfg: issueFieldsConfig,
labels: [],
eventName: "pull_request",
eventAction: "opened",
title: "Urgent bug: project metadata stays blank",
body: "This bug blocks release and should be fixed immediately.",
headRef: "fix/metadata-governance-sync",
});

expect(result).toMatchObject({
status: "In review",
priority: "Critical",
type: "Bug",
});
});

test("writes derived values to GITHUB_OUTPUT when run as a script", () => {
const tmpDir = fs.mkdtempSync(
path.join(os.tmpdir(), "derive-project-fields-"),
);
const outputPath = path.join(tmpDir, "github-output.txt");
const originalEnv = {
ISSUE_FIELDS_CONFIG: process.env.ISSUE_FIELDS_CONFIG,
EVENT_NAME: process.env.EVENT_NAME,
EVENT_ACTION: process.env.EVENT_ACTION,
LABELS: process.env.LABELS,
ITEM_TITLE: process.env.ITEM_TITLE,
ITEM_BODY: process.env.ITEM_BODY,
PR_HEAD_REF: process.env.PR_HEAD_REF,
GITHUB_OUTPUT: process.env.GITHUB_OUTPUT,
};

try {
process.env.EVENT_NAME = "pull_request";
process.env.EVENT_ACTION = "opened";
process.env.LABELS = "";
process.env.ITEM_TITLE = "Urgent bug: project metadata stays blank";
process.env.ITEM_BODY =
"This bug blocks release and should be fixed immediately.";
process.env.PR_HEAD_REF = "fix/metadata-governance-sync";
process.env.GITHUB_OUTPUT = outputPath;

run();
} finally {
for (const [key, value] of Object.entries(originalEnv)) {
if (typeof value === "undefined") delete process.env[key];
else process.env[key] = value;
}
}

const output = fs.readFileSync(outputPath, "utf8");
expect(output).toContain("status=In review");
expect(output).toContain("priority=Critical");
expect(output).toContain("type=Bug");
});
});
Loading
Loading