Merge remote-tracking branch 'origin/polecat/valkyrie'
This commit is contained in:
1420
.beads/issues.jsonl
1420
.beads/issues.jsonl
File diff suppressed because it is too large
Load Diff
@@ -149,13 +149,23 @@ type InstantiateOptions struct {
|
|||||||
|
|
||||||
// InstantiateMolecule creates child issues from a molecule template.
|
// InstantiateMolecule creates child issues from a molecule template.
|
||||||
//
|
//
|
||||||
// For each step in the molecule, this creates:
|
// This function supports two molecule formats (format bridge pattern):
|
||||||
|
//
|
||||||
|
// 1. New format (child issues): If the molecule proto has child issues,
|
||||||
|
// those children are used as templates. Dependencies are copied from
|
||||||
|
// the template children's DependsOn relationships.
|
||||||
|
//
|
||||||
|
// 2. Old format (embedded markdown): If the molecule has no children,
|
||||||
|
// steps are parsed from the Description field using ParseMoleculeSteps().
|
||||||
|
// Dependencies are extracted from "Needs:" declarations in the markdown.
|
||||||
|
//
|
||||||
|
// For each step, this creates:
|
||||||
// - A child issue with ID "{parent.ID}.{step.Ref}"
|
// - A child issue with ID "{parent.ID}.{step.Ref}"
|
||||||
// - Title from step title
|
// - Title from step title
|
||||||
// - Description from step instructions (with template vars expanded)
|
// - Description from step instructions (with template vars expanded)
|
||||||
// - Type: task
|
// - Type: task
|
||||||
// - Priority: inherited from parent
|
// - Priority: inherited from parent
|
||||||
// - Dependencies wired according to Needs: declarations
|
// - Dependencies wired according to template
|
||||||
//
|
//
|
||||||
// The function is atomic via bd CLI - either all issues are created or none.
|
// The function is atomic via bd CLI - either all issues are created or none.
|
||||||
// Returns the created step issues.
|
// Returns the created step issues.
|
||||||
@@ -167,6 +177,95 @@ func (b *Beads) InstantiateMolecule(mol *Issue, parent *Issue, opts InstantiateO
|
|||||||
return nil, fmt.Errorf("parent issue is nil")
|
return nil, fmt.Errorf("parent issue is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FORMAT BRIDGE: Try new format first (child issues), fall back to old format (markdown)
|
||||||
|
templateChildren, err := b.List(ListOptions{
|
||||||
|
Parent: mol.ID,
|
||||||
|
Status: "all",
|
||||||
|
Priority: -1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// Non-fatal - might not have children, continue to old format
|
||||||
|
templateChildren = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(templateChildren) > 0 {
|
||||||
|
// NEW FORMAT: Use child issues as templates
|
||||||
|
return b.instantiateFromChildren(mol, parent, templateChildren, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OLD FORMAT: Parse steps from molecule description
|
||||||
|
return b.instantiateFromMarkdown(mol, parent, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// instantiateFromChildren creates steps from template child issues (new format).
|
||||||
|
func (b *Beads) instantiateFromChildren(mol *Issue, parent *Issue, templates []*Issue, opts InstantiateOptions) ([]*Issue, error) {
|
||||||
|
var createdIssues []*Issue
|
||||||
|
templateToNew := make(map[string]string) // template ID -> new issue ID
|
||||||
|
|
||||||
|
// First pass: create all child issues
|
||||||
|
for _, tmpl := range templates {
|
||||||
|
// Expand template variables in description
|
||||||
|
description := tmpl.Description
|
||||||
|
if opts.Context != nil {
|
||||||
|
description = ExpandTemplateVars(description, opts.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add provenance metadata
|
||||||
|
if description != "" {
|
||||||
|
description += "\n\n"
|
||||||
|
}
|
||||||
|
description += fmt.Sprintf("instantiated_from: %s\ntemplate_step: %s", mol.ID, tmpl.ID)
|
||||||
|
|
||||||
|
// Create the child issue
|
||||||
|
childOpts := CreateOptions{
|
||||||
|
Title: tmpl.Title,
|
||||||
|
Type: tmpl.Type,
|
||||||
|
Priority: parent.Priority,
|
||||||
|
Description: description,
|
||||||
|
Parent: parent.ID,
|
||||||
|
}
|
||||||
|
if childOpts.Type == "" {
|
||||||
|
childOpts.Type = "task"
|
||||||
|
}
|
||||||
|
|
||||||
|
child, err := b.Create(childOpts)
|
||||||
|
if err != nil {
|
||||||
|
// Attempt to clean up created issues on failure
|
||||||
|
for _, created := range createdIssues {
|
||||||
|
_ = b.Close(created.ID)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("creating step from template %q: %w", tmpl.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdIssues = append(createdIssues, child)
|
||||||
|
templateToNew[tmpl.ID] = child.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: wire dependencies based on template dependencies
|
||||||
|
for _, tmpl := range templates {
|
||||||
|
if len(tmpl.DependsOn) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newChildID := templateToNew[tmpl.ID]
|
||||||
|
for _, depTemplateID := range tmpl.DependsOn {
|
||||||
|
newDepID, ok := templateToNew[depTemplateID]
|
||||||
|
if !ok {
|
||||||
|
// Dependency points outside the template - skip
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := b.AddDependency(newChildID, newDepID); err != nil {
|
||||||
|
// Log but don't fail - the issues are created
|
||||||
|
return createdIssues, fmt.Errorf("adding dependency %s -> %s: %w", newChildID, newDepID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdIssues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// instantiateFromMarkdown creates steps from embedded markdown (old format).
|
||||||
|
func (b *Beads) instantiateFromMarkdown(mol *Issue, parent *Issue, opts InstantiateOptions) ([]*Issue, error) {
|
||||||
// Parse steps from molecule
|
// Parse steps from molecule
|
||||||
steps, err := ParseMoleculeSteps(mol.Description)
|
steps, err := ParseMoleculeSteps(mol.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -257,6 +356,13 @@ func (b *Beads) InstantiateMolecule(mol *Issue, parent *Issue, opts InstantiateO
|
|||||||
|
|
||||||
// ValidateMolecule checks if an issue is a valid molecule definition.
|
// ValidateMolecule checks if an issue is a valid molecule definition.
|
||||||
// Returns an error describing the problem, or nil if valid.
|
// Returns an error describing the problem, or nil if valid.
|
||||||
|
//
|
||||||
|
// Note: This function only validates the old format (embedded markdown steps).
|
||||||
|
// For new format molecules (with child issues), validation is implicit during
|
||||||
|
// instantiation - if the molecule has children, those are used as templates.
|
||||||
|
// Use InstantiateMolecule directly for new format molecules; this function
|
||||||
|
// will report "no steps defined" for new format molecules since it cannot
|
||||||
|
// access child issues without a Beads client.
|
||||||
func ValidateMolecule(mol *Issue) error {
|
func ValidateMolecule(mol *Issue) error {
|
||||||
if mol == nil {
|
if mol == nil {
|
||||||
return fmt.Errorf("molecule is nil")
|
return fmt.Errorf("molecule is nil")
|
||||||
|
|||||||
Reference in New Issue
Block a user