ace-retro 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-retro.yml +19 -0
- data/.ace-defaults/retro/config.yml +16 -0
- data/CHANGELOG.md +252 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/Rakefile +13 -0
- data/docs/demo/ace-retro-getting-started.gif +0 -0
- data/docs/demo/ace-retro-getting-started.tape.yml +33 -0
- data/docs/demo/fixtures/README.md +3 -0
- data/docs/demo/fixtures/sample.txt +1 -0
- data/docs/getting-started.md +77 -0
- data/docs/handbook.md +60 -0
- data/docs/usage.md +141 -0
- data/exe/ace-retro +22 -0
- data/handbook/skills/as-handbook-selfimprove/SKILL.md +31 -0
- data/handbook/skills/as-retro-create/SKILL.md +26 -0
- data/handbook/skills/as-retro-synthesize/SKILL.md +26 -0
- data/handbook/templates/retro/retro.template.md +194 -0
- data/handbook/workflow-instructions/retro/create.wf.md +141 -0
- data/handbook/workflow-instructions/retro/selfimprove.wf.md +197 -0
- data/handbook/workflow-instructions/retro/synthesize.wf.md +94 -0
- data/lib/ace/retro/atoms/retro_file_pattern.rb +40 -0
- data/lib/ace/retro/atoms/retro_frontmatter_defaults.rb +42 -0
- data/lib/ace/retro/atoms/retro_id_formatter.rb +37 -0
- data/lib/ace/retro/atoms/retro_validation_rules.rb +82 -0
- data/lib/ace/retro/cli/commands/create.rb +87 -0
- data/lib/ace/retro/cli/commands/doctor.rb +204 -0
- data/lib/ace/retro/cli/commands/list.rb +63 -0
- data/lib/ace/retro/cli/commands/show.rb +55 -0
- data/lib/ace/retro/cli/commands/update.rb +117 -0
- data/lib/ace/retro/cli.rb +70 -0
- data/lib/ace/retro/models/retro.rb +40 -0
- data/lib/ace/retro/molecules/retro_config_loader.rb +93 -0
- data/lib/ace/retro/molecules/retro_creator.rb +165 -0
- data/lib/ace/retro/molecules/retro_display_formatter.rb +95 -0
- data/lib/ace/retro/molecules/retro_doctor_fixer.rb +404 -0
- data/lib/ace/retro/molecules/retro_doctor_reporter.rb +257 -0
- data/lib/ace/retro/molecules/retro_frontmatter_validator.rb +120 -0
- data/lib/ace/retro/molecules/retro_loader.rb +119 -0
- data/lib/ace/retro/molecules/retro_mover.rb +80 -0
- data/lib/ace/retro/molecules/retro_resolver.rb +57 -0
- data/lib/ace/retro/molecules/retro_scanner.rb +56 -0
- data/lib/ace/retro/molecules/retro_structure_validator.rb +193 -0
- data/lib/ace/retro/organisms/retro_doctor.rb +199 -0
- data/lib/ace/retro/organisms/retro_manager.rb +210 -0
- data/lib/ace/retro/version.rb +7 -0
- data/lib/ace/retro.rb +41 -0
- metadata +165 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: workflow
|
|
3
|
+
title: Self-Improve Workflow
|
|
4
|
+
purpose: Documentation for ace-retro/handbook/workflow-instructions/retro/selfimprove.wf.md
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-03-01
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Self-Improve Workflow
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
Transform mistakes and recurring issues into system improvements. Fix the process first, then fix the immediate issue.
|
|
15
|
+
|
|
16
|
+
## Anti-Pattern
|
|
17
|
+
|
|
18
|
+
❌ Mistake happens → Agent re-runs instruction → Same mistake can happen again
|
|
19
|
+
|
|
20
|
+
## Correct Pattern
|
|
21
|
+
|
|
22
|
+
✅ Mistake identified → Analyze root cause → Update process → Fix immediate issue → Record retro
|
|
23
|
+
|
|
24
|
+
## Input Sources
|
|
25
|
+
|
|
26
|
+
Self-improvement is always a consequence of retrospective. Input can come from:
|
|
27
|
+
|
|
28
|
+
- **Session context** — analyze current conversation for mistakes or suboptimal patterns
|
|
29
|
+
- **Existing retros** — load retros via `ace-retro list`/`ace-retro show` to find recurring issues
|
|
30
|
+
- **User input** — user describes what went wrong
|
|
31
|
+
|
|
32
|
+
## Process Steps
|
|
33
|
+
|
|
34
|
+
### Step 1: Gather Input
|
|
35
|
+
|
|
36
|
+
Determine the source and capture incident details:
|
|
37
|
+
|
|
38
|
+
**From session context:**
|
|
39
|
+
- Review the current conversation for mistakes, repeated attempts, or corrections
|
|
40
|
+
- Identify what went wrong and what should have happened
|
|
41
|
+
|
|
42
|
+
**From existing retros:**
|
|
43
|
+
```bash
|
|
44
|
+
# Find retros with relevant issues
|
|
45
|
+
ace-retro list --status active
|
|
46
|
+
|
|
47
|
+
# Load specific retro content
|
|
48
|
+
ace-retro show REF --content
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**From user input:**
|
|
52
|
+
- Capture the user's description of the problem
|
|
53
|
+
|
|
54
|
+
Document the incident:
|
|
55
|
+
|
|
56
|
+
| Question | Details |
|
|
57
|
+
|----------|---------|
|
|
58
|
+
| **What happened** | What action was taken? |
|
|
59
|
+
| **Actual result** | What was the output? |
|
|
60
|
+
| **Expected result** | What should have happened? |
|
|
61
|
+
| **Source** | Session / retro REF / user description |
|
|
62
|
+
|
|
63
|
+
### Step 2: Identify Root Cause Category
|
|
64
|
+
|
|
65
|
+
Ask: "Why did this happen?" Categorize the root cause:
|
|
66
|
+
|
|
67
|
+
| Category | Description | Example |
|
|
68
|
+
|----------|-------------|---------|
|
|
69
|
+
| **Ambiguous instructions** | Workflow allows misinterpretation | "Reorganize commits" without specifying scope source |
|
|
70
|
+
| **Missing validation** | No checkpoint to catch the error | No step to verify scope before executing |
|
|
71
|
+
| **Assumed context** | Agent didn't have necessary information | Agent used plan data instead of querying actual state |
|
|
72
|
+
| **Scope narrowing** | Agent under-scoped the task | Followed plan literally instead of understanding intent |
|
|
73
|
+
| **Scope creep** | Agent over-scoped the task | Made changes beyond what was requested |
|
|
74
|
+
| **Missing example** | No example of correct behavior | Workflow lacks example showing full scope discovery |
|
|
75
|
+
| **Redundant computation** | Multiple agents derive same value independently, causing divergence | Orchestrator computes path one way, agent re-derives differently |
|
|
76
|
+
|
|
77
|
+
### Step 3: Find the Source
|
|
78
|
+
|
|
79
|
+
Search for the relevant process files that need updating:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Search workflow instructions
|
|
83
|
+
ace-bundle wfi://{relevant-workflow}
|
|
84
|
+
|
|
85
|
+
# Search guides
|
|
86
|
+
ace-bundle guide://{relevant-guide}
|
|
87
|
+
|
|
88
|
+
# Search skills
|
|
89
|
+
ace-bundle skill://{relevant-skill}
|
|
90
|
+
|
|
91
|
+
# Discover available resources
|
|
92
|
+
ace-nav --sources
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Search targets (in preference order):**
|
|
96
|
+
|
|
97
|
+
1. **Workflow instructions** (`wfi://namespace/action`) — preferred for process improvements
|
|
98
|
+
2. **Guides** (`guide://topic`) — preferred for best practices and conventions
|
|
99
|
+
3. **Skills** (`.claude/skills/*/SKILL.md`) — only when workflow/guide doesn't exist
|
|
100
|
+
4. **CLAUDE.md files** — project-level overrides only
|
|
101
|
+
|
|
102
|
+
### Step 4: Draft the Fix
|
|
103
|
+
|
|
104
|
+
Propose specific edits. The fix should:
|
|
105
|
+
|
|
106
|
+
1. **Address the root cause** — not just the symptom
|
|
107
|
+
2. **Be minimal** — only change what's necessary
|
|
108
|
+
3. **Include validation** — add checkpoints where missing
|
|
109
|
+
4. **Add examples** — show correct behavior if unclear
|
|
110
|
+
|
|
111
|
+
**Fix templates by category:**
|
|
112
|
+
|
|
113
|
+
**For ambiguous instructions:**
|
|
114
|
+
```markdown
|
|
115
|
+
## After
|
|
116
|
+
**Scope Discovery**: Before executing, always query the actual state:
|
|
117
|
+
- Run the relevant query command to get the full scope
|
|
118
|
+
- Do NOT rely on estimates — query actual state
|
|
119
|
+
- Confirm scope with user if actual differs from expectations
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**For missing validation:**
|
|
123
|
+
```markdown
|
|
124
|
+
## After
|
|
125
|
+
### Validate Before Executing
|
|
126
|
+
- [ ] Query actual state
|
|
127
|
+
- [ ] Compare to expected scope
|
|
128
|
+
- [ ] If mismatch, confirm with user before proceeding
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**For redundant computation:**
|
|
132
|
+
```markdown
|
|
133
|
+
## After
|
|
134
|
+
Pass computed values explicitly; don't re-derive:
|
|
135
|
+
- Orchestrator computes once and passes to subagent
|
|
136
|
+
- Subagent uses the provided value, never re-derives
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Step 5: Present to User
|
|
140
|
+
|
|
141
|
+
Before making any changes, present:
|
|
142
|
+
|
|
143
|
+
```markdown
|
|
144
|
+
## Root Cause Analysis
|
|
145
|
+
|
|
146
|
+
**What happened**: [Concise description]
|
|
147
|
+
**Why it happened**: [Root cause category and explanation]
|
|
148
|
+
**Source file**: [File path(s) to update]
|
|
149
|
+
|
|
150
|
+
## Proposed Process Changes
|
|
151
|
+
|
|
152
|
+
**File**: `{path/to/file}`
|
|
153
|
+
**Change**: [Description]
|
|
154
|
+
|
|
155
|
+
**Diff preview**:
|
|
156
|
+
` ``diff
|
|
157
|
+
- [old content]
|
|
158
|
+
+ [new content]
|
|
159
|
+
` ``
|
|
160
|
+
|
|
161
|
+
## Questions
|
|
162
|
+
|
|
163
|
+
1. Does this analysis match your understanding?
|
|
164
|
+
2. Should I proceed with these process changes?
|
|
165
|
+
3. After updating the process, should I also fix the immediate issue?
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Step 6: Implement Changes
|
|
169
|
+
|
|
170
|
+
After user approval:
|
|
171
|
+
|
|
172
|
+
1. **Update the process file(s)** — apply the proposed edits
|
|
173
|
+
2. **Fix the immediate issue** (if requested)
|
|
174
|
+
3. **Record a retro** documenting the improvement:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
ace-retro create "selfimprove-TOPIC" --type self-improvement --tags process-fix
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Populate the retro with the root cause analysis, the fix applied, and the expected impact.
|
|
181
|
+
|
|
182
|
+
### Step 7: Archive Consumed Retros
|
|
183
|
+
|
|
184
|
+
If the input source was an existing retro, archive it after the improvement has been applied — the retro has been "consumed":
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
ace-retro move REF --to archive
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Success Criteria
|
|
191
|
+
|
|
192
|
+
- Root cause is identified (not just symptoms)
|
|
193
|
+
- Process fix prevents recurrence
|
|
194
|
+
- User approves changes before implementation
|
|
195
|
+
- Both process and immediate issue are addressed
|
|
196
|
+
- Improvement is recorded as a retro via `ace-retro create`
|
|
197
|
+
- Source retros (if any) are archived after processing
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: workflow
|
|
3
|
+
title: Synthesize Retros Workflow Instruction
|
|
4
|
+
purpose: Documentation for ace-retro/handbook/workflow-instructions/retro/synthesize.wf.md
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-03-01
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Synthesize Retros Workflow Instruction
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
Reduce multiple retros into a single synthesis retro. Distill common themes, recurring issues, and shared action items from N input retros into one consolidated retro using the standard retro format.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
- At least 2 active retros exist to synthesize
|
|
19
|
+
- Access to `ace-retro` CLI
|
|
20
|
+
|
|
21
|
+
## Process Steps
|
|
22
|
+
|
|
23
|
+
### 1. Gather Retros
|
|
24
|
+
|
|
25
|
+
Find candidate retros for synthesis:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# List active retros
|
|
29
|
+
ace-retro list --status active
|
|
30
|
+
|
|
31
|
+
# Filter by tags if synthesizing a specific topic
|
|
32
|
+
ace-retro list --status active --tags TAG
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Select which retros to include. Good candidates share a common theme, time period, or tag.
|
|
36
|
+
|
|
37
|
+
### 2. Load Content
|
|
38
|
+
|
|
39
|
+
Read each selected retro:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ace-retro show REF --content
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Collect the content from all selected retros before proceeding to analysis.
|
|
46
|
+
|
|
47
|
+
### 3. Reduce
|
|
48
|
+
|
|
49
|
+
Analyze all input retros together. Identify:
|
|
50
|
+
|
|
51
|
+
- **Common themes** — issues or observations that appear across multiple retros
|
|
52
|
+
- **Recurring patterns** — problems or successes that repeat
|
|
53
|
+
- **Shared action items** — improvements suggested in multiple retros
|
|
54
|
+
- **Contradictions** — conflicting recommendations that need resolution
|
|
55
|
+
- **Unique insights** — one-off observations worth preserving
|
|
56
|
+
|
|
57
|
+
For each theme, note which source retros contributed to it and how frequently it appeared.
|
|
58
|
+
|
|
59
|
+
### 4. Create Synthesis Retro
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ace-retro create "synthesis-TOPIC" --tags synthesis
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Read the created file path from the output.
|
|
66
|
+
|
|
67
|
+
### 5. Populate
|
|
68
|
+
|
|
69
|
+
Fill the synthesis retro using the standard template format (`tmpl://retro/retro`). The content is distilled from the N input retros:
|
|
70
|
+
|
|
71
|
+
- **What Went Well** — patterns of success across multiple retros, validated approaches
|
|
72
|
+
- **What Could Be Improved** — recurring issues, systemic problems identified across retros
|
|
73
|
+
- **Key Learnings** — consolidated insights, with frequency noted where relevant
|
|
74
|
+
- **Action Items** — merged and deduplicated action items, prioritized by how often they appeared
|
|
75
|
+
|
|
76
|
+
When populating, reference source retros to provide traceability (e.g., "Identified in 3/5 retros").
|
|
77
|
+
|
|
78
|
+
### 6. Archive Sources
|
|
79
|
+
|
|
80
|
+
After the synthesis retro is populated and complete, archive each source retro — they have been "consumed" by the synthesis:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ace-retro move REF --to archive
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Repeat for each source retro.
|
|
87
|
+
|
|
88
|
+
## Success Criteria
|
|
89
|
+
|
|
90
|
+
- Synthesis retro created using standard retro format
|
|
91
|
+
- Common themes and recurring patterns identified across inputs
|
|
92
|
+
- Action items deduplicated and prioritized by frequency
|
|
93
|
+
- Source retros archived after processing
|
|
94
|
+
- Output is a single retro that stands on its own — no external templates or analytics needed
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Retro
|
|
5
|
+
module Atoms
|
|
6
|
+
# Provides glob patterns for finding retro files.
|
|
7
|
+
# Retros use the .retro.md extension.
|
|
8
|
+
module RetroFilePattern
|
|
9
|
+
# Glob pattern for retro files within a directory
|
|
10
|
+
FILE_GLOB = "*.retro.md"
|
|
11
|
+
|
|
12
|
+
# Full file extension for retro files
|
|
13
|
+
FILE_EXTENSION = ".retro.md"
|
|
14
|
+
|
|
15
|
+
# Build the retro filename
|
|
16
|
+
# @param id [String] Raw 6-char b36ts ID
|
|
17
|
+
# @param slug [String] Kebab-case slug
|
|
18
|
+
# @return [String] Retro filename (e.g., "8ppq7w-sprint-review.retro.md")
|
|
19
|
+
def self.retro_filename(id, slug)
|
|
20
|
+
"#{id}-#{slug}#{FILE_EXTENSION}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Build the folder name for a retro
|
|
24
|
+
# @param id [String] Raw 6-char b36ts ID
|
|
25
|
+
# @param slug [String] Kebab-case slug
|
|
26
|
+
# @return [String] Folder name (e.g., "8ppq7w-sprint-review")
|
|
27
|
+
def self.folder_name(id, slug)
|
|
28
|
+
"#{id}-#{slug}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check if a filename matches the retro file pattern
|
|
32
|
+
# @param filename [String] Filename to check
|
|
33
|
+
# @return [Boolean] True if it's a retro file
|
|
34
|
+
def self.retro_file?(filename)
|
|
35
|
+
filename.to_s.end_with?(FILE_EXTENSION)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Retro
|
|
7
|
+
module Atoms
|
|
8
|
+
# Provides default frontmatter values for retro files.
|
|
9
|
+
# Generates the canonical YAML frontmatter block for new retros.
|
|
10
|
+
module RetroFrontmatterDefaults
|
|
11
|
+
# Build frontmatter hash for a new retro
|
|
12
|
+
# @param id [String] Raw 6-char b36ts ID
|
|
13
|
+
# @param title [String] Retro title
|
|
14
|
+
# @param type [String] Retro type (standard, conversation-analysis, self-review)
|
|
15
|
+
# @param tags [Array<String>] List of tags (default: [])
|
|
16
|
+
# @param status [String] Initial status (default: "active")
|
|
17
|
+
# @param created_at [Time] Creation time (default: now)
|
|
18
|
+
# @return [Hash] Frontmatter hash ready for YAML serialization
|
|
19
|
+
def self.build(id:, title:, type: "standard", tags: [], status: "active",
|
|
20
|
+
created_at: Time.now.utc)
|
|
21
|
+
{
|
|
22
|
+
"id" => id,
|
|
23
|
+
"title" => title,
|
|
24
|
+
"type" => type,
|
|
25
|
+
"tags" => Array(tags),
|
|
26
|
+
"created_at" => created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
|
27
|
+
"status" => status
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Serialize frontmatter hash to YAML block string.
|
|
32
|
+
# Delegates to the shared FrontmatterSerializer atom.
|
|
33
|
+
# @param frontmatter [Hash] Frontmatter data
|
|
34
|
+
# @return [String] YAML frontmatter block including delimiters
|
|
35
|
+
def self.serialize(frontmatter)
|
|
36
|
+
require "ace/support/items"
|
|
37
|
+
Ace::Support::Items::Atoms::FrontmatterSerializer.serialize(frontmatter)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/b36ts"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Retro
|
|
7
|
+
module Atoms
|
|
8
|
+
# Generates and formats raw b36ts IDs for retros.
|
|
9
|
+
# Retros use raw 6-char b36ts IDs without type markers.
|
|
10
|
+
# Example: "8ppq7w" (no ".t." or ".i." separator)
|
|
11
|
+
class RetroIdFormatter
|
|
12
|
+
# Generate a new raw 6-char b36ts ID for a retro
|
|
13
|
+
# @param time [Time] Time to encode (default: now)
|
|
14
|
+
# @return [String] 6-character raw b36ts ID (e.g., "8ppq7w")
|
|
15
|
+
def self.generate(time = Time.now.utc)
|
|
16
|
+
Ace::B36ts.encode(time, format: :"2sec")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Validate that a string is a valid raw retro ID
|
|
20
|
+
# @param id [String] The ID to validate
|
|
21
|
+
# @return [Boolean] True if valid 6-char b36ts ID
|
|
22
|
+
def self.valid?(id)
|
|
23
|
+
return false if id.nil? || id.empty?
|
|
24
|
+
|
|
25
|
+
Ace::B36ts.valid?(id)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Decode a raw retro ID to the time it was created
|
|
29
|
+
# @param id [String] Raw 6-char b36ts ID
|
|
30
|
+
# @return [Time] The time encoded in the ID
|
|
31
|
+
def self.decode_time(id)
|
|
32
|
+
Ace::B36ts.decode(id)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "retro_id_formatter"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Retro
|
|
7
|
+
module Atoms
|
|
8
|
+
# Pure validation predicates and constants for retro health checking.
|
|
9
|
+
# Used by doctor validators to determine correctness of frontmatter,
|
|
10
|
+
# file structure, and scope/status consistency.
|
|
11
|
+
module RetroValidationRules
|
|
12
|
+
VALID_STATUSES = %w[active done].freeze
|
|
13
|
+
TERMINAL_STATUSES = %w[done].freeze
|
|
14
|
+
REQUIRED_FIELDS = %w[id title type status created_at].freeze
|
|
15
|
+
RECOMMENDED_FIELDS = %w[tags].freeze
|
|
16
|
+
|
|
17
|
+
# Check if a status string is valid
|
|
18
|
+
# @param status [String] Status to validate
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def self.valid_status?(status)
|
|
21
|
+
VALID_STATUSES.include?(status.to_s)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Check if a status is terminal (belongs in _archive)
|
|
25
|
+
# @param status [String] Status to check
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def self.terminal_status?(status)
|
|
28
|
+
TERMINAL_STATUSES.include?(status.to_s)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check if an ID string is a valid b36ts retro ID
|
|
32
|
+
# @param id [String] ID to validate
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def self.valid_id?(id)
|
|
35
|
+
RetroIdFormatter.valid?(id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check if scope (special folder) is consistent with status
|
|
39
|
+
# @param status [String] Retro status
|
|
40
|
+
# @param special_folder [String, nil] Special folder name (e.g., "_archive", nil)
|
|
41
|
+
# @return [Array<Hash>] List of inconsistency issues (empty if consistent)
|
|
42
|
+
def self.scope_consistent?(status, special_folder)
|
|
43
|
+
issues = []
|
|
44
|
+
|
|
45
|
+
if terminal_status?(status) && special_folder != "_archive"
|
|
46
|
+
issues << {
|
|
47
|
+
type: :warning,
|
|
48
|
+
message: "Retro with terminal status '#{status}' not in _archive/"
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if special_folder == "_archive" && !terminal_status?(status) && status
|
|
53
|
+
issues << {
|
|
54
|
+
type: :warning,
|
|
55
|
+
message: "Retro in _archive/ but status is '#{status}' (expected terminal status)"
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
issues
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Return list of missing required fields from frontmatter
|
|
63
|
+
# @param frontmatter [Hash] Parsed frontmatter
|
|
64
|
+
# @return [Array<String>] Names of missing required fields
|
|
65
|
+
def self.missing_required_fields(frontmatter)
|
|
66
|
+
return REQUIRED_FIELDS.dup if frontmatter.nil? || !frontmatter.is_a?(Hash)
|
|
67
|
+
|
|
68
|
+
REQUIRED_FIELDS.select { |field| frontmatter[field].nil? || frontmatter[field].to_s.strip.empty? }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Return list of missing recommended fields from frontmatter
|
|
72
|
+
# @param frontmatter [Hash] Parsed frontmatter
|
|
73
|
+
# @return [Array<String>] Names of missing recommended fields
|
|
74
|
+
def self.missing_recommended_fields(frontmatter)
|
|
75
|
+
return RECOMMENDED_FIELDS.dup if frontmatter.nil? || !frontmatter.is_a?(Hash)
|
|
76
|
+
|
|
77
|
+
RECOMMENDED_FIELDS.select { |field| frontmatter[field].nil? }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/cli"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Retro
|
|
7
|
+
module CLI
|
|
8
|
+
module Commands
|
|
9
|
+
# ace-support-cli Command class for ace-retro create
|
|
10
|
+
class Create < Ace::Support::Cli::Command
|
|
11
|
+
include Ace::Support::Cli::Base
|
|
12
|
+
|
|
13
|
+
desc <<~DESC.strip
|
|
14
|
+
Create a new retro
|
|
15
|
+
|
|
16
|
+
Creates a new retrospective with title, type, and tags.
|
|
17
|
+
|
|
18
|
+
DESC
|
|
19
|
+
|
|
20
|
+
example [
|
|
21
|
+
'"Sprint Review" --type standard --tags sprint,team # Standard retro',
|
|
22
|
+
'"Quick self-review" --type self-review # Self-review retro',
|
|
23
|
+
'"Sprint Review" --move-to archive # Create in _archive/',
|
|
24
|
+
'"Sprint Review" --dry-run # Preview without writing'
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
argument :title, required: false, desc: "Retro title"
|
|
28
|
+
|
|
29
|
+
option :type, type: :string, aliases: %w[-t], desc: "Retro type (standard, conversation-analysis, self-review)"
|
|
30
|
+
option :tags, type: :string, aliases: %w[-T], desc: "Comma-separated tags"
|
|
31
|
+
option :"move-to", type: :string, aliases: %w[-m], desc: "Target folder (e.g. archive)"
|
|
32
|
+
option :"dry-run", type: :boolean, aliases: %w[-n], desc: "Preview without writing"
|
|
33
|
+
|
|
34
|
+
option :git_commit, type: :boolean, aliases: %w[--gc], desc: "Auto-commit changes"
|
|
35
|
+
|
|
36
|
+
option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
|
|
37
|
+
option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
|
|
38
|
+
option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
|
|
39
|
+
|
|
40
|
+
def call(title: nil, **options)
|
|
41
|
+
type = options[:type]
|
|
42
|
+
tags_str = options[:tags]
|
|
43
|
+
move_to = options[:"move-to"]
|
|
44
|
+
dry_run = options[:"dry-run"]
|
|
45
|
+
|
|
46
|
+
tags = tags_str ? tags_str.split(",").map(&:strip).reject(&:empty?) : []
|
|
47
|
+
|
|
48
|
+
unless title
|
|
49
|
+
warn "Error: title is required"
|
|
50
|
+
warn ""
|
|
51
|
+
warn "Usage: ace-retro create TITLE [--type TYPE] [--tags T1,T2] [--move-to FOLDER]"
|
|
52
|
+
raise Ace::Support::Cli::Error.new("Title required")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if dry_run
|
|
56
|
+
puts "Would create retro:"
|
|
57
|
+
puts " Title: #{title}"
|
|
58
|
+
puts " Type: #{type || "standard"}"
|
|
59
|
+
puts " Tags: #{tags.any? ? tags.join(", ") : "(none)"}"
|
|
60
|
+
puts " Folder: #{move_to ? "_#{move_to.delete_prefix("_")}" : "(root)"}"
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
manager = Ace::Retro::Organisms::RetroManager.new
|
|
65
|
+
retro = manager.create(
|
|
66
|
+
title,
|
|
67
|
+
type: type,
|
|
68
|
+
tags: tags,
|
|
69
|
+
move_to: move_to
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
folder_info = retro.special_folder ? " (#{retro.special_folder})" : ""
|
|
73
|
+
puts "Retro created: #{retro.id} #{retro.title}#{folder_info}"
|
|
74
|
+
puts " Path: #{retro.file_path}"
|
|
75
|
+
|
|
76
|
+
if options[:git_commit]
|
|
77
|
+
Ace::Support::Items::Molecules::GitCommitter.commit(
|
|
78
|
+
paths: [retro.path],
|
|
79
|
+
intention: "create retro #{retro.id}"
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|