Integration Examples¶
Examples of integrating structured-requirements with other tools and workflows.
CI/CD Integration¶
GitHub Actions¶
Validate PRDs in your CI pipeline:
name: PRD Validation
on:
pull_request:
paths:
- '**/*.prd.json'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Install validator
run: go install github.com/grokify/structured-plan/cmd/srequirements@latest
- name: Validate PRDs
run: |
for file in $(find . -name "*.prd.json"); do
echo "Validating $file..."
srequirements validate "$file"
done
Scoring Gate¶
Block PRs with low-scoring PRDs:
- name: Score PRD
run: |
SCORE=$(srequirements score docs/feature.prd.json --output=score)
if [ "$SCORE" -lt 70 ]; then
echo "PRD score ($SCORE%) below threshold (70%)"
exit 1
fi
Slack Notifications¶
Send PRD summaries to Slack:
package main
import (
"bytes"
"encoding/json"
"net/http"
"github.com/grokify/structured-plan/prd"
)
func notifySlack(doc *prd.Document, webhookURL string) error {
scores := prd.Score(doc)
execView := prd.GenerateExecView(doc, scores)
message := map[string]interface{}{
"blocks": []map[string]interface{}{
{
"type": "header",
"text": map[string]string{
"type": "plain_text",
"text": "PRD Review: " + doc.Metadata.Title,
},
},
{
"type": "section",
"fields": []map[string]string{
{"type": "mrkdwn", "text": "*Decision:*\n" + execView.Decision},
{"type": "mrkdwn", "text": "*Score:*\n" + execView.ScoreSummary},
},
},
},
}
body, _ := json.Marshal(message)
_, err := http.Post(webhookURL, "application/json", bytes.NewReader(body))
return err
}
Notion Integration¶
Export PRD to Notion:
package main
import (
"github.com/grokify/structured-plan/prd"
// hypothetical notion client
"github.com/example/notion-go"
)
func exportToNotion(doc *prd.Document, pageID string) error {
client := notion.NewClient(os.Getenv("NOTION_TOKEN"))
// Generate PM view markdown
pmView := prd.GeneratePMView(doc)
markdown := prd.RenderPMMarkdown(pmView)
// Create Notion page
return client.CreatePage(notion.Page{
Parent: notion.PageID(pageID),
Title: doc.Metadata.Title,
Content: markdown,
Properties: map[string]interface{}{
"Status": doc.Metadata.Status,
"Version": doc.Metadata.Version,
"Author": doc.Metadata.Authors[0].Name,
},
})
}
Jira Integration¶
Create Jira epics from PRD requirements:
func createJiraEpics(doc *prd.Document) error {
client := jira.NewClient(os.Getenv("JIRA_URL"), os.Getenv("JIRA_TOKEN"))
for _, req := range doc.Requirements.Functional {
if req.MoSCoW == prd.MoSCoWMust {
epic := jira.Epic{
Summary: req.Title,
Description: req.Description,
Labels: []string{"prd:" + doc.Metadata.ID},
Priority: mapPriority(req.Priority),
}
if err := client.CreateEpic(epic); err != nil {
return err
}
}
}
return nil
}
Confluence Export¶
Publish 6-pager to Confluence:
func publishToConfluence(doc *prd.Document, spaceKey, parentID string) error {
client := confluence.NewClient(
os.Getenv("CONFLUENCE_URL"),
os.Getenv("CONFLUENCE_TOKEN"),
)
// Generate 6-pager
sixPager := prd.GenerateSixPagerView(doc)
content := prd.RenderSixPagerMarkdown(sixPager)
return client.CreatePage(confluence.Page{
SpaceKey: spaceKey,
ParentID: parentID,
Title: doc.Metadata.Title + " - 6-Pager",
Body: content,
})
}
Goals Sync¶
Sync PRD metrics with OKR tracking:
func syncWithOKRTool(doc *prd.Document) error {
if doc.Goals == nil || doc.Goals.OKR == nil {
return nil
}
client := okrtool.NewClient(os.Getenv("OKR_API_KEY"))
for _, obj := range doc.Goals.OKR.Objectives {
for _, kr := range obj.KeyResults {
// Update KR progress in external tool
err := client.UpdateKeyResult(kr.ID, okrtool.Update{
Score: kr.Score,
Current: kr.Current,
})
if err != nil {
return err
}
}
}
return nil
}
Automated Reviews¶
Weekly PRD review automation:
func weeklyReview() {
prds, _ := filepath.Glob("prds/*.prd.json")
var report strings.Builder
report.WriteString("# Weekly PRD Review\n\n")
for _, path := range prds {
doc, _ := prd.Load(path)
scores := prd.Score(doc)
report.WriteString(fmt.Sprintf("## %s\n", doc.Metadata.Title))
report.WriteString(fmt.Sprintf("- Score: %.0f%%\n", scores.OverallScore*100))
report.WriteString(fmt.Sprintf("- Decision: %s\n", scores.Decision))
if len(scores.Triggers) > 0 {
report.WriteString("- Issues:\n")
for _, t := range scores.Triggers {
report.WriteString(fmt.Sprintf(" - %s\n", t.Issue))
}
}
report.WriteString("\n")
}
// Send report
sendEmail("team@example.com", "Weekly PRD Review", report.String())
}
Template Generation¶
Generate PRD from templates:
func generateFromTemplate(templateName string, data map[string]string) *prd.Document {
templates := map[string]func(map[string]string) *prd.Document{
"feature": createFeaturePRD,
"platform": createPlatformPRD,
"api": createAPIPRD,
}
generator := templates[templateName]
if generator == nil {
generator = createFeaturePRD
}
return generator(data)
}
func createFeaturePRD(data map[string]string) *prd.Document {
doc := prd.New(prd.GenerateID(), data["title"],
prd.Person{Name: data["author"]})
doc.ExecutiveSummary.ProblemStatement = data["problem"]
doc.ExecutiveSummary.ProposedSolution = data["solution"]
return doc
}
Watch Mode¶
Auto-validate on file changes:
func watchPRDs(dir string) {
watcher, _ := fsnotify.NewWatcher()
defer watcher.Close()
watcher.Add(dir)
for {
select {
case event := <-watcher.Events:
if strings.HasSuffix(event.Name, ".prd.json") {
if event.Op&fsnotify.Write == fsnotify.Write {
validateAndNotify(event.Name)
}
}
case err := <-watcher.Errors:
log.Println("error:", err)
}
}
}
func validateAndNotify(path string) {
doc, err := prd.Load(path)
if err != nil {
notify("Validation Error", err.Error())
return
}
result := prd.Validate(doc)
if !result.Valid {
notify("PRD Invalid", formatErrors(result.Errors))
}
}