diff --git a/GOALS.md b/GOALS.md index 1d98a94c3..2daa6be3b 100644 --- a/GOALS.md +++ b/GOALS.md @@ -40,18 +40,6 @@ The north star claims every session is smarter than the last. Instrumented and v **Steer:** decrease (maintain) -### 7. Enforce release cadence in pre-release gate - -Policy: max 1 published release per week (security hotfixes exempt). Was violated (4 releases on 2026-02-27). Added `scripts/release-cadence-check.sh` with warn <7 days, block <1 day. Wired into `ci-local-release.sh` Phase 2. Gate: cadence check passes. - -**Steer:** decrease (violation count) - -### 8. Tighten cli/ complexity ceiling from 21 to 20 - -Two functions at CC 21 refactored: `buildHandoffContext` 21→10 (extracted renderHandoffEntry, renderDegradationWarnings, resolveNarrativeCap) and `runRPIServe` 21→12 (extracted runServeOrchestrate, runServeWatch, newDashboardServer). Gate threshold lowered from 21 to 20. - -**Steer:** decrease (maintain) - ### 5. Run Athena knowledge cycle daily The flywheel captures learnings reactively. Athena mines git, `.agents/`, and code hotspots to extract signal that sessions missed, then defrags stale/duplicate learnings and flags oscillating evolve goals before they waste cycles. Gate: `ao defrag` report is ≤26 hours old and stale learning count ≤5. @@ -64,6 +52,18 @@ Goals that alternate improved→fail for ≥3 consecutive cycles indicate the im **Steer:** decrease +### 7. Enforce release cadence in pre-release gate + +Policy: max 1 published release per week (security hotfixes exempt). Was violated (4 releases on 2026-02-27). Added `scripts/release-cadence-check.sh` with warn <7 days, block <1 day. Wired into `ci-local-release.sh` Phase 2. Gate: cadence check passes. + +**Steer:** decrease (violation count) + +### 8. Tighten cli/ complexity ceiling from 21 to 20 + +Two functions at CC 21 refactored: `buildHandoffContext` 21→10 (extracted renderHandoffEntry, renderDegradationWarnings, resolveNarrativeCap) and `runRPIServe` 21→12 (extracted runServeOrchestrate, runServeWatch, newDashboardServer). Gate threshold lowered from 21 to 20. + +**Steer:** decrease (maintain) + ## Gates | ID | Check | Weight | Description | diff --git a/cli/cmd/ao/inject.go b/cli/cmd/ao/inject.go index e686cfda4..a933f87fd 100644 --- a/cli/cmd/ao/inject.go +++ b/cli/cmd/ao/inject.go @@ -126,7 +126,11 @@ Examples: ao inject --apply-decay # Apply confidence decay before ranking ao inject --bead ag-7abc # Work-scoped injection for bead ao inject --predecessor /path/to/handoff.md # Include predecessor context - ao inject --for=research "authentication" # Filtered by skill's context contract`, + ao inject --for=research "authentication" # Filtered by skill's context contract + +Environment variables: + RPI_RUN_ID When set, --for uses this as the context artifact directory name + instead of generating an adhoc- ID. Set by /rpi orchestrator.`, Args: cobra.MaximumNArgs(1), RunE: runInject, } diff --git a/cli/cmd/ao/inject_context.go b/cli/cmd/ao/inject_context.go index 6d1a7f960..c3dcc3fb5 100644 --- a/cli/cmd/ao/inject_context.go +++ b/cli/cmd/ao/inject_context.go @@ -4,11 +4,26 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "gopkg.in/yaml.v3" ) +// Section name constants for context filtering. +const ( + sectionHistory = "HISTORY" + sectionIntel = "INTEL" + sectionTask = "TASK" +) + +// validIntelScopes is the set of allowed intel_scope values. +var validIntelScopes = map[string]bool{ + "none": true, + "topic": true, + "full": true, +} + // ContextDeclaration represents a skill's context window policy. type ContextDeclaration struct { Window string `yaml:"window"` // isolated, fork, inherit @@ -113,6 +128,11 @@ func parseContextFromFrontmatter(frontmatter []byte) (*ContextDeclaration, error return nil, err } } + if decl.IntelScope != "" { + if err := validateIntelScope(decl.IntelScope); err != nil { + return nil, err + } + } return &decl, nil } @@ -133,6 +153,14 @@ func validateWindow(w string) error { } } +// validateIntelScope checks that the intel_scope value is one of the allowed values. +func validateIntelScope(scope string) error { + if validIntelScopes[scope] { + return nil + } + return fmt.Errorf("invalid context.intel_scope %q: must be none, topic, or full", scope) +} + // resolveSkillPath finds the SKILL.md for a given skill name. // Search order: local repo, installed skills, plugin cache. func resolveSkillPath(skillName string) (string, error) { @@ -166,7 +194,8 @@ func resolveSkillPath(skillName string) (string, error) { cachePattern := filepath.Join(home, ".claude", "plugins", "cache", "agentops-marketplace", "agentops", "*", "skills", skillName, "SKILL.md") matches, _ := filepath.Glob(cachePattern) if len(matches) > 0 { - return matches[0], nil + sort.Strings(matches) + return matches[len(matches)-1], nil // latest version (lexicographically highest) } return "", fmt.Errorf("skill %q not found", skillName) @@ -192,14 +221,14 @@ func applyContextFilter(knowledge *injectedKnowledge, decl *ContextDeclaration) for _, s := range decl.Sections.Include { allowed[s] = true } - if !allowed["HISTORY"] { + if !allowed[sectionHistory] { knowledge.Sessions = nil } - if !allowed["INTEL"] { + if !allowed[sectionIntel] { knowledge.Learnings = nil knowledge.Patterns = nil } - if !allowed["TASK"] { + if !allowed[sectionTask] { knowledge.BeadID = "" knowledge.Predecessor = nil } @@ -207,12 +236,12 @@ func applyContextFilter(knowledge *injectedKnowledge, decl *ContextDeclaration) // Exclude mode: zero sections in the exclude list. for _, section := range decl.Sections.Exclude { switch section { - case "HISTORY": + case sectionHistory: knowledge.Sessions = nil - case "INTEL": + case sectionIntel: knowledge.Learnings = nil knowledge.Patterns = nil - case "TASK": + case sectionTask: knowledge.BeadID = "" knowledge.Predecessor = nil } diff --git a/cli/cmd/ao/inject_context_paths.go b/cli/cmd/ao/inject_context_paths.go index 63beca4ca..4b1ffe9aa 100644 --- a/cli/cmd/ao/inject_context_paths.go +++ b/cli/cmd/ao/inject_context_paths.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "math/rand" "os" "path/filepath" "time" @@ -12,7 +13,7 @@ import ( // Called automatically when --for is used; uses RPI_RUN_ID if set. func contextArtifactDir(runID string) string { if runID == "" { - runID = fmt.Sprintf("adhoc-%d", time.Now().Unix()) + runID = fmt.Sprintf("adhoc-%d-%04x", time.Now().Unix(), rand.Intn(0x10000)) //nolint:gosec // non-cryptographic use } return filepath.Join(".agents", "context", runID) } diff --git a/cli/cmd/ao/inject_context_paths_test.go b/cli/cmd/ao/inject_context_paths_test.go index 372e26f85..4271fd599 100644 --- a/cli/cmd/ao/inject_context_paths_test.go +++ b/cli/cmd/ao/inject_context_paths_test.go @@ -21,15 +21,23 @@ func TestContextArtifactDir_Empty(t *testing.T) { if !strings.HasPrefix(got, prefix) { t.Errorf("contextArtifactDir(\"\") = %q, want prefix %q", got, prefix) } - // Verify the suffix after "adhoc-" is numeric + // Verify the suffix after "adhoc-" matches -<4hex> suffix := strings.TrimPrefix(got, prefix) if suffix == "" { - t.Errorf("contextArtifactDir(\"\") suffix is empty, expected numeric timestamp") + t.Errorf("contextArtifactDir(\"\") suffix is empty, expected timestamp-hex") } - for _, c := range suffix { - if c < '0' || c > '9' { - t.Errorf("contextArtifactDir(\"\") suffix %q contains non-numeric character %q", suffix, string(c)) - break + parts := strings.SplitN(suffix, "-", 2) + if len(parts) != 2 { + t.Errorf("contextArtifactDir(\"\") suffix %q expected format -, got %d parts", suffix, len(parts)) + } else { + for _, c := range parts[0] { + if c < '0' || c > '9' { + t.Errorf("contextArtifactDir(\"\") timestamp part %q contains non-numeric character %q", parts[0], string(c)) + break + } + } + if len(parts[1]) != 4 { + t.Errorf("contextArtifactDir(\"\") hex suffix %q expected 4 characters", parts[1]) } } } diff --git a/cli/cmd/ao/inject_context_test.go b/cli/cmd/ao/inject_context_test.go index 554734812..79d14eebc 100644 --- a/cli/cmd/ao/inject_context_test.go +++ b/cli/cmd/ao/inject_context_test.go @@ -537,3 +537,25 @@ func TestApplyContextFilter_IncludePrecedenceOverExclude(t *testing.T) { } } +func TestValidateIntelScope_Valid(t *testing.T) { + for _, scope := range []string{"none", "topic", "full"} { + if err := validateIntelScope(scope); err != nil { + t.Errorf("validateIntelScope(%q) = %v, want nil", scope, err) + } + } +} + +func TestValidateIntelScope_Invalid(t *testing.T) { + if err := validateIntelScope("typo"); err == nil { + t.Error("validateIntelScope(\"typo\") = nil, want error") + } +} + +func TestParseContextFromFrontmatter_InvalidIntelScope(t *testing.T) { + fm := []byte("context:\n window: fork\n intel_scope: invalid") + _, err := parseContextFromFrontmatter(fm) + if err == nil { + t.Error("parseContextFromFrontmatter with invalid intel_scope should error") + } +} + diff --git a/cli/cmd/ao/rpi_phased_context.go b/cli/cmd/ao/rpi_phased_context.go index bd7a1c34d..bab64e7f7 100644 --- a/cli/cmd/ao/rpi_phased_context.go +++ b/cli/cmd/ao/rpi_phased_context.go @@ -463,7 +463,7 @@ func buildPromptForPhase(cwd string, phaseNum int, state *phasedState, _ *retryC // Write provenance audit trail if state.RunID != "" { if err := writePromptAuditTrail(cwd, state.RunID, phaseNum, prompt.String()); err != nil { - VerbosePrintf("Warning: failed to write prompt audit trail for phase %d: %v\n", phaseNum, err) + fmt.Fprintf(os.Stderr, "Warning: failed to write prompt audit trail for phase %d: %v\n", phaseNum, err) } } @@ -612,7 +612,7 @@ func buildRetryPrompt(cwd string, phaseNum int, state *phasedState, retryCtx *re // Write provenance audit trail for retry if state.RunID != "" { if err := writePromptAuditTrail(cwd, state.RunID, phaseNum, prompt.String()); err != nil { - VerbosePrintf("Warning: failed to write retry audit trail for phase %d: %v\n", phaseNum, err) + fmt.Fprintf(os.Stderr, "Warning: failed to write retry audit trail for phase %d: %v\n", phaseNum, err) } } diff --git a/cli/cmd/ao/rpi_phased_handoff.go b/cli/cmd/ao/rpi_phased_handoff.go index cb0cdb941..dabefe5e5 100644 --- a/cli/cmd/ao/rpi_phased_handoff.go +++ b/cli/cmd/ao/rpi_phased_handoff.go @@ -267,7 +267,7 @@ func renderHandoffEntry(sb *strings.Builder, h *phaseHandoff, manifest phaseMani if narrativeCap > 0 && h.Narrative != "" { narrative := h.Narrative if len(narrative) > narrativeCap { - narrative = narrative[:narrativeCap] + "..." + narrative = truncateRunes(narrative, narrativeCap) } sb.WriteString(fmt.Sprintf("Narrative (from phase-%d-summary): %s\n", h.Phase, narrative)) } @@ -325,7 +325,7 @@ func buildPhaseHandoffFromState(state *phasedState, phaseNum int, cwd string) *p if content, err := os.ReadFile(matches[len(matches)-1]); err == nil { narrative := string(content) if len(narrative) > 2000 { - narrative = narrative[:2000] + narrative = truncateRunes(narrative, 2000) } h.Narrative = narrative } @@ -389,3 +389,13 @@ func discoverPhaseArtifacts(cwd string, phaseNum int) []string { return artifacts } + +// truncateRunes truncates s to at most cap runes and appends "..." if truncated. +// Safe for multi-byte UTF-8 characters — avoids slicing mid-codepoint. +func truncateRunes(s string, cap int) string { + runes := []rune(s) + if len(runes) <= cap { + return s + } + return string(runes[:cap]) + "..." +} diff --git a/cli/cmd/ao/rpi_phased_handoff_test.go b/cli/cmd/ao/rpi_phased_handoff_test.go index 815bc4223..428de362b 100644 --- a/cli/cmd/ao/rpi_phased_handoff_test.go +++ b/cli/cmd/ao/rpi_phased_handoff_test.go @@ -418,6 +418,32 @@ func TestRenderHandoffField_EmptyString(t *testing.T) { } } +func TestTruncateRunes_MultiByteUTF8(t *testing.T) { + // Mix of ASCII and multi-byte characters (Japanese + emoji) + input := "Hello 世界! 🌍🌎🌏 more text here" + got := truncateRunes(input, 10) + // Should be first 10 runes + "..." + runes := []rune(input) + want := string(runes[:10]) + "..." + if got != want { + t.Errorf("truncateRunes(%q, 10) = %q, want %q", input, got, want) + } + // Verify result is valid UTF-8 + for i, r := range got { + if r == '\uFFFD' { + t.Errorf("truncateRunes produced invalid UTF-8 at byte %d", i) + } + } +} + +func TestTruncateRunes_ShortString(t *testing.T) { + input := "short" + got := truncateRunes(input, 100) + if got != input { + t.Errorf("truncateRunes(%q, 100) = %q, want %q (no truncation)", input, got, input) + } +} + func TestFormatVerdicts_Sorted(t *testing.T) { verdicts := map[string]string{ "zebra": "FAIL", diff --git a/cli/cmd/ao/rpi_serve.go b/cli/cmd/ao/rpi_serve.go index 3d3955fd8..4d9648ba7 100644 --- a/cli/cmd/ao/rpi_serve.go +++ b/cli/cmd/ao/rpi_serve.go @@ -159,6 +159,7 @@ func runServeOrchestrate(cwd, goal string) error { stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(stop) go func() { <-stop orchCancel() @@ -224,6 +225,7 @@ func runServeWatch(cwd, watchRunID string) error { stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(stop) go func() { <-stop ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -275,7 +277,7 @@ func buildServeMux(root *serveMuxRoot, runID string) *http.ServeMux { mux.HandleFunc("/", serveRPIIndex) mux.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { - setCORSHeaders(w) + setCORSHeaders(w, r) w.WriteHeader(http.StatusNoContent) return } @@ -283,7 +285,7 @@ func buildServeMux(root *serveMuxRoot, runID string) *http.ServeMux { }) mux.HandleFunc("/runs", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { - setCORSHeaders(w) + setCORSHeaders(w, r) w.WriteHeader(http.StatusNoContent) return } @@ -319,7 +321,7 @@ func serveRPIEvents(w http.ResponseWriter, r *http.Request, root, defaultRunID s w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Set("X-Accel-Buffering", "no") - setCORSHeaders(w) + setCORSHeaders(w, r) seen := 0 ticker := time.NewTicker(500 * time.Millisecond) @@ -333,6 +335,11 @@ func serveRPIEvents(w http.ResponseWriter, r *http.Request, root, defaultRunID s case <-ticker.C: events, err := loadRPIC2Events(root, runID) if err != nil { + errEvent := RPIC2Event{Type: "error", Message: err.Error(), Timestamp: time.Now().UTC().Format(time.RFC3339)} + if writeSSEEvent(w, errEvent) != nil { + return + } + flusher.Flush() continue } for ; seen < len(events); seen++ { @@ -354,20 +361,45 @@ func writeSSEEvent(w http.ResponseWriter, ev RPIC2Event) error { return err } -func serveRPIRuns(w http.ResponseWriter, _ *http.Request, root string) { - setCORSHeaders(w) +func serveRPIRuns(w http.ResponseWriter, r *http.Request, root string) { + setCORSHeaders(w, r) runs := scanRegistryRuns(root) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(runs) } -func setCORSHeaders(w http.ResponseWriter) { - w.Header().Set("Access-Control-Allow-Origin", "*") +func setCORSHeaders(w http.ResponseWriter, r ...*http.Request) { + origin := "" + if len(r) > 0 && r[0] != nil { + origin = r[0].Header.Get("Origin") + } + // Only allow localhost origins to prevent cross-site data exfiltration. + if origin == "" || isLocalhostOrigin(origin) { + if origin != "" { + w.Header().Set("Access-Control-Allow-Origin", origin) + } else { + w.Header().Set("Access-Control-Allow-Origin", "http://localhost") + } + } w.Header().Set("Access-Control-Allow-Headers", "Content-Type") w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") w.Header().Set("Access-Control-Max-Age", "86400") } +// isLocalhostOrigin returns true if the origin is a localhost URL. +func isLocalhostOrigin(origin string) bool { + for _, prefix := range []string{ + "http://localhost", "https://localhost", + "http://127.0.0.1", "https://127.0.0.1", + "http://[::1]", "https://[::1]", + } { + if strings.HasPrefix(origin, prefix) { + return true + } + } + return false +} + // openBrowserURL opens url in the default system browser. func openBrowserURL(url string) { var cmd *exec.Cmd diff --git a/cli/cmd/ao/rpi_serve_test.go b/cli/cmd/ao/rpi_serve_test.go index 5f95bae9a..c9fe4c26d 100644 --- a/cli/cmd/ao/rpi_serve_test.go +++ b/cli/cmd/ao/rpi_serve_test.go @@ -135,10 +135,29 @@ func TestBuildServeMux(t *testing.T) { } func TestSetCORSHeaders(t *testing.T) { + // Without request, defaults to localhost rr := httptest.NewRecorder() setCORSHeaders(rr) - if v := rr.Header().Get("Access-Control-Allow-Origin"); v != "*" { - t.Errorf("CORS origin: want *, got %q", v) + if v := rr.Header().Get("Access-Control-Allow-Origin"); v != "http://localhost" { + t.Errorf("CORS origin (no request): want http://localhost, got %q", v) + } + + // With localhost origin, echoes it back + rr = httptest.NewRecorder() + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("Origin", "http://localhost:3000") + setCORSHeaders(rr, req) + if v := rr.Header().Get("Access-Control-Allow-Origin"); v != "http://localhost:3000" { + t.Errorf("CORS origin (localhost): want http://localhost:3000, got %q", v) + } + + // With non-localhost origin, does not set origin header + rr = httptest.NewRecorder() + req = httptest.NewRequest("GET", "/", nil) + req.Header.Set("Origin", "http://evil.example.com") + setCORSHeaders(rr, req) + if v := rr.Header().Get("Access-Control-Allow-Origin"); v != "" { + t.Errorf("CORS origin (external): want empty, got %q", v) } } diff --git a/docs/SKILL-ROUTER.md b/docs/SKILL-ROUTER.md index 6ab55edb6..82e5c3716 100644 --- a/docs/SKILL-ROUTER.md +++ b/docs/SKILL-ROUTER.md @@ -41,7 +41,7 @@ What are you trying to do? │ ├─ "Learn from past work" │ ├─ What do we know about X? ──► /knowledge -│ ├─ Save this insight ─────────► /learn "insight" +│ ├─ Save this insight ─────────► /retro --quick "insight" │ └─ Run a retrospective ───────► /retro │ ├─ "Parallelize work" diff --git a/docs/workflows/complete-cycle.md b/docs/workflows/complete-cycle.md index 6f88b30e5..44fcff804 100644 --- a/docs/workflows/complete-cycle.md +++ b/docs/workflows/complete-cycle.md @@ -163,7 +163,7 @@ Read CLAUDE.md # Reload primer context if needed **Commands:** ```bash -/learn [topic] # Extract patterns +/retro --quick [topic] # Extract patterns /bundle-save [topic]-learning # Share learnings ``` @@ -215,7 +215,7 @@ Read CLAUDE.md /implement --resume [topic]-implementation-progress # Complete implementation /vibe recent -/learn [topic] +/retro --quick [topic] # Context: 40-70k total (20-35%) ``` @@ -321,7 +321,7 @@ Read CLAUDE.md **Session 4: Learn** ```bash -/learn redis-pooling-implementation +/retro --quick redis-pooling-implementation # Agent extracts: # Pattern: Redis Connection Pooling for Burst Traffic # - When: High-traffic services with burst patterns diff --git a/docs/workflows/continuous-improvement.md b/docs/workflows/continuous-improvement.md index 6ada84d59..a24ced252 100644 --- a/docs/workflows/continuous-improvement.md +++ b/docs/workflows/continuous-improvement.md @@ -39,7 +39,7 @@ Phase 4: Improve (10-15% context) **Commands:** ```bash -/learn --retrospective --since "30 days ago" +/retro --retrospective --since "30 days ago" ``` **Activities:** @@ -92,7 +92,7 @@ Low Impact, High Effort → SKIP /plan [improvement] /implement [improvement]-plan /vibe recent -/learn [improvement] +/retro --quick [improvement] ``` **Output:** Improved system + new patterns @@ -103,7 +103,7 @@ Low Impact, High Effort → SKIP ```bash # Phase 1: Review -/learn --retrospective --since "2025-10-01" +/retro --retrospective --since "2025-10-01" # Findings: # - 15 commits reviewed # - 3 patterns emerged (used 5+ times each) @@ -124,10 +124,10 @@ Low Impact, High Effort → SKIP /plan pre-commit-hook /implement pre-commit-hook-plan /vibe recent -/learn pre-commit-improvement +/retro --quick pre-commit-improvement # Result: 90% reduction in linting issues ``` --- -**Start continuous improvement with:** `/learn --retrospective` +**Start continuous improvement with:** `/retro --retrospective` diff --git a/docs/workflows/debug-cycle.md b/docs/workflows/debug-cycle.md index e8828895a..c998f69a1 100644 --- a/docs/workflows/debug-cycle.md +++ b/docs/workflows/debug-cycle.md @@ -100,7 +100,7 @@ Read CLAUDE.md **Commands:** ```bash /vibe recent -/learn [issue]-fix +/retro --quick [issue]-fix ``` **Activities:** @@ -134,7 +134,7 @@ Read CLAUDE.md # Phase 4: Verify /vibe recent # ✅ Load test: No failures at 3x traffic -/learn redis-pool-debugging +/retro --quick redis-pool-debugging # Pattern: Intermittent failures + time correlation = resource exhaustion ``` diff --git a/docs/workflows/knowledge-synthesis.md b/docs/workflows/knowledge-synthesis.md index d767bd202..da9245bc5 100644 --- a/docs/workflows/knowledge-synthesis.md +++ b/docs/workflows/knowledge-synthesis.md @@ -68,7 +68,7 @@ Phase 3: Document (5-10% context) **Commands:** ```bash -/learn [topic] +/retro --quick [topic] # Creates pattern documentation # Updates knowledge base ``` @@ -103,7 +103,7 @@ Read CLAUDE.md # Pattern: Middleware-based validation # Best practice: Separate auth concerns from business logic -/learn auth-system-knowledge +/retro --quick auth-system-knowledge # Documented for future reference ``` diff --git a/docs/workflows/multi-domain.md b/docs/workflows/multi-domain.md index 5718547ab..318abeb1c 100644 --- a/docs/workflows/multi-domain.md +++ b/docs/workflows/multi-domain.md @@ -115,7 +115,7 @@ Read CLAUDE.md **Session 3: Integration** ```bash # Both bundles loaded (technical + personal) -/learn k8s-deployment +/retro --quick k8s-deployment # Pattern extracted: # - Technical: K8s app deployment pattern # - Personal: Career advancement evidence diff --git a/docs/workflows/post-work-retro.md b/docs/workflows/post-work-retro.md index 45d7d1826..28fe6e8b4 100644 --- a/docs/workflows/post-work-retro.md +++ b/docs/workflows/post-work-retro.md @@ -2,7 +2,7 @@ **Purpose:** Systematic retrospective after completing significant work -**Composes:** `/retro` -> `/learn` -> improvement creation +**Composes:** `/retro` -> `/post-mortem` -> improvement creation **Failure Patterns Prevented:** Institutional memory loss, repeated mistakes, ecosystem gaps @@ -17,7 +17,7 @@ Run /retro (failure pattern analysis) ↓ Identify Learnings (patterns, anti-patterns) ↓ -Run /learn for each pattern +Run /retro --quick for each pattern ↓ Create Improvements (skills, workflows, agents) ↓ @@ -178,21 +178,21 @@ Extract patterns (what to repeat) and anti-patterns (what to avoid): **Evidence:** 3 rollback commits in session ``` -### Step 5: Run /learn for Each Pattern +### Step 5: Run /retro --quick for Each Pattern Extract reusable patterns to pattern library: ```markdown # For each pattern worth capturing: -/learn pattern="tracer-bullet-first" \ +/retro --quick pattern="tracer-bullet-first" \ context="Infrastructure deployments with operators" \ what="Deploy minimal resource before full implementation" \ why="Catches admission webhook rejections early" \ when="Any deployment using CRDs with admission webhooks" \ evidence="Dify deployment 2025-11-27, saved 2h" -/learn pattern="version-check-before-docs" \ +/retro --quick pattern="version-check-before-docs" \ context="Using external documentation" \ what="Check installed version before trusting docs" \ why="Docs may be for newer version with features you don't have" \ @@ -345,12 +345,12 @@ Based on gaps identified, create issues or drafts for: → Saves bundle ``` -### With /learn Command +### With /retro Command ```markdown # Each pattern identified gets captured -/learn pattern="name" context="..." what="..." why="..." when="..." +/retro --quick pattern="name" context="..." what="..." why="..." when="..." # Patterns stored in: # - Knowledge graph (Memory MCP) @@ -381,7 +381,7 @@ infrastructure-deployment: | Run /retro | 5 min | Failure pattern analysis | | Ecosystem audit | 5 min | Tool usage review | | Identify patterns | 10 min | Extract learnings | -| Run /learn | 5 min | Capture patterns | +| Run /retro --quick | 5 min | Capture patterns | | Create improvements | 10 min | File issues/drafts | | Save bundle | 2 min | Preserve context | | **Total** | **~40 min** | | @@ -418,7 +418,7 @@ Post-work retrospective is successful when: # What tools helped? What was missing? 4. Extract patterns - /learn pattern="name" ... + /retro --quick pattern="name" ... 5. Create improvements # File issues for skills/workflows needed diff --git a/docs/workflows/session-lifecycle.md b/docs/workflows/session-lifecycle.md index 7ada91a56..e5dcf3600 100644 --- a/docs/workflows/session-lifecycle.md +++ b/docs/workflows/session-lifecycle.md @@ -241,7 +241,7 @@ Next time, just say "continue the caching work" and I'll pick up where you left | Command | Purpose | When to Use | |---------|---------|-------------| | `/retro` | Session retrospective | After significant work | -| `/learn` | Extract reusable patterns | After solving problems | +| `/retro --quick` | Extract reusable patterns | After solving problems | --- @@ -300,7 +300,6 @@ Next time, just say "continue the caching work" and I'll pick up where you left /session-end # optionally: /retro -/learn ``` --- diff --git a/scripts/release-cadence-check.sh b/scripts/release-cadence-check.sh index 3e5d99a75..93a76e35f 100755 --- a/scripts/release-cadence-check.sh +++ b/scripts/release-cadence-check.sh @@ -42,6 +42,8 @@ if [[ -z "$TAG_DATE" ]]; then exit 0 fi +# Note: Uses rolling 86400-second window, not calendar-day boundaries. +# Both date +%s values are UTC epoch seconds, so subtraction is timezone-neutral. NOW=$(date +%s) DAYS_AGO=$(( (NOW - TAG_DATE) / 86400 )) diff --git a/skills-codex/flywheel/references/promotion-tiers.md b/skills-codex/flywheel/references/promotion-tiers.md index a7be41729..01f5010ef 100644 --- a/skills-codex/flywheel/references/promotion-tiers.md +++ b/skills-codex/flywheel/references/promotion-tiers.md @@ -12,7 +12,7 @@ Defines the maturity pipeline for knowledge artifacts. ## Tier 1: Learnings (`.agents/learnings/`) -- **Source:** `/retro`, `/learn`, `/extract`, promoted from forge +- **Source:** `/retro`, `/post-mortem`, `/extract`, promoted from forge - **Confidence:** 0.3-1.0 - **Citations:** 1+ - **Promotion criteria:** Promote to Tier 2 when confidence >= 0.8 AND cited >= 3 times AND age > 30 days @@ -28,7 +28,7 @@ Defines the maturity pipeline for knowledge artifacts. ## Cross-Repo Promotion -- Any tier can be promoted to `~/.claude/patterns/` via `/learn --global` +- Any tier can be promoted to `~/.claude/patterns/` via `/retro --global` - Global patterns are user-level, shared across all repositories - Promotion is a manual decision (human judgment on cross-repo applicability) - Global patterns are found by `/research`, `/knowledge`, and `/inject` via grep diff --git a/skills-codex/post-mortem/SKILL.md b/skills-codex/post-mortem/SKILL.md index cc9a7cd9f..7c0a42dd7 100644 --- a/skills-codex/post-mortem/SKILL.md +++ b/skills-codex/post-mortem/SKILL.md @@ -274,7 +274,7 @@ Enables adversarial two-round review for post-implementation validation. Use for Inline extraction of learnings from the completed work (formerly delegated to the retro skill). -#### Step 4.1: Gather Context +#### Step EX.1: Gather Context ```bash # Recent commits @@ -291,7 +291,7 @@ Read relevant artifacts: research documents, plan documents, commit messages, co **If retrospecting an epic:** Run the closure integrity quick-check from `references/context-gathering.md` (Phantom Bead Detection + Multi-Wave Regression Scan). Include any warnings in findings. -#### Step 4.2: Classify Learnings +#### Step EX.2: Classify Learnings Ask these questions: @@ -318,7 +318,7 @@ For each learning, capture: - **Why it matters**: Impact on future work - **Confidence**: high, medium, low -#### Step 4.3: Write Learnings +#### Step EX.3: Write Learnings **Write to:** `.agents/learnings/YYYY-MM-DD-.md` @@ -353,13 +353,13 @@ confidence: ... ``` -#### Step 4.4: Classify Learning Scope +#### Step EX.4: Classify Learning Scope -For each learning extracted in Step 4.3, classify: +For each learning extracted in Step EX.3, classify: **Question:** "Does this learning reference specific files, packages, or architecture in THIS repo? Or is it a transferable pattern that helps any project?" -- **Repo-specific** -> Write to `.agents/learnings/` (existing behavior from Step 4.3). Use `git rev-parse --show-toplevel` to resolve repo root — never write relative to cwd. +- **Repo-specific** -> Write to `.agents/learnings/` (existing behavior from Step EX.3). Use `git rev-parse --show-toplevel` to resolve repo root — never write relative to cwd. - **Cross-cutting/transferable** -> Rewrite to remove repo-specific context (file paths, function names, package names), then: 1. Write abstracted version to `~/.agents/learnings/YYYY-MM-DD-.md` (NOT local — one copy only) 2. Run abstraction lint check: @@ -377,7 +377,7 @@ For each learning extracted in Step 4.3, classify: - Local: "Athena's validate package needs O_CREATE|O_EXCL for atomic claims because Zeus spawns concurrent workers" - Global: "Use O_CREATE|O_EXCL for atomic file creation when multiple processes may race on the same path" -#### Step 4.5: Compile Constraint Templates +#### Step EX.5: Compile Constraint Templates For each extracted learning scoring >= 4/5 on actionability AND tagged "constraint" or "anti-pattern", run `bash hooks/constraint-compiler.sh ` to generate a constraint template. @@ -594,7 +594,7 @@ source: "[[.agents/plans/YYYY-MM-DD-]]" **Epic:** **Duration:** -**Cycle-Time Trend:** +**Cycle-Time Trend:** ## Council Verdict: PASS / WARN / FAIL diff --git a/skills-codex/post-mortem/references/context-gathering.md b/skills-codex/post-mortem/references/context-gathering.md index f76311aaf..c3026239b 100644 --- a/skills-codex/post-mortem/references/context-gathering.md +++ b/skills-codex/post-mortem/references/context-gathering.md @@ -46,7 +46,7 @@ python3 ~/.claude/scripts/analyze-sessions.py --session=$SESSION_ID --limit=50 | Conversation Data | Retro Output | |-------------------|--------------| -| `DecisionExtraction` | `.agents/retros/` - Decisions section | +| `DecisionExtraction` | `.agents/council/` - Decisions section | | `QualityResult.issues` | Friction detection | | `DocExtraction(type="warning")` | `.agents/learnings/` | | `DecisionExtraction(type="pattern")` | `.agents/patterns/` | diff --git a/skills-codex/post-mortem/references/learning-templates.md b/skills-codex/post-mortem/references/learning-templates.md index b02316327..09c478e7f 100644 --- a/skills-codex/post-mortem/references/learning-templates.md +++ b/skills-codex/post-mortem/references/learning-templates.md @@ -230,7 +230,7 @@ mcp__ai-platform__memory_store( ## Retro Summary Template -Write to: `.agents/retros/YYYY-MM-DD-{epic}.md` +Write to: `.agents/council/YYYY-MM-DD-{epic}.md` ```markdown # Retro: [Epic Title] diff --git a/skills-codex/post-mortem/references/output-templates.md b/skills-codex/post-mortem/references/output-templates.md index 67616062c..83ec72275 100644 --- a/skills-codex/post-mortem/references/output-templates.md +++ b/skills-codex/post-mortem/references/output-templates.md @@ -19,7 +19,7 @@ See `.claude/includes/tag-vocabulary.md` for the complete tag vocabulary. ## Retro Summary Template -Write to `.agents/retros/YYYY-MM-DD-{topic}.md`: +Write to `.agents/council/YYYY-MM-DD-{topic}.md`: ```markdown --- @@ -329,7 +329,7 @@ Cross-references added (not superseded): -> Added to "Related" section No action needed: - - .agents/retros/2025-10-01-unrelated.md + - .agents/council/2025-10-01-unrelated.md -> Different topic, no relationship Ready for Phase 5: User Review @@ -340,7 +340,7 @@ Ready for Phase 5: User Review ``` Retro complete: -- Summary: .agents/retros/YYYY-MM-DD-topic.md +- Summary: .agents/council/YYYY-MM-DD-topic.md - Learnings: .agents/learnings/YYYY-MM-DD-topic.md (if applicable) - Patterns: [updated/created files] (if applicable) diff --git a/skills/SKILL-TIERS.md b/skills/SKILL-TIERS.md index a4ffec015..a0061c4e9 100644 --- a/skills/SKILL-TIERS.md +++ b/skills/SKILL-TIERS.md @@ -402,7 +402,7 @@ Orchestrators, interactive skills, and single-task executors stay in the main se | implement | Single-task | Single issue, medium duration | | bug-hunt | Investigator | Hypothesis loop, need to see reasoning | | vibe | Orchestrator | Orchestrates council, reports verdict | -| post-mortem | Orchestrator | Orchestrates council + retro | +| post-mortem | Orchestrator | Orchestrates council + knowledge lifecycle | | pre-mortem | Orchestrator | Default inline, orchestrates if --deep | ### Tier 2: FORK (subagent/worktree via `context: fork`) diff --git a/skills/flywheel/references/promotion-tiers.md b/skills/flywheel/references/promotion-tiers.md index a7be41729..01f5010ef 100644 --- a/skills/flywheel/references/promotion-tiers.md +++ b/skills/flywheel/references/promotion-tiers.md @@ -12,7 +12,7 @@ Defines the maturity pipeline for knowledge artifacts. ## Tier 1: Learnings (`.agents/learnings/`) -- **Source:** `/retro`, `/learn`, `/extract`, promoted from forge +- **Source:** `/retro`, `/post-mortem`, `/extract`, promoted from forge - **Confidence:** 0.3-1.0 - **Citations:** 1+ - **Promotion criteria:** Promote to Tier 2 when confidence >= 0.8 AND cited >= 3 times AND age > 30 days @@ -28,7 +28,7 @@ Defines the maturity pipeline for knowledge artifacts. ## Cross-Repo Promotion -- Any tier can be promoted to `~/.claude/patterns/` via `/learn --global` +- Any tier can be promoted to `~/.claude/patterns/` via `/retro --global` - Global patterns are user-level, shared across all repositories - Promotion is a manual decision (human judgment on cross-repo applicability) - Global patterns are found by `/research`, `/knowledge`, and `/inject` via grep diff --git a/skills/post-mortem/SKILL.md b/skills/post-mortem/SKILL.md index cc9a7cd9f..7c0a42dd7 100644 --- a/skills/post-mortem/SKILL.md +++ b/skills/post-mortem/SKILL.md @@ -274,7 +274,7 @@ Enables adversarial two-round review for post-implementation validation. Use for Inline extraction of learnings from the completed work (formerly delegated to the retro skill). -#### Step 4.1: Gather Context +#### Step EX.1: Gather Context ```bash # Recent commits @@ -291,7 +291,7 @@ Read relevant artifacts: research documents, plan documents, commit messages, co **If retrospecting an epic:** Run the closure integrity quick-check from `references/context-gathering.md` (Phantom Bead Detection + Multi-Wave Regression Scan). Include any warnings in findings. -#### Step 4.2: Classify Learnings +#### Step EX.2: Classify Learnings Ask these questions: @@ -318,7 +318,7 @@ For each learning, capture: - **Why it matters**: Impact on future work - **Confidence**: high, medium, low -#### Step 4.3: Write Learnings +#### Step EX.3: Write Learnings **Write to:** `.agents/learnings/YYYY-MM-DD-.md` @@ -353,13 +353,13 @@ confidence: ... ``` -#### Step 4.4: Classify Learning Scope +#### Step EX.4: Classify Learning Scope -For each learning extracted in Step 4.3, classify: +For each learning extracted in Step EX.3, classify: **Question:** "Does this learning reference specific files, packages, or architecture in THIS repo? Or is it a transferable pattern that helps any project?" -- **Repo-specific** -> Write to `.agents/learnings/` (existing behavior from Step 4.3). Use `git rev-parse --show-toplevel` to resolve repo root — never write relative to cwd. +- **Repo-specific** -> Write to `.agents/learnings/` (existing behavior from Step EX.3). Use `git rev-parse --show-toplevel` to resolve repo root — never write relative to cwd. - **Cross-cutting/transferable** -> Rewrite to remove repo-specific context (file paths, function names, package names), then: 1. Write abstracted version to `~/.agents/learnings/YYYY-MM-DD-.md` (NOT local — one copy only) 2. Run abstraction lint check: @@ -377,7 +377,7 @@ For each learning extracted in Step 4.3, classify: - Local: "Athena's validate package needs O_CREATE|O_EXCL for atomic claims because Zeus spawns concurrent workers" - Global: "Use O_CREATE|O_EXCL for atomic file creation when multiple processes may race on the same path" -#### Step 4.5: Compile Constraint Templates +#### Step EX.5: Compile Constraint Templates For each extracted learning scoring >= 4/5 on actionability AND tagged "constraint" or "anti-pattern", run `bash hooks/constraint-compiler.sh ` to generate a constraint template. @@ -594,7 +594,7 @@ source: "[[.agents/plans/YYYY-MM-DD-]]" **Epic:** **Duration:** -**Cycle-Time Trend:** +**Cycle-Time Trend:** ## Council Verdict: PASS / WARN / FAIL diff --git a/skills/post-mortem/references/context-gathering.md b/skills/post-mortem/references/context-gathering.md index f76311aaf..c3026239b 100644 --- a/skills/post-mortem/references/context-gathering.md +++ b/skills/post-mortem/references/context-gathering.md @@ -46,7 +46,7 @@ python3 ~/.claude/scripts/analyze-sessions.py --session=$SESSION_ID --limit=50 | Conversation Data | Retro Output | |-------------------|--------------| -| `DecisionExtraction` | `.agents/retros/` - Decisions section | +| `DecisionExtraction` | `.agents/council/` - Decisions section | | `QualityResult.issues` | Friction detection | | `DocExtraction(type="warning")` | `.agents/learnings/` | | `DecisionExtraction(type="pattern")` | `.agents/patterns/` | diff --git a/skills/post-mortem/references/learning-templates.md b/skills/post-mortem/references/learning-templates.md index b02316327..09c478e7f 100644 --- a/skills/post-mortem/references/learning-templates.md +++ b/skills/post-mortem/references/learning-templates.md @@ -230,7 +230,7 @@ mcp__ai-platform__memory_store( ## Retro Summary Template -Write to: `.agents/retros/YYYY-MM-DD-{epic}.md` +Write to: `.agents/council/YYYY-MM-DD-{epic}.md` ```markdown # Retro: [Epic Title] diff --git a/skills/post-mortem/references/output-templates.md b/skills/post-mortem/references/output-templates.md index 67616062c..83ec72275 100644 --- a/skills/post-mortem/references/output-templates.md +++ b/skills/post-mortem/references/output-templates.md @@ -19,7 +19,7 @@ See `.claude/includes/tag-vocabulary.md` for the complete tag vocabulary. ## Retro Summary Template -Write to `.agents/retros/YYYY-MM-DD-{topic}.md`: +Write to `.agents/council/YYYY-MM-DD-{topic}.md`: ```markdown --- @@ -329,7 +329,7 @@ Cross-references added (not superseded): -> Added to "Related" section No action needed: - - .agents/retros/2025-10-01-unrelated.md + - .agents/council/2025-10-01-unrelated.md -> Different topic, no relationship Ready for Phase 5: User Review @@ -340,7 +340,7 @@ Ready for Phase 5: User Review ``` Retro complete: -- Summary: .agents/retros/YYYY-MM-DD-topic.md +- Summary: .agents/council/YYYY-MM-DD-topic.md - Learnings: .agents/learnings/YYYY-MM-DD-topic.md (if applicable) - Patterns: [updated/created files] (if applicable)