From ab6dabd542e55089e1052910aded9c5a090441a9 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Thu, 30 Apr 2026 12:20:26 +0200 Subject: [PATCH] fix(ci): correct image digest separator --- .claude/commands/wave.md | 37 + .claude/wave-agent.md | 95 +++ .gitea/workflows/ci.yml | 2 +- .gitea/workflows/deploy.yml | 2 +- .wave/.onboarded | 5 + .wave/.ontology-stale | 0 .wave/contracts/adr-context.schema.json | 44 ++ .wave/contracts/adr-options.schema.json | 38 ++ .wave/contracts/analysis-report.schema.json | 60 ++ .wave/contracts/audit-findings.schema.json | 84 +++ .wave/contracts/audit-inventory.schema.json | 116 ++++ .../audit-publish-result.schema.json | 80 +++ .../contracts/audit-triage-report.schema.json | 133 ++++ .../bootstrap-assessment.schema.json | 69 ++ .../contracts/categorized-changes.schema.json | 55 ++ .wave/contracts/checklist-status.schema.json | 36 + .wave/contracts/clarify-status.schema.json | 30 + .wave/contracts/comment-result.schema.json | 84 +++ .wave/contracts/commit-analysis.schema.json | 53 ++ .../convergent-proposals.schema.json | 81 +++ .wave/contracts/coverage-analysis.schema.json | 24 + .../dead-code-issue-result.schema.json | 71 ++ .wave/contracts/dead-code-scan.schema.json | 47 ++ .../dead-code-verification.schema.json | 48 ++ .wave/contracts/debug-hypotheses.schema.json | 22 + .../contracts/debug-reproduction.schema.json | 21 + .wave/contracts/diff-analysis.schema.json | 41 ++ .../contracts/divergent-findings.schema.json | 82 +++ .../doc-consistency-report.schema.json | 87 +++ .wave/contracts/doc-fix-scan.schema.json | 38 ++ .wave/contracts/doc-issue-result.schema.json | 71 ++ .wave/contracts/doc-scan-results.schema.json | 112 +++ .wave/contracts/docs-phase.schema.json | 112 +++ .wave/contracts/dummy-phase.schema.json | 110 +++ .wave/contracts/enhancement-plan.schema.json | 69 ++ .../contracts/enhancement-results.schema.json | 71 ++ .wave/contracts/epic-assessment.schema.json | 87 +++ .wave/contracts/epic-children.schema.json | 28 + .wave/contracts/epic-report.schema.json | 49 ++ .wave/contracts/epic-scope-plan.schema.json | 52 ++ .wave/contracts/explain-analysis.schema.json | 76 +++ .../contracts/explain-exploration.schema.json | 62 ++ .../contracts/feature-exploration.schema.json | 52 ++ .wave/contracts/feature-plan.schema.json | 33 + .wave/contracts/findings.schema.json | 38 ++ .wave/contracts/gap-analysis.schema.json | 37 + .wave/contracts/gate-result.schema.json | 33 + .../gh-pr-comment-result.schema.json | 29 + .../github-enhancement-plan.schema.json | 65 ++ .../github-enhancement-results.schema.json | 71 ++ .../github-issue-analysis.schema.json | 86 +++ .../github-verification-report.schema.json | 87 +++ .../contracts/hello-world-result.schema.json | 26 + .wave/contracts/implement-phase.schema.json | 138 ++++ .../improvement-assessment.schema.json | 44 ++ .wave/contracts/interface-design.schema.json | 36 + .wave/contracts/issue-assessment.schema.json | 91 +++ .wave/contracts/issue-content.schema.json | 96 +++ .wave/contracts/issue-impl-plan.schema.json | 72 ++ .../issue-update-context.schema.json | 116 ++++ .../contracts/issue-update-draft.schema.json | 73 ++ .../contracts/issue-update-result.schema.json | 57 ++ .wave/contracts/iteration-state.schema.json | 36 + .../contracts/ontology-evolution.schema.json | 38 ++ .wave/contracts/ontology.schema.json | 89 +++ .wave/contracts/plan-exploration.schema.json | 179 +++++ .wave/contracts/plan-review.schema.json | 235 +++++++ .wave/contracts/plan-status.schema.json | 40 ++ .wave/contracts/plan-tasks.schema.json | 142 ++++ .wave/contracts/pr-result.schema.json | 43 ++ .wave/contracts/probed-findings.schema.json | 42 ++ .wave/contracts/project-survey.schema.json | 119 ++++ .wave/contracts/recinq-context.schema.json | 43 ++ .wave/contracts/refactor-analysis.schema.json | 27 + .wave/contracts/research-findings.schema.json | 178 +++++ .wave/contracts/research-report.schema.json | 206 ++++++ .wave/contracts/research-topics.schema.json | 106 +++ .wave/contracts/scope-plan.schema.json | 73 ++ .wave/contracts/scope-report.schema.json | 78 +++ .wave/contracts/security-scan.schema.json | 73 ++ .wave/contracts/spec-phase.schema.json | 119 ++++ .wave/contracts/specify-status.schema.json | 35 + .../supervision-evaluation.schema.json | 97 +++ .../supervision-evidence.schema.json | 83 +++ .wave/contracts/tasks-status.schema.json | 33 + .../contracts/validated-findings.schema.json | 50 ++ .wave/logs/audit-doc-20260328-173838-e10a.log | 106 +++ .wave/logs/audit-dx-20260328-135948-267b.log | 101 +++ .../audit-pedagogy-20260328-175313-79a5.log | 144 ++++ .../audit-pedagogy-20260328-194243-c189.log | 136 ++++ .../audit-pedagogy-20260328-213312-b6f8.log | 231 +++++++ .../audit-pedagogy-20260329-001752-e77c.log | 115 ++++ .../audit-pedagogy-20260329-210909-22e5.log | 103 +++ .../audit-security-20260328-152711-3121.log | 225 +++++++ .../logs/doc-onboard-20260328-161951-4e6a.log | 71 ++ .../impl-feature-20260328-133515-7069.log | 244 +++++++ .../impl-feature-20260328-200101-ecae.log | 244 +++++++ .../logs/impl-hotfix-20260328-162004-c571.log | 113 ++++ .../impl-improve-20260328-163932-ceeb.log | 140 ++++ .../logs/impl-issue-20260328-191418-09a9.log | 114 ++++ .../logs/impl-issue-20260328-191418-09bb.log | 637 ++++++++++++++++++ .../logs/impl-issue-20260328-200048-e45d.log | 226 +++++++ .../logs/impl-issue-20260328-234704-0b6f.log | 171 +++++ .../logs/impl-issue-20260329-215756-8d2f.log | Bin 0 -> 3836 bytes .../logs/impl-issue-20260329-232103-73b5.log | 136 ++++ .wave/logs/test-gen-20260328-152719-741b.log | 172 +++++ .wave/personas/auditor.md | 28 + .wave/personas/base-protocol.md | 70 ++ .wave/personas/bitbucket-analyst.md | 35 + .wave/personas/bitbucket-commenter.md | 67 ++ .wave/personas/bitbucket-enhancer.md | 33 + .wave/personas/bitbucket-scoper.md | 53 ++ .wave/personas/craftsman.md | 45 ++ .wave/personas/debugger.md | 33 + .wave/personas/gitea-analyst.md | 21 + .wave/personas/gitea-commenter.md | 44 ++ .wave/personas/gitea-enhancer.md | 24 + .wave/personas/gitea-scoper.md | 35 + .wave/personas/github-analyst.md | 21 + .wave/personas/github-commenter.md | 56 ++ .wave/personas/github-enhancer.md | 21 + .wave/personas/github-scoper.md | 41 ++ .wave/personas/gitlab-analyst.md | 21 + .wave/personas/gitlab-commenter.md | 40 ++ .wave/personas/gitlab-enhancer.md | 28 + .wave/personas/gitlab-scoper.md | 38 ++ .wave/personas/implementer.md | 33 + .wave/personas/navigator.md | 37 + .wave/personas/pedagogy-auditor.md | 42 ++ .wave/personas/philosopher.md | 47 ++ .wave/personas/planner.md | 39 ++ .wave/personas/provocateur.md | 41 ++ .wave/personas/researcher.md | 37 + .wave/personas/reviewer.md | 34 + .wave/personas/summarizer.md | 35 + .wave/personas/supervisor.md | 34 + .wave/personas/synthesizer.md | 32 + .wave/personas/validator.md | 24 + .wave/pipelines/audit-closed.yaml | 278 ++++++++ .wave/pipelines/audit-consolidate.yaml | 74 ++ .wave/pipelines/audit-dead-code-issue.yaml | 200 ++++++ .wave/pipelines/audit-dead-code-review.yaml | 186 +++++ .wave/pipelines/audit-dead-code.yaml | 285 ++++++++ .wave/pipelines/audit-doc.yaml | 265 ++++++++ .wave/pipelines/audit-dual.yaml | 193 ++++++ .wave/pipelines/audit-dx.yaml | 66 ++ .wave/pipelines/audit-junk-code.yaml | 73 ++ .wave/pipelines/audit-pedagogy.yaml | 147 ++++ .wave/pipelines/audit-quality-loop.yaml | 31 + .wave/pipelines/audit-security.yaml | 157 +++++ .wave/pipelines/audit-ux.yaml | 68 ++ .wave/pipelines/changelog.yaml | 141 ++++ .wave/pipelines/dead-code.yaml | 261 +++++++ .wave/pipelines/debug.yaml | 142 ++++ .wave/pipelines/doc-changelog.yaml | 149 ++++ .wave/pipelines/doc-explain.yaml | 140 ++++ .wave/pipelines/doc-fix.yaml | 255 +++++++ .wave/pipelines/doc-onboard.yaml | 129 ++++ .wave/pipelines/explain.yaml | 131 ++++ .wave/pipelines/gh-pr-review.yaml | 178 +++++ .wave/pipelines/gh-refresh.yaml | 184 +++++ .wave/pipelines/gh-research.yaml | 255 +++++++ .wave/pipelines/gh-rewrite.yaml | 99 +++ .wave/pipelines/gh-scope.yaml | 173 +++++ .wave/pipelines/gt-refresh.yaml | 184 +++++ .wave/pipelines/gt-research.yaml | 255 +++++++ .wave/pipelines/gt-rewrite.yaml | 98 +++ .wave/pipelines/gt-scope.yaml | 167 +++++ .wave/pipelines/impl-feature.yaml | 214 ++++++ .wave/pipelines/impl-hotfix.yaml | 118 ++++ .wave/pipelines/impl-improve.yaml | 133 ++++ .wave/pipelines/impl-issue.yaml | 155 +++++ .wave/pipelines/impl-prototype.yaml | 367 ++++++++++ .wave/pipelines/impl-recinq.yaml | 560 +++++++++++++++ .wave/pipelines/impl-refactor.yaml | 150 +++++ .wave/pipelines/impl-research.yaml | 28 + .wave/pipelines/impl-speckit.yaml | 256 +++++++ .wave/pipelines/onboard.yaml | 123 ++++ .wave/pipelines/ops-bootstrap.yaml | 212 ++++++ .wave/pipelines/ops-debug.yaml | 163 +++++ .wave/pipelines/ops-epic-runner.yaml | 26 + .wave/pipelines/ops-hello-world.yaml | 54 ++ .wave/pipelines/ops-implement-epic.yaml | 146 ++++ .wave/pipelines/ops-issue-quality.yaml | 195 ++++++ .wave/pipelines/ops-parallel-audit.yaml | 101 +++ .wave/pipelines/ops-pr-review.yaml | 242 +++++++ .wave/pipelines/ops-refresh.yaml | 186 +++++ .wave/pipelines/ops-release-harden.yaml | 35 + .wave/pipelines/ops-rewrite.yaml | 123 ++++ .wave/pipelines/ops-supervise.yaml | 178 +++++ .wave/pipelines/plan-adr.yaml | 237 +++++++ .wave/pipelines/plan-research.yaml | 282 ++++++++ .wave/pipelines/plan-scope.yaml | 187 +++++ .wave/pipelines/plan-task.yaml | 218 ++++++ .wave/pipelines/plan.yaml | 208 ++++++ .wave/pipelines/recinq.yaml | 535 +++++++++++++++ .wave/pipelines/refactor.yaml | 136 ++++ .wave/pipelines/test-gen.yaml | 125 ++++ .wave/prompts/bench/solve.md | 21 + .wave/prompts/implement/create-pr.md | 131 ++++ .wave/prompts/implement/fetch-assess.md | 99 +++ .wave/prompts/implement/implement.md | 92 +++ .wave/prompts/implement/plan.md | 91 +++ .wave/prompts/speckit-flow/analyze.md | 47 ++ .wave/prompts/speckit-flow/checklist.md | 40 ++ .wave/prompts/speckit-flow/clarify.md | 42 ++ .wave/prompts/speckit-flow/create-pr.md | 60 ++ .wave/prompts/speckit-flow/implement.md | 49 ++ .wave/prompts/speckit-flow/plan.md | 41 ++ .wave/prompts/speckit-flow/specify.md | 50 ++ .wave/prompts/speckit-flow/tasks.md | 52 ++ .../audit-doc-20260328-173838-e10a.json | 55 ++ .../retros/audit-dx-20260328-135948-267b.json | 25 + .../audit-pedagogy-20260328-175313-79a5.json | 45 ++ .../audit-pedagogy-20260328-194243-c189.json | 45 ++ .../audit-pedagogy-20260328-213312-b6f8.json | 45 ++ .../audit-pedagogy-20260329-001752-e77c.json | 45 ++ .../audit-pedagogy-20260329-210909-22e5.json | 45 ++ .../audit-security-20260328-152711-3121.json | 45 ++ .../doc-onboard-20260328-161951-4e6a.json | 35 + .../impl-feature-20260328-133515-7069.json | 55 ++ .../impl-feature-20260328-200101-ecae.json | 55 ++ .../impl-hotfix-20260328-162004-c571.json | 45 ++ .../impl-improve-20260328-163932-ceeb.json | 45 ++ .../impl-issue-20260328-191418-09a9.json | 55 ++ .../impl-issue-20260328-191418-09bb.json | 55 ++ .../impl-issue-20260328-200048-e45d.json | 55 ++ .../impl-issue-20260328-234704-0b6f.json | 55 ++ .../impl-issue-20260329-232103-73b5.json | 55 ++ .../ops-hello-world-20260328-133020-fbb0.json | 35 + .../retros/test-gen-20260328-152719-741b.json | 45 ++ .wave/state.db | Bin 0 -> 1368064 bytes 232 files changed, 22956 insertions(+), 2 deletions(-) create mode 100644 .claude/commands/wave.md create mode 100644 .claude/wave-agent.md create mode 100644 .wave/.onboarded create mode 100644 .wave/.ontology-stale create mode 100644 .wave/contracts/adr-context.schema.json create mode 100644 .wave/contracts/adr-options.schema.json create mode 100644 .wave/contracts/analysis-report.schema.json create mode 100644 .wave/contracts/audit-findings.schema.json create mode 100644 .wave/contracts/audit-inventory.schema.json create mode 100644 .wave/contracts/audit-publish-result.schema.json create mode 100644 .wave/contracts/audit-triage-report.schema.json create mode 100644 .wave/contracts/bootstrap-assessment.schema.json create mode 100644 .wave/contracts/categorized-changes.schema.json create mode 100644 .wave/contracts/checklist-status.schema.json create mode 100644 .wave/contracts/clarify-status.schema.json create mode 100644 .wave/contracts/comment-result.schema.json create mode 100644 .wave/contracts/commit-analysis.schema.json create mode 100644 .wave/contracts/convergent-proposals.schema.json create mode 100644 .wave/contracts/coverage-analysis.schema.json create mode 100644 .wave/contracts/dead-code-issue-result.schema.json create mode 100644 .wave/contracts/dead-code-scan.schema.json create mode 100644 .wave/contracts/dead-code-verification.schema.json create mode 100644 .wave/contracts/debug-hypotheses.schema.json create mode 100644 .wave/contracts/debug-reproduction.schema.json create mode 100644 .wave/contracts/diff-analysis.schema.json create mode 100644 .wave/contracts/divergent-findings.schema.json create mode 100644 .wave/contracts/doc-consistency-report.schema.json create mode 100644 .wave/contracts/doc-fix-scan.schema.json create mode 100644 .wave/contracts/doc-issue-result.schema.json create mode 100644 .wave/contracts/doc-scan-results.schema.json create mode 100644 .wave/contracts/docs-phase.schema.json create mode 100644 .wave/contracts/dummy-phase.schema.json create mode 100644 .wave/contracts/enhancement-plan.schema.json create mode 100644 .wave/contracts/enhancement-results.schema.json create mode 100644 .wave/contracts/epic-assessment.schema.json create mode 100644 .wave/contracts/epic-children.schema.json create mode 100644 .wave/contracts/epic-report.schema.json create mode 100644 .wave/contracts/epic-scope-plan.schema.json create mode 100644 .wave/contracts/explain-analysis.schema.json create mode 100644 .wave/contracts/explain-exploration.schema.json create mode 100644 .wave/contracts/feature-exploration.schema.json create mode 100644 .wave/contracts/feature-plan.schema.json create mode 100644 .wave/contracts/findings.schema.json create mode 100644 .wave/contracts/gap-analysis.schema.json create mode 100644 .wave/contracts/gate-result.schema.json create mode 100644 .wave/contracts/gh-pr-comment-result.schema.json create mode 100644 .wave/contracts/github-enhancement-plan.schema.json create mode 100644 .wave/contracts/github-enhancement-results.schema.json create mode 100644 .wave/contracts/github-issue-analysis.schema.json create mode 100644 .wave/contracts/github-verification-report.schema.json create mode 100644 .wave/contracts/hello-world-result.schema.json create mode 100644 .wave/contracts/implement-phase.schema.json create mode 100644 .wave/contracts/improvement-assessment.schema.json create mode 100644 .wave/contracts/interface-design.schema.json create mode 100644 .wave/contracts/issue-assessment.schema.json create mode 100644 .wave/contracts/issue-content.schema.json create mode 100644 .wave/contracts/issue-impl-plan.schema.json create mode 100644 .wave/contracts/issue-update-context.schema.json create mode 100644 .wave/contracts/issue-update-draft.schema.json create mode 100644 .wave/contracts/issue-update-result.schema.json create mode 100644 .wave/contracts/iteration-state.schema.json create mode 100644 .wave/contracts/ontology-evolution.schema.json create mode 100644 .wave/contracts/ontology.schema.json create mode 100644 .wave/contracts/plan-exploration.schema.json create mode 100644 .wave/contracts/plan-review.schema.json create mode 100644 .wave/contracts/plan-status.schema.json create mode 100644 .wave/contracts/plan-tasks.schema.json create mode 100644 .wave/contracts/pr-result.schema.json create mode 100644 .wave/contracts/probed-findings.schema.json create mode 100644 .wave/contracts/project-survey.schema.json create mode 100644 .wave/contracts/recinq-context.schema.json create mode 100644 .wave/contracts/refactor-analysis.schema.json create mode 100644 .wave/contracts/research-findings.schema.json create mode 100644 .wave/contracts/research-report.schema.json create mode 100644 .wave/contracts/research-topics.schema.json create mode 100644 .wave/contracts/scope-plan.schema.json create mode 100644 .wave/contracts/scope-report.schema.json create mode 100644 .wave/contracts/security-scan.schema.json create mode 100644 .wave/contracts/spec-phase.schema.json create mode 100644 .wave/contracts/specify-status.schema.json create mode 100644 .wave/contracts/supervision-evaluation.schema.json create mode 100644 .wave/contracts/supervision-evidence.schema.json create mode 100644 .wave/contracts/tasks-status.schema.json create mode 100644 .wave/contracts/validated-findings.schema.json create mode 100644 .wave/logs/audit-doc-20260328-173838-e10a.log create mode 100644 .wave/logs/audit-dx-20260328-135948-267b.log create mode 100644 .wave/logs/audit-pedagogy-20260328-175313-79a5.log create mode 100644 .wave/logs/audit-pedagogy-20260328-194243-c189.log create mode 100644 .wave/logs/audit-pedagogy-20260328-213312-b6f8.log create mode 100644 .wave/logs/audit-pedagogy-20260329-001752-e77c.log create mode 100644 .wave/logs/audit-pedagogy-20260329-210909-22e5.log create mode 100644 .wave/logs/audit-security-20260328-152711-3121.log create mode 100644 .wave/logs/doc-onboard-20260328-161951-4e6a.log create mode 100644 .wave/logs/impl-feature-20260328-133515-7069.log create mode 100644 .wave/logs/impl-feature-20260328-200101-ecae.log create mode 100644 .wave/logs/impl-hotfix-20260328-162004-c571.log create mode 100644 .wave/logs/impl-improve-20260328-163932-ceeb.log create mode 100644 .wave/logs/impl-issue-20260328-191418-09a9.log create mode 100644 .wave/logs/impl-issue-20260328-191418-09bb.log create mode 100644 .wave/logs/impl-issue-20260328-200048-e45d.log create mode 100644 .wave/logs/impl-issue-20260328-234704-0b6f.log create mode 100644 .wave/logs/impl-issue-20260329-215756-8d2f.log create mode 100644 .wave/logs/impl-issue-20260329-232103-73b5.log create mode 100644 .wave/logs/test-gen-20260328-152719-741b.log create mode 100644 .wave/personas/auditor.md create mode 100644 .wave/personas/base-protocol.md create mode 100644 .wave/personas/bitbucket-analyst.md create mode 100644 .wave/personas/bitbucket-commenter.md create mode 100644 .wave/personas/bitbucket-enhancer.md create mode 100644 .wave/personas/bitbucket-scoper.md create mode 100644 .wave/personas/craftsman.md create mode 100644 .wave/personas/debugger.md create mode 100644 .wave/personas/gitea-analyst.md create mode 100644 .wave/personas/gitea-commenter.md create mode 100644 .wave/personas/gitea-enhancer.md create mode 100644 .wave/personas/gitea-scoper.md create mode 100644 .wave/personas/github-analyst.md create mode 100644 .wave/personas/github-commenter.md create mode 100644 .wave/personas/github-enhancer.md create mode 100644 .wave/personas/github-scoper.md create mode 100644 .wave/personas/gitlab-analyst.md create mode 100644 .wave/personas/gitlab-commenter.md create mode 100644 .wave/personas/gitlab-enhancer.md create mode 100644 .wave/personas/gitlab-scoper.md create mode 100644 .wave/personas/implementer.md create mode 100644 .wave/personas/navigator.md create mode 100644 .wave/personas/pedagogy-auditor.md create mode 100644 .wave/personas/philosopher.md create mode 100644 .wave/personas/planner.md create mode 100644 .wave/personas/provocateur.md create mode 100644 .wave/personas/researcher.md create mode 100644 .wave/personas/reviewer.md create mode 100644 .wave/personas/summarizer.md create mode 100644 .wave/personas/supervisor.md create mode 100644 .wave/personas/synthesizer.md create mode 100644 .wave/personas/validator.md create mode 100644 .wave/pipelines/audit-closed.yaml create mode 100644 .wave/pipelines/audit-consolidate.yaml create mode 100644 .wave/pipelines/audit-dead-code-issue.yaml create mode 100644 .wave/pipelines/audit-dead-code-review.yaml create mode 100644 .wave/pipelines/audit-dead-code.yaml create mode 100644 .wave/pipelines/audit-doc.yaml create mode 100644 .wave/pipelines/audit-dual.yaml create mode 100644 .wave/pipelines/audit-dx.yaml create mode 100644 .wave/pipelines/audit-junk-code.yaml create mode 100644 .wave/pipelines/audit-pedagogy.yaml create mode 100644 .wave/pipelines/audit-quality-loop.yaml create mode 100644 .wave/pipelines/audit-security.yaml create mode 100644 .wave/pipelines/audit-ux.yaml create mode 100644 .wave/pipelines/changelog.yaml create mode 100644 .wave/pipelines/dead-code.yaml create mode 100644 .wave/pipelines/debug.yaml create mode 100644 .wave/pipelines/doc-changelog.yaml create mode 100644 .wave/pipelines/doc-explain.yaml create mode 100644 .wave/pipelines/doc-fix.yaml create mode 100644 .wave/pipelines/doc-onboard.yaml create mode 100644 .wave/pipelines/explain.yaml create mode 100644 .wave/pipelines/gh-pr-review.yaml create mode 100644 .wave/pipelines/gh-refresh.yaml create mode 100644 .wave/pipelines/gh-research.yaml create mode 100644 .wave/pipelines/gh-rewrite.yaml create mode 100644 .wave/pipelines/gh-scope.yaml create mode 100644 .wave/pipelines/gt-refresh.yaml create mode 100644 .wave/pipelines/gt-research.yaml create mode 100644 .wave/pipelines/gt-rewrite.yaml create mode 100644 .wave/pipelines/gt-scope.yaml create mode 100644 .wave/pipelines/impl-feature.yaml create mode 100644 .wave/pipelines/impl-hotfix.yaml create mode 100644 .wave/pipelines/impl-improve.yaml create mode 100644 .wave/pipelines/impl-issue.yaml create mode 100644 .wave/pipelines/impl-prototype.yaml create mode 100644 .wave/pipelines/impl-recinq.yaml create mode 100644 .wave/pipelines/impl-refactor.yaml create mode 100644 .wave/pipelines/impl-research.yaml create mode 100644 .wave/pipelines/impl-speckit.yaml create mode 100644 .wave/pipelines/onboard.yaml create mode 100644 .wave/pipelines/ops-bootstrap.yaml create mode 100644 .wave/pipelines/ops-debug.yaml create mode 100644 .wave/pipelines/ops-epic-runner.yaml create mode 100644 .wave/pipelines/ops-hello-world.yaml create mode 100644 .wave/pipelines/ops-implement-epic.yaml create mode 100644 .wave/pipelines/ops-issue-quality.yaml create mode 100644 .wave/pipelines/ops-parallel-audit.yaml create mode 100644 .wave/pipelines/ops-pr-review.yaml create mode 100644 .wave/pipelines/ops-refresh.yaml create mode 100644 .wave/pipelines/ops-release-harden.yaml create mode 100644 .wave/pipelines/ops-rewrite.yaml create mode 100644 .wave/pipelines/ops-supervise.yaml create mode 100644 .wave/pipelines/plan-adr.yaml create mode 100644 .wave/pipelines/plan-research.yaml create mode 100644 .wave/pipelines/plan-scope.yaml create mode 100644 .wave/pipelines/plan-task.yaml create mode 100644 .wave/pipelines/plan.yaml create mode 100644 .wave/pipelines/recinq.yaml create mode 100644 .wave/pipelines/refactor.yaml create mode 100644 .wave/pipelines/test-gen.yaml create mode 100644 .wave/prompts/bench/solve.md create mode 100644 .wave/prompts/implement/create-pr.md create mode 100644 .wave/prompts/implement/fetch-assess.md create mode 100644 .wave/prompts/implement/implement.md create mode 100644 .wave/prompts/implement/plan.md create mode 100644 .wave/prompts/speckit-flow/analyze.md create mode 100644 .wave/prompts/speckit-flow/checklist.md create mode 100644 .wave/prompts/speckit-flow/clarify.md create mode 100644 .wave/prompts/speckit-flow/create-pr.md create mode 100644 .wave/prompts/speckit-flow/implement.md create mode 100644 .wave/prompts/speckit-flow/plan.md create mode 100644 .wave/prompts/speckit-flow/specify.md create mode 100644 .wave/prompts/speckit-flow/tasks.md create mode 100644 .wave/retros/audit-doc-20260328-173838-e10a.json create mode 100644 .wave/retros/audit-dx-20260328-135948-267b.json create mode 100644 .wave/retros/audit-pedagogy-20260328-175313-79a5.json create mode 100644 .wave/retros/audit-pedagogy-20260328-194243-c189.json create mode 100644 .wave/retros/audit-pedagogy-20260328-213312-b6f8.json create mode 100644 .wave/retros/audit-pedagogy-20260329-001752-e77c.json create mode 100644 .wave/retros/audit-pedagogy-20260329-210909-22e5.json create mode 100644 .wave/retros/audit-security-20260328-152711-3121.json create mode 100644 .wave/retros/doc-onboard-20260328-161951-4e6a.json create mode 100644 .wave/retros/impl-feature-20260328-133515-7069.json create mode 100644 .wave/retros/impl-feature-20260328-200101-ecae.json create mode 100644 .wave/retros/impl-hotfix-20260328-162004-c571.json create mode 100644 .wave/retros/impl-improve-20260328-163932-ceeb.json create mode 100644 .wave/retros/impl-issue-20260328-191418-09a9.json create mode 100644 .wave/retros/impl-issue-20260328-191418-09bb.json create mode 100644 .wave/retros/impl-issue-20260328-200048-e45d.json create mode 100644 .wave/retros/impl-issue-20260328-234704-0b6f.json create mode 100644 .wave/retros/impl-issue-20260329-232103-73b5.json create mode 100644 .wave/retros/ops-hello-world-20260328-133020-fbb0.json create mode 100644 .wave/retros/test-gen-20260328-152719-741b.json create mode 100644 .wave/state.db diff --git a/.claude/commands/wave.md b/.claude/commands/wave.md new file mode 100644 index 0000000..db6b85c --- /dev/null +++ b/.claude/commands/wave.md @@ -0,0 +1,37 @@ +--- +description: Run Wave multi-agent pipelines +--- + +## User Input + +```text +$ARGUMENTS +``` + +## Instructions + +You are invoking the Wave multi-agent pipeline orchestrator. Parse the user's arguments to determine which subcommand to run. + +### Subcommand Routing + +Based on the arguments provided: + +**If arguments start with "run"** (e.g., `/wave run impl-issue -- "fix bug"`): +- Execute: `wave run ` +- Example: `wave run -v impl-issue -- "implement feature X"` + +**If arguments start with "status"** (e.g., `/wave status`): +- Execute: `wave list runs --limit 10` +- Show the output to the user in a readable format + +**If arguments start with "list"** (e.g., `/wave list`): +- Execute: `wave list pipelines` +- Show available pipelines to the user + +**If arguments start with "logs"** (e.g., `/wave logs `): +- Execute: `wave logs ` +- Show the pipeline run logs + +**If no arguments or "help"**: +- Show available subcommands: run, status, list, logs +- Example usage for each subcommand diff --git a/.claude/wave-agent.md b/.claude/wave-agent.md new file mode 100644 index 0000000..2734d20 --- /dev/null +++ b/.claude/wave-agent.md @@ -0,0 +1,95 @@ +--- +model: claude-haiku-4-5 +disallowedTools: + - TodoWrite +permissionMode: bypassPermissions +--- +# Wave Agent Protocol + +You are operating within a Wave pipeline step. + +## Operational Context + +- **Fresh context**: You have no memory of prior steps. Each step starts clean. +- **Artifact I/O**: Read inputs from injected artifacts. Write outputs to artifact files. +- **Workspace isolation**: You are in an ephemeral worktree. Changes here do not affect the source repository directly. +- **Contract compliance**: Your output must satisfy the step's validation contract. +- **Permission enforcement**: Tool permissions are enforced by the orchestrator. Do not attempt to bypass restrictions listed below. +- **Real execution only**: Always use actual tool calls to execute commands. Never generate simulated or fabricated output. +- **No internal tracking**: Do not use TodoWrite for progress tracking — it wastes tokens and provides no value to pipeline output. + +## Artifact Conventions + +When reading artifacts from previous steps: +- Artifacts are injected into `.wave/artifacts/` with the name specified in the pipeline +- Read the artifact content to understand what the previous step produced +- Do not assume artifact structure — read and verify +- **Error handling**: If a required artifact is missing or empty, fail immediately with + a clear error message (e.g., "Required artifact 'findings' not found at .wave/artifacts/findings"). + If a JSON artifact fails to parse, report the parse error and do not proceed with stale assumptions + +When writing output artifacts: +- Write to the path specified in the step's `output_artifacts` configuration +- JSON artifacts must be valid JSON matching the contract schema if specified +- Markdown artifacts should be well-structured with clear sections +- Always write output before the step completes — missing artifacts fail the contract + +Path conventions: +- `.wave/artifacts/` — injected artifacts from prior steps (read-only input) +- `.wave/output/` or the path from `output_artifacts` — your step's output files that contract validation checks + +## Tool Usage + +- Use the Edit tool for file modifications. Do NOT use perl, sed, or awk +- Use the Write tool for new files. Do NOT use cat heredocs or echo redirection +- Use the Read tool for reading files. Do NOT use cat, head, or tail +- Use the Grep tool for searching. Do NOT use grep or rg via Bash +- Do NOT push to remote — that happens in the create-pr step +- Do NOT include Co-Authored-By or AI attribution in commits +- Do NOT use GitHub closing keywords (`Closes #N`, `Fixes #N`, `Resolves #N`) in commit messages or PR bodies — use `Related to #N` instead. Closing keywords auto-close issues on merge, which causes false-positive closures when PRs only partially address an issue + +These rules apply to both the main context AND any Task subagents you spawn. + +## Template Variables Reference + +Pipeline prompts may contain template variables that are resolved at runtime. +These are the available variables: + +| Variable | Type | Description | +|----------|------|-------------| +| `{{ input }}` | string | CLI input passed to the pipeline via `wave run -- ""` | +| `{{ pipeline_id }}` | string | Unique identifier for the current pipeline run | +| `{{ forge.cli_tool }}` | string | Git forge CLI tool name (`gh`, `glab`, `tea`, `bb`) | +| `{{ forge.pr_command }}` | string | Forge-specific PR subcommand (`pr`, `mr`, `pulls`) | +| `{{ project.test_command }}` | string | Project's test command (e.g., `go test ./...`) | +| `{{ project.build_command }}` | string | Project's build command (e.g., `go build ./...`) | +| `{{ project.skill }}` | string | Project's primary skill identifier | + +Variables are resolved before the prompt is passed to the persona. Unresolved +variables (e.g., typos) are detected by contract validation and cause step failure. + +## Inter-Step Communication + +- Each step receives only the artifacts explicitly injected via `inject_artifacts` +- You cannot access outputs from steps that are not listed as dependencies +- Your output artifacts will be available to downstream steps that depend on you +- Keep artifact content focused and machine-parseable where possible + + +--- + +# + +You are operating as the persona. + + +--- + +## Restrictions + +The following restrictions are enforced by the pipeline orchestrator. + +### Denied Tools + +- `TodoWrite` + diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 9d66cc0..f81b2f6 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: ci: runs-on: ubuntu-latest container: - image: git.librete.ch/public/runner-image:v0.1.0:9d1e204fe8e06b7d16cdc8da0c7077fa4171daef62099cc8c09993834e576ca5 + image: git.librete.ch/public/runner-image:v0.1.0@sha256:9d1e204fe8e06b7d16cdc8da0c7077fa4171daef62099cc8c09993834e576ca5 timeout-minutes: 10 steps: - uses: actions/checkout@v4 diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index f03da19..e68de12 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -27,7 +27,7 @@ jobs: deploy: runs-on: ubuntu-latest container: - image: git.librete.ch/public/runner-image:v0.1.0:9d1e204fe8e06b7d16cdc8da0c7077fa4171daef62099cc8c09993834e576ca5 + image: git.librete.ch/public/runner-image:v0.1.0@sha256:9d1e204fe8e06b7d16cdc8da0c7077fa4171daef62099cc8c09993834e576ca5 timeout-minutes: 20 if: ${{ vars.DEPLOY_ENABLED == 'true' }} steps: diff --git a/.wave/.onboarded b/.wave/.onboarded new file mode 100644 index 0000000..97c7383 --- /dev/null +++ b/.wave/.onboarded @@ -0,0 +1,5 @@ +{ + "completed": true, + "completed_at": "2026-03-28T12:38:49.56259042+01:00", + "version": 1 +} \ No newline at end of file diff --git a/.wave/.ontology-stale b/.wave/.ontology-stale new file mode 100644 index 0000000..e69de29 diff --git a/.wave/contracts/adr-context.schema.json b/.wave/contracts/adr-context.schema.json new file mode 100644 index 0000000..a54f828 --- /dev/null +++ b/.wave/contracts/adr-context.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ADR Context", + "description": "Contextual information gathered for an Architecture Decision Record", + "type": "object", + "required": ["decision_topic", "current_state", "constraints", "timestamp"], + "properties": { + "decision_topic": { "type": "string", "minLength": 5 }, + "current_state": { + "type": "object", + "required": ["description"], + "properties": { + "description": { "type": "string", "minLength": 10 }, + "affected_files": { "type": "array", "items": { "type": "string" } }, + "affected_components": { "type": "array", "items": { "type": "string" } } + } + }, + "constraints": { + "type": "array", + "items": { + "type": "object", + "required": ["type", "description"], + "properties": { + "type": { "type": "string", "enum": ["technical", "organizational", "timeline"] }, + "description": { "type": "string", "minLength": 5 } + } + } + }, + "precedents": { + "type": "array", + "items": { + "type": "object", + "required": ["description"], + "properties": { + "description": { "type": "string" }, + "location": { "type": "string" }, + "outcome": { "type": "string" } + } + } + }, + "stakeholders": { "type": "array", "items": { "type": "string" } }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/adr-options.schema.json b/.wave/contracts/adr-options.schema.json new file mode 100644 index 0000000..53396ba --- /dev/null +++ b/.wave/contracts/adr-options.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ADR Options Analysis", + "description": "Analysis of options for an architectural decision", + "type": "object", + "required": ["decision_topic", "options", "recommendation", "timestamp"], + "properties": { + "decision_topic": { "type": "string", "minLength": 5 }, + "options": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "required": ["name", "description", "pros", "cons"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 10 }, + "pros": { "type": "array", "items": { "type": "string" } }, + "cons": { "type": "array", "items": { "type": "string" } }, + "effort": { "type": "string", "enum": ["trivial", "small", "medium", "large", "epic"] }, + "risk": { "type": "string", "enum": ["low", "medium", "high"] }, + "reversibility": { "type": "string", "enum": ["easy", "moderate", "difficult", "irreversible"] }, + "compatibility": { "type": "string", "enum": ["high", "medium", "low"] } + } + } + }, + "recommendation": { + "type": "object", + "required": ["option", "rationale", "confidence"], + "properties": { + "option": { "type": "string", "minLength": 1 }, + "rationale": { "type": "string", "minLength": 20 }, + "confidence": { "type": "string", "enum": ["high", "medium", "low"] } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/analysis-report.schema.json b/.wave/contracts/analysis-report.schema.json new file mode 100644 index 0000000..1956d71 --- /dev/null +++ b/.wave/contracts/analysis-report.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Speckit Analysis Report", + "description": "Cross-artifact consistency and quality analysis report before implementation.", + "type": "object", + "required": ["total_requirements", "total_tasks", "coverage_percent", "can_proceed", "feature_dir", "summary"], + "properties": { + "total_requirements": { + "type": "integer", + "description": "Total number of requirements extracted from spec" + }, + "total_tasks": { + "type": "integer", + "description": "Total number of tasks in the task breakdown" + }, + "coverage_percent": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Percentage of requirements covered by tasks (0-100)" + }, + "can_proceed": { + "type": "boolean", + "description": "Whether the feature is ready for implementation" + }, + "feature_dir": { + "type": "string", + "minLength": 1, + "description": "Path to the feature directory" + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "Brief summary of the analysis findings" + }, + "issues": { + "type": "object", + "properties": { + "critical": { + "type": "integer", + "description": "Number of critical severity issues" + }, + "high": { + "type": "integer", + "description": "Number of high severity issues" + }, + "medium": { + "type": "integer", + "description": "Number of medium severity issues" + }, + "low": { + "type": "integer", + "description": "Number of low severity issues" + } + }, + "description": "Issue counts grouped by severity level" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/audit-findings.schema.json b/.wave/contracts/audit-findings.schema.json new file mode 100644 index 0000000..5e31a41 --- /dev/null +++ b/.wave/contracts/audit-findings.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Audit Findings", + "description": "Output from the audit-items step: per-item fidelity classification with evidence", + "type": "object", + "required": ["findings", "summary", "timestamp"], + "properties": { + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["item_number", "item_type", "item_url", "title", "category", "evidence"], + "properties": { + "item_number": { + "type": "integer", + "minimum": 1, + "description": "Source issue or PR number" + }, + "item_type": { + "type": "string", + "enum": ["issue", "pr"], + "description": "Whether the source item is an issue or PR" + }, + "item_url": { + "type": "string", + "description": "Full GitHub URL for reference" + }, + "title": { + "type": "string", + "minLength": 1, + "description": "Item title for readability" + }, + "category": { + "type": "string", + "enum": ["verified", "partial", "regressed", "obsolete", "unverifiable"], + "description": "Fidelity classification" + }, + "evidence": { + "type": "array", + "items": { "type": "string" }, + "description": "File paths, code references, and commit SHAs supporting the classification" + }, + "unmet_criteria": { + "type": "array", + "items": { "type": "string" }, + "description": "Specific acceptance criteria not satisfied (for partial/regressed)" + }, + "remediation": { + "type": "string", + "description": "Actionable description of what needs to change (empty for verified/obsolete)" + } + } + }, + "description": "Per-item audit findings" + }, + "summary": { + "type": "object", + "required": ["total_audited"], + "properties": { + "total_audited": { + "type": "integer", + "minimum": 0, + "description": "Total number of items audited" + }, + "by_category": { + "type": "object", + "properties": { + "verified": { "type": "integer", "minimum": 0 }, + "partial": { "type": "integer", "minimum": 0 }, + "regressed": { "type": "integer", "minimum": 0 }, + "obsolete": { "type": "integer", "minimum": 0 }, + "unverifiable": { "type": "integer", "minimum": 0 } + }, + "description": "Count of findings per fidelity category" + } + } + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the audit was performed" + } + } +} diff --git a/.wave/contracts/audit-inventory.schema.json b/.wave/contracts/audit-inventory.schema.json new file mode 100644 index 0000000..c936160 --- /dev/null +++ b/.wave/contracts/audit-inventory.schema.json @@ -0,0 +1,116 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Audit Inventory", + "description": "Output from the collect-inventory step: all closed issues and merged PRs with extracted metadata", + "type": "object", + "required": ["scope", "items", "timestamp"], + "properties": { + "scope": { + "type": "object", + "required": ["mode"], + "properties": { + "mode": { + "type": "string", + "enum": ["full", "time_range", "label"], + "description": "How the inventory was scoped" + }, + "filter": { + "type": "string", + "description": "The filter expression applied (e.g., 'last 30 days', 'label:enhancement')" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name (owner/repo)" + } + } + }, + "items": { + "type": "array", + "items": { + "type": "object", + "required": ["number", "type", "title", "url", "close_reason", "closed_at"], + "properties": { + "number": { + "type": "integer", + "minimum": 1, + "description": "GitHub issue or PR number" + }, + "type": { + "type": "string", + "enum": ["issue", "pr"], + "description": "Whether this is an issue or pull request" + }, + "title": { + "type": "string", + "minLength": 1, + "description": "Issue or PR title" + }, + "url": { + "type": "string", + "description": "Full GitHub URL" + }, + "body": { + "type": "string", + "description": "Full issue/PR body text" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Associated labels" + }, + "close_reason": { + "type": "string", + "description": "Why the item was closed (completed, merged, not_planned)" + }, + "closed_at": { + "type": "string", + "format": "date-time", + "description": "When the item was closed" + }, + "linked_prs": { + "type": "array", + "items": { "type": "integer" }, + "description": "PR numbers linked to this issue" + }, + "linked_commits": { + "type": "array", + "items": { "type": "string" }, + "description": "Commit SHAs associated with this item" + }, + "acceptance_criteria": { + "type": "array", + "items": { "type": "string" }, + "description": "Extracted acceptance criteria from the body" + } + } + }, + "description": "All inventory items to audit" + }, + "summary": { + "type": "object", + "properties": { + "total_issues": { + "type": "integer", + "minimum": 0, + "description": "Number of closed issues in inventory" + }, + "total_prs": { + "type": "integer", + "minimum": 0, + "description": "Number of merged PRs in inventory" + }, + "excluded_not_planned": { + "type": "integer", + "minimum": 0, + "description": "Number of issues excluded due to not_planned close reason" + } + } + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the inventory was collected" + } + } +} diff --git a/.wave/contracts/audit-publish-result.schema.json b/.wave/contracts/audit-publish-result.schema.json new file mode 100644 index 0000000..9d81687 --- /dev/null +++ b/.wave/contracts/audit-publish-result.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Audit Publish Result", + "description": "Output from the publish step: GitHub issue creation results for actionable findings", + "type": "object", + "required": ["success", "repository", "timestamp"], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the publish operation completed successfully" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name (owner/repo)" + }, + "issues_created": { + "type": "array", + "items": { + "type": "object", + "required": ["number", "url", "source_item", "category"], + "properties": { + "number": { + "type": "integer", + "minimum": 1, + "description": "Created GitHub issue number" + }, + "url": { + "type": "string", + "description": "URL to the created issue" + }, + "source_item": { + "type": "integer", + "minimum": 1, + "description": "Original issue/PR number that this finding references" + }, + "category": { + "type": "string", + "enum": ["partial", "regressed"], + "description": "Fidelity category of the finding" + } + } + }, + "description": "List of GitHub issues created for fixable gaps" + }, + "issues_skipped": { + "type": "integer", + "minimum": 0, + "description": "Number of findings skipped (verified, obsolete, unverifiable, or no remediation)" + }, + "skipped": { + "type": "boolean", + "description": "True if no issues were created because all findings are verified/obsolete" + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["authentication_failed", "rate_limit_exceeded", "permission_denied", "network_error", "api_error", "unknown_error"], + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "retryable": { + "type": "boolean", + "description": "Whether the operation can be retried" + } + }, + "description": "Error details (only present if success=false)" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Operation timestamp" + } + } +} diff --git a/.wave/contracts/audit-triage-report.schema.json b/.wave/contracts/audit-triage-report.schema.json new file mode 100644 index 0000000..76decc8 --- /dev/null +++ b/.wave/contracts/audit-triage-report.schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Audit Triage Report", + "description": "Output from the compose-triage step: aggregated findings with prioritized action list", + "type": "object", + "required": ["metadata", "summary", "findings", "prioritized_actions"], + "properties": { + "metadata": { + "type": "object", + "required": ["scope", "timestamp", "repository", "total_items_audited"], + "properties": { + "scope": { + "type": "string", + "description": "Audit scope description (e.g., 'full', 'last 30 days', 'label:enhancement')" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the triage report was composed" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name (owner/repo)" + }, + "total_items_audited": { + "type": "integer", + "minimum": 0, + "description": "Total number of items that were audited" + } + } + }, + "summary": { + "type": "object", + "required": ["verified", "partial", "regressed", "obsolete", "unverifiable"], + "properties": { + "verified": { + "type": "integer", + "minimum": 0, + "description": "Items fully implemented and intact" + }, + "partial": { + "type": "integer", + "minimum": 0, + "description": "Items with some acceptance criteria unmet" + }, + "regressed": { + "type": "integer", + "minimum": 0, + "description": "Items that were implemented but later broken" + }, + "obsolete": { + "type": "integer", + "minimum": 0, + "description": "Items no longer applicable to current codebase" + }, + "unverifiable": { + "type": "integer", + "minimum": 0, + "description": "Items with no traceable implementation artifacts" + } + } + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["item_number", "item_type", "item_url", "title", "category", "evidence"], + "properties": { + "item_number": { + "type": "integer", + "minimum": 1, + "description": "Source issue or PR number" + }, + "item_type": { + "type": "string", + "enum": ["issue", "pr"], + "description": "Whether the source item is an issue or PR" + }, + "item_url": { + "type": "string", + "description": "Full GitHub URL" + }, + "title": { + "type": "string", + "minLength": 1, + "description": "Item title" + }, + "category": { + "type": "string", + "enum": ["verified", "partial", "regressed", "obsolete", "unverifiable"], + "description": "Fidelity classification" + }, + "evidence": { + "type": "array", + "items": { "type": "string" }, + "description": "Supporting evidence (file paths, code refs, commit SHAs)" + }, + "remediation": { + "type": "string", + "description": "Actionable fix description" + } + } + }, + "description": "All findings grouped by category (regressed first, then partial, then unverifiable, then obsolete, then verified)" + }, + "prioritized_actions": { + "type": "array", + "items": { + "type": "object", + "required": ["priority", "item_number", "action_description"], + "properties": { + "priority": { + "type": "integer", + "minimum": 1, + "description": "Priority rank (1 = highest)" + }, + "item_number": { + "type": "integer", + "minimum": 1, + "description": "Source issue or PR number" + }, + "action_description": { + "type": "string", + "minLength": 1, + "description": "What needs to be done to resolve this finding" + } + } + }, + "description": "Prioritized list of remediation actions (regressed first, then partial)" + } + } +} diff --git a/.wave/contracts/bootstrap-assessment.schema.json b/.wave/contracts/bootstrap-assessment.schema.json new file mode 100644 index 0000000..b4c5ffb --- /dev/null +++ b/.wave/contracts/bootstrap-assessment.schema.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Bootstrap Project Assessment", + "type": "object", + "required": ["flavour", "project_intent", "existing_files", "scaffold_recommendations"], + "properties": { + "flavour": { + "type": "string", + "description": "Detected or configured project language/framework (e.g. go, rust, node, bun, python, csharp)" + }, + "project_intent": { + "type": "string", + "description": "Description of the project's purpose derived from user input, README, or design docs" + }, + "existing_files": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of files already present in the project directory" + }, + "scaffold_recommendations": { + "type": "object", + "required": ["files_to_create", "build_system", "ci_provider"], + "properties": { + "files_to_create": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Recommended files to scaffold for this flavour" + }, + "build_system": { + "type": "string", + "description": "Recommended build system (e.g. cargo, go, npm, bun, pip, dotnet)" + }, + "ci_provider": { + "type": "string", + "description": "CI provider to generate config for (e.g. github-actions)" + }, + "gitignore_patterns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Patterns to include in .gitignore" + } + } + }, + "wave_config": { + "type": "object", + "properties": { + "language": { + "type": "string", + "description": "Value of project.language from wave.yaml if set" + }, + "build_command": { + "type": "string", + "description": "Value of project.build_command from wave.yaml if set" + }, + "test_command": { + "type": "string", + "description": "Value of project.test_command from wave.yaml if set" + } + }, + "description": "Relevant configuration from wave.yaml" + } + } +} diff --git a/.wave/contracts/categorized-changes.schema.json b/.wave/contracts/categorized-changes.schema.json new file mode 100644 index 0000000..e17dae6 --- /dev/null +++ b/.wave/contracts/categorized-changes.schema.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Categorized Changelog Changes", + "description": "Categorized and described changes for changelog generation", + "type": "object", + "required": ["version_label", "sections", "timestamp"], + "properties": { + "version_label": { "type": "string", "minLength": 1 }, + "breaking_changes": { + "type": "array", + "items": { + "type": "object", + "required": ["description"], + "properties": { + "description": { "type": "string" }, + "migration": { "type": "string" }, + "related_commits": { "type": "array", "items": { "type": "string" } } + } + } + }, + "sections": { + "type": "array", + "items": { + "type": "object", + "required": ["title", "type", "entries"], + "properties": { + "title": { "type": "string" }, + "type": { "type": "string" }, + "entries": { + "type": "array", + "items": { + "type": "object", + "required": ["description"], + "properties": { + "description": { "type": "string" }, + "scope": { "type": "string" }, + "commits": { "type": "array", "items": { "type": "string" } }, + "notable": { "type": "boolean" } + } + } + } + } + } + }, + "contributors": { "type": "array", "items": { "type": "string" } }, + "stats": { + "type": "object", + "properties": { + "total_entries": { "type": "integer" }, + "notable_entries": { "type": "integer" } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/checklist-status.schema.json b/.wave/contracts/checklist-status.schema.json new file mode 100644 index 0000000..a2fe86e --- /dev/null +++ b/.wave/contracts/checklist-status.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Speckit Checklist Status", + "description": "Status report from the checklist step: quality checklist generation for requirement validation.", + "type": "object", + "required": ["checklist_files", "total_items", "feature_dir", "summary"], + "properties": { + "checklist_files": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths to generated checklist files" + }, + "total_items": { + "type": "integer", + "minimum": 0, + "description": "Total number of checklist items across all files" + }, + "feature_dir": { + "type": "string", + "minLength": 1, + "description": "Path to the feature directory" + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "Brief description of the checklists generated" + }, + "critical_gaps": { + "type": "integer", + "description": "Number of critical quality gaps identified" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/clarify-status.schema.json b/.wave/contracts/clarify-status.schema.json new file mode 100644 index 0000000..2622f60 --- /dev/null +++ b/.wave/contracts/clarify-status.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Speckit Clarify Status", + "description": "Status report from the clarify step: ambiguity resolution and spec refinement.", + "type": "object", + "required": ["questions_asked", "answers_encoded", "feature_dir", "summary"], + "properties": { + "questions_asked": { + "type": "integer", + "minimum": 0, + "description": "Number of clarification questions identified" + }, + "answers_encoded": { + "type": "integer", + "minimum": 0, + "description": "Number of clarifications resolved and encoded into the spec" + }, + "feature_dir": { + "type": "string", + "minLength": 1, + "description": "Path to the feature directory" + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "Brief description of clarifications made" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/comment-result.schema.json b/.wave/contracts/comment-result.schema.json new file mode 100644 index 0000000..9e7aad3 --- /dev/null +++ b/.wave/contracts/comment-result.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Comment Result", + "description": "Result of posting research report as a GitHub issue comment", + "type": "object", + "required": ["success", "issue_reference", "timestamp"], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the comment was successfully posted" + }, + "issue_reference": { + "type": "object", + "required": ["issue_number", "repository"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1, + "description": "Issue number that was commented on" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name" + }, + "issue_url": { + "type": "string", + "format": "uri", + "description": "URL to the issue" + } + } + }, + "comment": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "minimum": 1, + "description": "GitHub comment ID" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Direct URL to the posted comment" + }, + "body_length": { + "type": "integer", + "minimum": 0, + "description": "Length of the posted comment body in characters" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the comment was created" + } + }, + "description": "Details of the posted comment (only present if success=true)" + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["authentication_failed", "rate_limit_exceeded", "issue_not_found", "issue_locked", "permission_denied", "comment_too_long", "network_error", "api_error", "unknown_error"], + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "retryable": { + "type": "boolean", + "description": "Whether the operation can be retried" + } + }, + "description": "Error details (only present if success=false)" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Operation timestamp" + } + } +} diff --git a/.wave/contracts/commit-analysis.schema.json b/.wave/contracts/commit-analysis.schema.json new file mode 100644 index 0000000..e0d4150 --- /dev/null +++ b/.wave/contracts/commit-analysis.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Commit Analysis", + "description": "Structured analysis of git commits for changelog generation", + "type": "object", + "required": ["range", "commits", "summary", "timestamp"], + "properties": { + "range": { + "type": "object", + "required": ["from", "to", "total_commits"], + "properties": { + "from": { "type": "string", "minLength": 1 }, + "to": { "type": "string", "minLength": 1 }, + "total_commits": { "type": "integer", "minimum": 0 }, + "date_range": { + "type": "object", + "properties": { + "start": { "type": "string" }, + "end": { "type": "string" } + } + } + } + }, + "commits": { + "type": "array", + "items": { + "type": "object", + "required": ["hash", "date", "type", "subject"], + "properties": { + "hash": { "type": "string", "minLength": 4 }, + "author": { "type": "string" }, + "date": { "type": "string" }, + "type": { "type": "string", "enum": ["feat", "fix", "docs", "refactor", "test", "chore", "perf", "ci", "other"] }, + "scope": { "type": ["string", "null"] }, + "subject": { "type": "string", "minLength": 1 }, + "breaking": { "type": "boolean" }, + "breaking_note": { "type": "string" } + } + } + }, + "summary": { + "type": "object", + "required": ["by_type"], + "properties": { + "by_type": { "type": "object" }, + "breaking_changes": { "type": "integer", "minimum": 0 }, + "contributors": { "type": "array", "items": { "type": "string" } }, + "most_changed_scopes": { "type": "array", "items": { "type": "string" } } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/convergent-proposals.schema.json b/.wave/contracts/convergent-proposals.schema.json new file mode 100644 index 0000000..5a64db4 --- /dev/null +++ b/.wave/contracts/convergent-proposals.schema.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Convergent Proposals", + "description": "Prioritized simplification proposals from convergent thinking phase", + "type": "object", + "required": ["source_findings", "validation_summary", "proposals", "timestamp"], + "properties": { + "source_findings": { + "type": "object", + "required": ["total_reviewed", "confirmed", "partially_confirmed", "rejected"], + "properties": { + "total_reviewed": { "type": "integer" }, + "confirmed": { "type": "integer" }, + "partially_confirmed": { "type": "integer" }, + "rejected": { "type": "integer" }, + "rejection_reasons": { + "type": "array", + "items": { + "type": "object", + "required": ["finding_id", "reason"], + "properties": { + "finding_id": { "type": "string" }, + "reason": { "type": "string" } + } + } + } + } + }, + "validation_summary": { + "type": "string", + "description": "Narrative summary of the converge→diverge→converge validation process" + }, + "proposals": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "title", "description", "source_findings", "impact", "effort", "risk", "tier", "files"], + "properties": { + "id": { "type": "string", "pattern": "^SMP-\\d{3}$" }, + "title": { "type": "string" }, + "description": { "type": "string" }, + "source_findings": { + "type": "array", + "items": { "type": "string" }, + "description": "DVG-xxx IDs this proposal addresses" + }, + "impact": { "type": "string", "enum": ["high", "medium", "low"] }, + "effort": { "type": "string", "enum": ["small", "medium", "large"] }, + "risk": { "type": "string", "enum": ["low", "medium", "high"] }, + "tier": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Priority tier: 1=do now, 2=do next, 3=consider later" + }, + "files": { + "type": "array", + "items": { "type": "string" }, + "description": "Files that would be modified" + }, + "dependencies": { + "type": "array", + "items": { "type": "string" }, + "description": "SMP-xxx IDs that must be applied first" + }, + "lines_removed_estimate": { "type": "integer" }, + "second_order_effects": { + "type": "array", + "items": { "type": "string" }, + "description": "Insights discovered during deeper probing" + } + } + } + }, + "eighty_twenty_analysis": { + "type": "string", + "description": "Which 20% of proposals yield 80% of the simplification benefit" + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/coverage-analysis.schema.json b/.wave/contracts/coverage-analysis.schema.json new file mode 100644 index 0000000..a964750 --- /dev/null +++ b/.wave/contracts/coverage-analysis.schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["current_coverage", "uncovered_functions", "uncovered_branches", "edge_cases", "mock_requirements"], + "properties": { + "current_coverage": { "type": "string" }, + "uncovered_functions": { + "type": "array", + "items": { "type": "string" } + }, + "uncovered_branches": { + "type": "array", + "items": { "type": "string" } + }, + "edge_cases": { + "type": "array", + "items": { "type": "string" } + }, + "mock_requirements": { + "type": "array", + "items": { "type": "string" } + } + } +} diff --git a/.wave/contracts/dead-code-issue-result.schema.json b/.wave/contracts/dead-code-issue-result.schema.json new file mode 100644 index 0000000..b444652 --- /dev/null +++ b/.wave/contracts/dead-code-issue-result.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Dead Code Issue Result", + "description": "Output from the create-issue step: GitHub issue creation result for dead code findings", + "type": "object", + "required": ["success", "repository", "timestamp"], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the issue was successfully created" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name (owner/repo)" + }, + "issue": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "minimum": 1, + "description": "GitHub issue number" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL to the created issue" + }, + "title": { + "type": "string", + "description": "Issue title" + }, + "finding_count": { + "type": "integer", + "minimum": 0, + "description": "Number of dead code findings reported in the issue" + } + }, + "description": "Details of the created issue (only present if success=true)" + }, + "skipped": { + "type": "boolean", + "description": "True if no issue was created because no findings were found" + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["authentication_failed", "rate_limit_exceeded", "permission_denied", "network_error", "api_error", "unknown_error"], + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "retryable": { + "type": "boolean", + "description": "Whether the operation can be retried" + } + }, + "description": "Error details (only present if success=false)" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Operation timestamp" + } + } +} diff --git a/.wave/contracts/dead-code-scan.schema.json b/.wave/contracts/dead-code-scan.schema.json new file mode 100644 index 0000000..d971065 --- /dev/null +++ b/.wave/contracts/dead-code-scan.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Dead Code Scan", + "description": "Scan results for dead or redundant code", + "type": "object", + "required": ["target", "findings", "summary", "timestamp"], + "properties": { + "target": { "type": "string", "minLength": 1 }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type", "location", "description", "confidence", "safe_to_remove"], + "properties": { + "id": { "type": "string", "pattern": "^DC-[0-9]{1,4}$" }, + "type": { + "type": "string", + "enum": ["unused_export", "unreachable", "orphaned_file", "redundant", "stale_test", "unused_import", "commented_code", "duplicate_signature"] + }, + "location": { "type": "string", "minLength": 1 }, + "symbol": { "type": "string" }, + "description": { "type": "string", "minLength": 5 }, + "evidence": { "type": "string" }, + "confidence": { "type": "string", "enum": ["high", "medium", "low"] }, + "safe_to_remove": { "type": "boolean" }, + "removal_note": { "type": "string" }, + "suggested_action": { + "type": "string", + "enum": ["remove", "consolidate", "investigate"], + "description": "Recommended action for this finding" + } + } + } + }, + "summary": { + "type": "object", + "required": ["total_findings"], + "properties": { + "total_findings": { "type": "integer", "minimum": 0 }, + "by_type": { "type": "object" }, + "high_confidence_count": { "type": "integer", "minimum": 0 }, + "estimated_lines_removable": { "type": "integer", "minimum": 0 } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/dead-code-verification.schema.json b/.wave/contracts/dead-code-verification.schema.json new file mode 100644 index 0000000..2389561 --- /dev/null +++ b/.wave/contracts/dead-code-verification.schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["verdict", "items_removed", "items_skipped", "lines_removed", "test_status", "summary"], + "properties": { + "verdict": { + "type": "string", + "const": "CLEAN", + "description": "Must be CLEAN for the pipeline to proceed. Output NEEDS_REVIEW to intentionally fail contract validation and halt the pipeline." + }, + "items_removed": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "description", "justification"], + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" }, + "justification": { "type": "string" } + } + } + }, + "items_skipped": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "description", "reason"], + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" }, + "reason": { "type": "string" } + } + } + }, + "lines_removed": { + "type": "integer", + "minimum": 0 + }, + "test_status": { + "type": "string", + "enum": ["passing", "failing", "not_run"] + }, + "summary": { + "type": "string", + "description": "Human-readable summary of the verification" + } + } +} diff --git a/.wave/contracts/debug-hypotheses.schema.json b/.wave/contracts/debug-hypotheses.schema.json new file mode 100644 index 0000000..f63cce4 --- /dev/null +++ b/.wave/contracts/debug-hypotheses.schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["hypotheses"], + "properties": { + "hypotheses": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "description", "likelihood", "test_approach"], + "properties": { + "id": { "type": "integer" }, + "description": { "type": "string", "minLength": 1 }, + "likelihood": { "type": "string", "enum": ["high", "medium", "low"] }, + "test_approach": { "type": "string", "minLength": 1 }, + "expected_result_if_true": { "type": "string" } + } + } + } + } +} diff --git a/.wave/contracts/debug-reproduction.schema.json b/.wave/contracts/debug-reproduction.schema.json new file mode 100644 index 0000000..a174d1c --- /dev/null +++ b/.wave/contracts/debug-reproduction.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["expected_behavior", "actual_behavior", "reproduction_steps", "relevant_files"], + "properties": { + "expected_behavior": { "type": "string", "minLength": 1 }, + "actual_behavior": { "type": "string", "minLength": 1 }, + "reproduction_steps": { + "type": "array", + "items": { "type": "string" } + }, + "relevant_files": { + "type": "array", + "items": { "type": "string" } + }, + "environmental_factors": { + "type": "array", + "items": { "type": "string" } + } + } +} diff --git a/.wave/contracts/diff-analysis.schema.json b/.wave/contracts/diff-analysis.schema.json new file mode 100644 index 0000000..c91a9fb --- /dev/null +++ b/.wave/contracts/diff-analysis.schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["pr_metadata", "files_changed", "modules_affected", "related_tests", "breaking_changes"], + "properties": { + "pr_metadata": { + "type": "object", + "required": ["number", "url", "head_branch", "base_branch"], + "properties": { + "number": { "type": "integer", "description": "PR number" }, + "url": { "type": "string", "description": "Full PR URL" }, + "head_branch": { "type": "string", "description": "Source branch name" }, + "base_branch": { "type": "string", "description": "Target branch name" } + } + }, + "files_changed": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "change_type", "purpose"], + "properties": { + "path": { "type": "string" }, + "change_type": { "type": "string", "enum": ["added", "modified", "deleted"] }, + "purpose": { "type": "string" } + } + } + }, + "modules_affected": { + "type": "array", + "items": { "type": "string" } + }, + "related_tests": { + "type": "array", + "items": { "type": "string" } + }, + "breaking_changes": { + "type": "array", + "items": { "type": "string" } + } + } +} diff --git a/.wave/contracts/divergent-findings.schema.json b/.wave/contracts/divergent-findings.schema.json new file mode 100644 index 0000000..5bf7e0a --- /dev/null +++ b/.wave/contracts/divergent-findings.schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Divergent Findings", + "description": "Broad-net findings from divergent thinking phase of the recinq pipeline", + "type": "object", + "required": ["scope", "findings", "metrics_summary", "timestamp"], + "properties": { + "scope": { + "type": "object", + "required": ["target", "files_scanned", "total_lines"], + "properties": { + "target": { "type": "string", "description": "What was analyzed (path, module, or 'whole-project')" }, + "files_scanned": { "type": "integer" }, + "total_lines": { "type": "integer" } + } + }, + "findings": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "category", "title", "description", "evidence", "severity"], + "properties": { + "id": { "type": "string", "pattern": "^DVG-\\d{3}$" }, + "category": { + "type": "string", + "enum": [ + "premature_abstraction", + "unnecessary_indirection", + "overengineering", + "yagni_violation", + "accidental_complexity", + "copy_paste_drift", + "dead_weight", + "naming_lie", + "dependency_gravity", + "missing_abstraction", + "other" + ] + }, + "title": { "type": "string" }, + "description": { "type": "string" }, + "evidence": { + "type": "object", + "required": ["files"], + "properties": { + "files": { "type": "array", "items": { "type": "string" } }, + "line_count": { "type": "integer" }, + "reference_count": { "type": "integer" }, + "change_frequency": { "type": "integer" }, + "metrics": { "type": "string" } + } + }, + "severity": { + "type": "string", + "enum": ["critical", "high", "medium", "low"] + }, + "confidence": { + "type": "string", + "enum": ["high", "medium", "low"], + "description": "How confident the provocateur is in this finding" + } + } + } + }, + "metrics_summary": { + "type": "object", + "required": ["total_findings"], + "properties": { + "total_findings": { "type": "integer" }, + "by_category": { "type": "object", "additionalProperties": { "type": "integer" } }, + "by_severity": { "type": "object", "additionalProperties": { "type": "integer" } }, + "hotspot_files": { + "type": "array", + "items": { "type": "string" }, + "description": "Files appearing in multiple findings" + } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/doc-consistency-report.schema.json b/.wave/contracts/doc-consistency-report.schema.json new file mode 100644 index 0000000..3eea472 --- /dev/null +++ b/.wave/contracts/doc-consistency-report.schema.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Documentation Consistency Report", + "description": "Output from the analyze-consistency step: list of inconsistencies between code and documentation", + "type": "object", + "required": ["summary", "inconsistencies", "timestamp"], + "properties": { + "summary": { + "type": "object", + "required": ["total_count", "by_severity"], + "properties": { + "total_count": { + "type": "integer", + "minimum": 0, + "description": "Total number of inconsistencies found" + }, + "by_severity": { + "type": "object", + "properties": { + "critical": { "type": "integer", "minimum": 0 }, + "high": { "type": "integer", "minimum": 0 }, + "medium": { "type": "integer", "minimum": 0 }, + "low": { "type": "integer", "minimum": 0 } + }, + "description": "Count of inconsistencies by severity level" + }, + "clean": { + "type": "boolean", + "description": "True if no inconsistencies were found" + } + } + }, + "inconsistencies": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "severity", "category", "title", "description", "source_location", "doc_location"], + "properties": { + "id": { + "type": "string", + "pattern": "^DOC-[0-9]{1,4}$", + "description": "Unique inconsistency ID (DOC-001, DOC-002, etc.)" + }, + "severity": { + "type": "string", + "enum": ["critical", "high", "medium", "low"], + "description": "Severity of the inconsistency" + }, + "category": { + "type": "string", + "enum": ["cli_flags", "personas", "pipelines", "contracts", "env_vars", "api", "stale_reference", "missing_docs", "other"], + "description": "Category of the inconsistency" + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "description": "Short title describing the inconsistency" + }, + "description": { + "type": "string", + "minLength": 1, + "description": "Detailed description of the inconsistency and what needs to change" + }, + "source_location": { + "type": "string", + "description": "File and line where the source of truth is (code, pipeline, etc.)" + }, + "doc_location": { + "type": "string", + "description": "File and section where the documentation is stale or missing" + }, + "fix_description": { + "type": "string", + "description": "What specifically needs to be updated" + } + } + }, + "description": "List of documentation inconsistencies" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the analysis was performed" + } + } +} diff --git a/.wave/contracts/doc-fix-scan.schema.json b/.wave/contracts/doc-fix-scan.schema.json new file mode 100644 index 0000000..aa831f9 --- /dev/null +++ b/.wave/contracts/doc-fix-scan.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Doc Fix Scan Results", + "description": "Documentation inconsistency scan for doc-fix pipeline", + "type": "object", + "required": ["scan_scope", "findings", "summary", "timestamp"], + "properties": { + "scan_scope": { "type": "string", "minLength": 1 }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type", "severity", "title", "description"], + "properties": { + "id": { "type": "string", "pattern": "^DOC-[0-9]{1,4}$" }, + "type": { "type": "string", "enum": ["MISSING_DOCS", "STALE_DOCS", "INACCURATE", "INCOMPLETE"] }, + "severity": { "type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"] }, + "title": { "type": "string", "minLength": 3 }, + "doc_location": { "type": "string" }, + "code_location": { "type": "string" }, + "description": { "type": "string", "minLength": 10 }, + "suggested_fix": { "type": "string" } + } + } + }, + "summary": { + "type": "object", + "required": ["total_findings"], + "properties": { + "total_findings": { "type": "integer", "minimum": 0 }, + "by_type": { "type": "object" }, + "by_severity": { "type": "object" }, + "fixable_count": { "type": "integer", "minimum": 0 } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/doc-issue-result.schema.json b/.wave/contracts/doc-issue-result.schema.json new file mode 100644 index 0000000..cf1ae31 --- /dev/null +++ b/.wave/contracts/doc-issue-result.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Documentation Issue Result", + "description": "Output from the create-issue step: GitHub issue creation result", + "type": "object", + "required": ["success", "repository", "timestamp"], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the issue was successfully created" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name (owner/repo)" + }, + "issue": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "minimum": 1, + "description": "GitHub issue number" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL to the created issue" + }, + "title": { + "type": "string", + "description": "Issue title" + }, + "inconsistency_count": { + "type": "integer", + "minimum": 0, + "description": "Number of inconsistencies reported in the issue" + } + }, + "description": "Details of the created issue (only present if success=true)" + }, + "skipped": { + "type": "boolean", + "description": "True if no issue was created because no inconsistencies were found" + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["authentication_failed", "rate_limit_exceeded", "permission_denied", "network_error", "api_error", "unknown_error"], + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "retryable": { + "type": "boolean", + "description": "Whether the operation can be retried" + } + }, + "description": "Error details (only present if success=false)" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Operation timestamp" + } + } +} diff --git a/.wave/contracts/doc-scan-results.schema.json b/.wave/contracts/doc-scan-results.schema.json new file mode 100644 index 0000000..a0f4cee --- /dev/null +++ b/.wave/contracts/doc-scan-results.schema.json @@ -0,0 +1,112 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Documentation Scan Results", + "description": "Output from the scan-changes step: changed files and current documentation state snapshot", + "type": "object", + "required": ["scan_scope", "changed_files", "documentation_snapshot", "timestamp"], + "properties": { + "scan_scope": { + "type": "object", + "required": ["mode", "range"], + "properties": { + "mode": { + "type": "string", + "enum": ["diff", "full"], + "description": "Whether this was a diff-based or full scan" + }, + "range": { + "type": "string", + "description": "Git range used for diff (e.g. 'main...HEAD') or 'full' for full scan" + }, + "base_ref": { + "type": "string", + "description": "Base reference for comparison" + } + } + }, + "changed_files": { + "type": "object", + "required": ["total_count", "categories"], + "properties": { + "total_count": { + "type": "integer", + "minimum": 0, + "description": "Total number of changed files" + }, + "categories": { + "type": "object", + "properties": { + "go_code": { + "type": "array", + "items": { "type": "string" }, + "description": "Changed Go source files" + }, + "cli_commands": { + "type": "array", + "items": { "type": "string" }, + "description": "Changed CLI command files (cmd/wave/commands/)" + }, + "pipelines": { + "type": "array", + "items": { "type": "string" }, + "description": "Changed pipeline definitions" + }, + "personas": { + "type": "array", + "items": { "type": "string" }, + "description": "Changed persona definitions" + }, + "contracts": { + "type": "array", + "items": { "type": "string" }, + "description": "Changed contract schemas" + }, + "documentation": { + "type": "array", + "items": { "type": "string" }, + "description": "Changed documentation files (docs/, README.md, CLAUDE.md, etc.)" + }, + "other": { + "type": "array", + "items": { "type": "string" }, + "description": "Other changed files" + } + } + } + } + }, + "documentation_snapshot": { + "type": "object", + "required": ["files"], + "properties": { + "files": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "exists"], + "properties": { + "path": { + "type": "string", + "description": "Path to the documentation file" + }, + "exists": { + "type": "boolean", + "description": "Whether the file exists" + }, + "summary": { + "type": "string", + "description": "Brief summary of the file's content and key sections" + } + } + }, + "description": "Key documentation files and their current state" + } + } + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the scan was performed" + } + } +} diff --git a/.wave/contracts/docs-phase.schema.json b/.wave/contracts/docs-phase.schema.json new file mode 100644 index 0000000..392efc7 --- /dev/null +++ b/.wave/contracts/docs-phase.schema.json @@ -0,0 +1,112 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.recinq.com/wave/docs-phase.schema.json", + "title": "Wave Docs Phase Output Contract", + "description": "Validates docs phase outputs for prototype-driven development pipeline", + "type": "object", + "properties": { + "phase": { + "type": "string", + "const": "docs", + "description": "Phase identifier" + }, + "artifacts": { + "type": "object", + "properties": { + "feature_docs": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*feature-docs\\.md$", + "description": "Path to feature documentation file" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Feature documentation file must exist" + }, + "content_type": { + "type": "string", + "const": "markdown", + "description": "Documentation content format" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + }, + "stakeholder_summary": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*stakeholder-summary\\.md$", + "description": "Path to stakeholder summary file" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Stakeholder summary file must exist" + }, + "content_type": { + "type": "string", + "const": "markdown", + "description": "Summary content format" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + } + }, + "required": ["feature_docs", "stakeholder_summary"], + "additionalProperties": true + }, + "validation": { + "type": "object", + "properties": { + "coverage_percentage": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Specification coverage in documentation" + }, + "readability_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Documentation readability rating" + }, + "documentation_quality": { + "type": "string", + "enum": ["poor", "fair", "good", "excellent"], + "description": "Overall documentation quality assessment" + } + }, + "required": ["documentation_quality"], + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When docs phase was completed" + }, + "duration_seconds": { + "type": "number", + "minimum": 0, + "description": "Time taken for docs phase" + }, + "source_spec_path": { + "type": "string", + "description": "Path to source specification file" + } + }, + "required": ["timestamp", "source_spec_path"], + "additionalProperties": false + } + }, + "required": ["phase", "artifacts", "validation", "metadata"], + "additionalProperties": false +} diff --git a/.wave/contracts/dummy-phase.schema.json b/.wave/contracts/dummy-phase.schema.json new file mode 100644 index 0000000..cf209e7 --- /dev/null +++ b/.wave/contracts/dummy-phase.schema.json @@ -0,0 +1,110 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.recinq.com/wave/dummy-phase.schema.json", + "title": "Wave Dummy Phase Output Contract", + "description": "Validates dummy phase outputs for prototype-driven development pipeline", + "type": "object", + "properties": { + "phase": { + "type": "string", + "const": "dummy", + "description": "Phase identifier" + }, + "artifacts": { + "type": "object", + "properties": { + "prototype": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*prototype/?$", + "description": "Path to prototype directory" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Prototype directory must exist" + }, + "content_type": { + "type": "string", + "const": "code", + "description": "Prototype content type" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + }, + "interface_definitions": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*interfaces\\.md$", + "description": "Path to interface definitions file" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Interface definitions file must exist" + }, + "content_type": { + "type": "string", + "const": "markdown", + "description": "Interface definitions content format" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + } + }, + "required": ["prototype", "interface_definitions"], + "additionalProperties": true + }, + "validation": { + "type": "object", + "properties": { + "runnable": { + "type": "boolean", + "description": "Whether the prototype can be executed" + }, + "interface_completeness": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Percentage of interfaces stubbed" + }, + "prototype_quality": { + "type": "string", + "enum": ["poor", "fair", "good", "excellent"], + "description": "Overall prototype quality assessment" + } + }, + "required": ["runnable", "prototype_quality"], + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When dummy phase was completed" + }, + "duration_seconds": { + "type": "number", + "minimum": 0, + "description": "Time taken for dummy phase" + }, + "source_docs_path": { + "type": "string", + "description": "Path to source documentation file" + } + }, + "required": ["timestamp", "source_docs_path"], + "additionalProperties": false + } + }, + "required": ["phase", "artifacts", "validation", "metadata"], + "additionalProperties": false +} diff --git a/.wave/contracts/enhancement-plan.schema.json b/.wave/contracts/enhancement-plan.schema.json new file mode 100644 index 0000000..20dfec0 --- /dev/null +++ b/.wave/contracts/enhancement-plan.schema.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Issue Enhancement Plan", + "description": "Schema for GitHub issue enhancement recommendations", + "type": "object", + "required": ["repository", "issues_to_enhance"], + "properties": { + "repository": { + "type": "string", + "description": "Repository in owner/repo format" + }, + "issues_to_enhance": { + "type": "array", + "items": { + "type": "object", + "required": ["issue_number", "enhancements"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "current_title": { + "type": "string" + }, + "suggested_title": { + "type": "string" + }, + "current_body": { + "type": "string" + }, + "body_template": { + "type": ["string", "null"], + "description": "Enhanced body template preserving original content" + }, + "suggested_labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "enhancements": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "description": "List of specific enhancements to apply" + }, + "rationale": { + "type": "string", + "description": "Why these enhancements are recommended" + }, + "priority": { + "type": "string", + "enum": ["high", "medium", "low"] + } + } + } + }, + "total_to_enhance": { + "type": "integer", + "minimum": 0 + }, + "enhancement_strategy": { + "type": "string", + "description": "Overall strategy for enhancements" + } + } +} diff --git a/.wave/contracts/enhancement-results.schema.json b/.wave/contracts/enhancement-results.schema.json new file mode 100644 index 0000000..1806af6 --- /dev/null +++ b/.wave/contracts/enhancement-results.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Issue Enhancement Results", + "description": "Schema for GitHub issue enhancement execution results", + "type": "object", + "required": ["enhanced_issues", "total_attempted", "total_successful"], + "properties": { + "enhanced_issues": { + "type": "array", + "items": { + "type": "object", + "required": ["issue_number", "success", "changes_made", "url"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "success": { + "type": "boolean" + }, + "changes_made": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of changes successfully applied" + }, + "title_updated": { + "type": "boolean" + }, + "body_updated": { + "type": "boolean" + }, + "labels_added": { + "type": "array", + "items": { + "type": "string" + } + }, + "comment_added": { + "type": "boolean" + }, + "error": { + "type": "string", + "description": "Error message if enhancement failed" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "total_attempted": { + "type": "integer", + "minimum": 0 + }, + "total_successful": { + "type": "integer", + "minimum": 0 + }, + "total_failed": { + "type": "integer", + "minimum": 0 + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } +} diff --git a/.wave/contracts/epic-assessment.schema.json b/.wave/contracts/epic-assessment.schema.json new file mode 100644 index 0000000..6221762 --- /dev/null +++ b/.wave/contracts/epic-assessment.schema.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epic Issue Assessment", + "description": "Schema for epic/umbrella issue analysis results", + "type": "object", + "required": ["is_epic", "issue", "analysis"], + "properties": { + "is_epic": { + "type": "boolean", + "const": true, + "description": "Must be true for the pipeline to proceed. Set to false if the issue is not an epic or umbrella issue." + }, + "issue": { + "type": "object", + "required": ["number", "title", "body", "repository", "url"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "body": { + "type": "string" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$" + }, + "url": { + "type": "string", + "format": "uri" + }, + "labels": { + "type": "array", + "items": { "type": "string" } + }, + "existing_issues": { + "type": "array", + "items": { + "type": "object", + "properties": { + "number": { "type": "integer" }, + "title": { "type": "string" }, + "labels": { "type": "array", "items": { "type": "string" } } + } + }, + "description": "Existing issues in the repo to avoid duplication" + } + } + }, + "analysis": { + "type": "object", + "required": ["scope_summary", "proposed_issues_count", "complexity"], + "properties": { + "scope_summary": { + "type": "string", + "minLength": 1, + "description": "Brief summary of what the epic covers" + }, + "proposed_issues_count": { + "type": "integer", + "minimum": 1, + "maximum": 15, + "description": "Number of sub-issues to create" + }, + "complexity": { + "type": "string", + "enum": ["small", "medium", "large", "epic"], + "description": "Overall complexity of the work" + }, + "key_themes": { + "type": "array", + "items": { "type": "string" }, + "description": "Major themes or categories of work identified" + }, + "risks": { + "type": "array", + "items": { "type": "string" }, + "description": "Identified risks or concerns" + } + } + } + } +} diff --git a/.wave/contracts/epic-children.schema.json b/.wave/contracts/epic-children.schema.json new file mode 100644 index 0000000..d869a18 --- /dev/null +++ b/.wave/contracts/epic-children.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epic Children", + "description": "Schema for the list of open child issue URLs belonging to an epic", + "type": "object", + "required": ["parent_url", "repo", "child_urls"], + "properties": { + "parent_url": { + "type": "string", + "format": "uri", + "description": "URL of the parent epic issue" + }, + "repo": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository in owner/repo format" + }, + "child_urls": { + "type": "array", + "items": { + "type": "string", + "format": "uri" + }, + "minItems": 1, + "description": "URLs of open child issues to implement" + } + } +} diff --git a/.wave/contracts/epic-report.schema.json b/.wave/contracts/epic-report.schema.json new file mode 100644 index 0000000..846b383 --- /dev/null +++ b/.wave/contracts/epic-report.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epic Implementation Report", + "description": "Output from the report step of ops-implement-epic: implementation status for each subissue", + "type": "object", + "required": ["parent_issue", "results", "summary"], + "properties": { + "parent_issue": { + "type": "object", + "required": ["owner", "repo", "number", "url"], + "properties": { + "owner": { "type": "string" }, + "repo": { "type": "string" }, + "number": { "type": "integer", "minimum": 1 }, + "url": { "type": "string", "format": "uri" } + }, + "additionalProperties": false + }, + "results": { + "type": "array", + "items": { + "type": "object", + "required": ["number", "title", "status"], + "properties": { + "number": { "type": "integer", "minimum": 1 }, + "title": { "type": "string" }, + "status": { "type": "string", "enum": ["implemented", "failed", "skipped"] }, + "pr_url": { "type": "string" }, + "pr_number": { "type": "integer" } + }, + "additionalProperties": false + } + }, + "summary": { + "type": "object", + "required": ["total_subissues", "implemented", "failed", "skipped"], + "properties": { + "total_subissues": { "type": "integer", "minimum": 0 }, + "implemented": { "type": "integer", "minimum": 0 }, + "failed": { "type": "integer", "minimum": 0 }, + "skipped": { "type": "integer", "minimum": 0 }, + "comment_posted": { "type": "boolean" }, + "comment_url": { "type": "string" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/.wave/contracts/epic-scope-plan.schema.json b/.wave/contracts/epic-scope-plan.schema.json new file mode 100644 index 0000000..2b23348 --- /dev/null +++ b/.wave/contracts/epic-scope-plan.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epic Scope Plan", + "description": "Output from the fetch-scope step of ops-implement-epic: parsed subissues with dependency graph", + "type": "object", + "required": ["parent_issue", "subissues", "total_subissues", "open_subissues"], + "properties": { + "parent_issue": { + "type": "object", + "required": ["owner", "repo", "number", "title", "url"], + "properties": { + "owner": { "type": "string" }, + "repo": { "type": "string" }, + "number": { "type": "integer", "minimum": 1 }, + "title": { "type": "string", "minLength": 1 }, + "url": { "type": "string", "format": "uri" } + }, + "additionalProperties": false + }, + "subissues": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["number", "repository", "title", "url", "state", "dependencies"], + "properties": { + "number": { "type": "integer", "minimum": 1 }, + "repository": { "type": "string", "pattern": "^[^/]+/[^/]+$" }, + "title": { "type": "string", "minLength": 1 }, + "url": { "type": "string", "format": "uri" }, + "state": { "type": "string", "enum": ["OPEN", "CLOSED"] }, + "complexity": { "type": "string", "enum": ["S", "M", "L", "XL"] }, + "dependencies": { + "type": "array", + "items": { "type": "integer", "minimum": 1 } + } + }, + "additionalProperties": false + } + }, + "total_subissues": { "type": "integer", "minimum": 1 }, + "open_subissues": { "type": "integer", "minimum": 0 }, + "dependency_tiers": { + "type": "array", + "items": { + "type": "array", + "items": { "type": "integer", "minimum": 1 } + } + } + }, + "additionalProperties": false +} diff --git a/.wave/contracts/explain-analysis.schema.json b/.wave/contracts/explain-analysis.schema.json new file mode 100644 index 0000000..37c3dd9 --- /dev/null +++ b/.wave/contracts/explain-analysis.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Code Architecture Analysis", + "description": "Architecture and design analysis of explored code", + "type": "object", + "required": ["topic", "patterns", "timestamp"], + "properties": { + "topic": { "type": "string", "minLength": 3 }, + "patterns": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "where", "why"], + "properties": { + "name": { "type": "string" }, + "where": { "type": "string" }, + "why": { "type": "string" }, + "quality": { "type": "string", "enum": ["well-implemented", "adequate", "needs-improvement"] } + } + } + }, + "data_flow": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "stages": { "type": "array", "items": { "type": "string" } }, + "key_transformations": { "type": "array", "items": { "type": "string" } } + } + } + }, + "error_handling": { + "type": "object", + "properties": { + "strategy": { "type": "string" }, + "patterns": { "type": "array", "items": { "type": "string" } }, + "gaps": { "type": "array", "items": { "type": "string" } } + } + }, + "concurrency": { + "type": "object", + "properties": { + "model": { "type": "string" }, + "primitives": { "type": "array", "items": { "type": "string" } }, + "risks": { "type": "array", "items": { "type": "string" } } + } + }, + "extension_points": { + "type": "array", + "items": { + "type": "object", + "required": ["interface", "file"], + "properties": { + "interface": { "type": "string" }, + "file": { "type": "string" }, + "purpose": { "type": "string" } + } + } + }, + "design_decisions": { + "type": "array", + "items": { + "type": "object", + "required": ["decision"], + "properties": { + "decision": { "type": "string" }, + "rationale": { "type": "string" }, + "trade_offs": { "type": "string" } + } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/explain-exploration.schema.json b/.wave/contracts/explain-exploration.schema.json new file mode 100644 index 0000000..20a98ae --- /dev/null +++ b/.wave/contracts/explain-exploration.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Explain Exploration Results", + "description": "Structured exploration of a codebase topic for explanation", + "type": "object", + "required": ["topic", "entry_points", "key_types", "timestamp"], + "properties": { + "topic": { "type": "string", "minLength": 1 }, + "entry_points": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["file", "symbol", "role"], + "properties": { + "file": { "type": "string", "minLength": 1 }, + "symbol": { "type": "string", "minLength": 1 }, + "role": { "type": "string", "minLength": 1 }, + "line": { "type": "integer", "minimum": 1 } + } + } + }, + "key_types": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "file", "purpose"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "file": { "type": "string", "minLength": 1 }, + "purpose": { "type": "string", "minLength": 1 }, + "methods": { "type": "array", "items": { "type": "string" } }, + "relationships": { "type": "array", "items": { "type": "string" } } + } + } + }, + "call_flows": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "steps"], + "properties": { + "name": { "type": "string" }, + "steps": { "type": "array", "items": { "type": "string" } }, + "description": { "type": "string" } + } + } + }, + "dependencies": { + "type": "object", + "properties": { + "depends_on": { "type": "array", "items": { "type": "string" } }, + "depended_by": { "type": "array", "items": { "type": "string" } }, + "external": { "type": "array", "items": { "type": "string" } } + } + }, + "test_files": { "type": "array", "items": { "type": "string" } }, + "config_points": { "type": "array", "items": { "type": "string" } }, + "file_count": { "type": "integer", "minimum": 0 }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/feature-exploration.schema.json b/.wave/contracts/feature-exploration.schema.json new file mode 100644 index 0000000..036b7dd --- /dev/null +++ b/.wave/contracts/feature-exploration.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Feature Exploration", + "description": "Codebase exploration for feature implementation", + "type": "object", + "required": ["feature", "scope", "related_files", "timestamp"], + "properties": { + "feature": { "type": "string", "minLength": 1 }, + "scope": { "type": "string", "enum": ["small", "medium", "large"] }, + "related_files": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["path", "relevance", "reason"], + "properties": { + "path": { "type": "string", "minLength": 1 }, + "relevance": { "type": "string", "enum": ["primary", "secondary"] }, + "reason": { "type": "string", "minLength": 1 }, + "key_symbols": { "type": "array", "items": { "type": "string" } } + } + } + }, + "patterns_to_follow": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "example_file": { "type": "string" }, + "relevance": { "type": "string", "enum": ["must_follow", "should_follow"] } + } + } + }, + "affected_modules": { "type": "array", "items": { "type": "string" } }, + "test_files": { "type": "array", "items": { "type": "string" } }, + "risks": { + "type": "array", + "items": { + "type": "object", + "required": ["description", "severity"], + "properties": { + "description": { "type": "string" }, + "severity": { "type": "string", "enum": ["high", "medium", "low"] }, + "mitigation": { "type": "string" } + } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/feature-plan.schema.json b/.wave/contracts/feature-plan.schema.json new file mode 100644 index 0000000..d298909 --- /dev/null +++ b/.wave/contracts/feature-plan.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Feature Implementation Plan", + "description": "Ordered implementation plan for a feature", + "type": "object", + "required": ["feature", "steps", "total_complexity", "timestamp"], + "properties": { + "feature": { "type": "string", "minLength": 5 }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "title", "description"], + "properties": { + "id": { "type": "string", "pattern": "^S\\d{2}$" }, + "title": { "type": "string" }, + "description": { "type": "string" }, + "files_to_modify": { "type": "array", "items": { "type": "string" } }, + "files_to_create": { "type": "array", "items": { "type": "string" } }, + "tests_to_write": { "type": "array", "items": { "type": "string" } }, + "acceptance_criteria": { "type": "array", "items": { "type": "string" } }, + "complexity": { "type": "string", "enum": ["S", "M", "L"] }, + "dependencies": { "type": "array", "items": { "type": "string" } } + } + } + }, + "total_complexity": { "type": "string", "enum": ["S", "M", "L", "XL"] }, + "branch_name_suggestion": { "type": "string" }, + "commit_message_suggestion": { "type": "string" }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/findings.schema.json b/.wave/contracts/findings.schema.json new file mode 100644 index 0000000..9a7540c --- /dev/null +++ b/.wave/contracts/findings.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Investigation Findings", + "type": "object", + "required": [ + "root_cause", + "affected_files", + "fix_approach" + ], + "properties": { + "root_cause": { + "type": "string", + "description": "Description of the root cause" + }, + "affected_files": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of affected file paths" + }, + "recent_commits": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of relevant commit hashes" + }, + "blast_radius": { + "type": "string", + "description": "Assessment of what else could be affected" + }, + "fix_approach": { + "type": "string", + "description": "Recommended approach to fix the issue" + } + } +} \ No newline at end of file diff --git a/.wave/contracts/gap-analysis.schema.json b/.wave/contracts/gap-analysis.schema.json new file mode 100644 index 0000000..5ec7fec --- /dev/null +++ b/.wave/contracts/gap-analysis.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Gap Analysis", + "description": "Ontology vs. code comparison identifying implementation gaps", + "type": "object", + "required": ["ontology_version", "gaps", "coverage"], + "properties": { + "ontology_version": { + "type": "string" + }, + "gaps": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type", "entity", "description", "severity"], + "properties": { + "id": { "type": "string", "pattern": "^GAP-[0-9]+$" }, + "type": { "type": "string", "enum": ["missing_entity", "missing_relationship", "missing_invariant", "partial_implementation", "stale_code", "undocumented_behavior"] }, + "entity": { "type": "string" }, + "description": { "type": "string" }, + "severity": { "type": "string", "enum": ["low", "medium", "high", "critical"] }, + "code_locations": { "type": "array", "items": { "type": "string" } }, + "recommendation": { "type": "string" } + } + } + }, + "coverage": { + "type": "object", + "required": ["entities_covered", "entities_total", "percentage"], + "properties": { + "entities_covered": { "type": "integer", "minimum": 0 }, + "entities_total": { "type": "integer", "minimum": 0 }, + "percentage": { "type": "number", "minimum": 0, "maximum": 100 } + } + } + } +} diff --git a/.wave/contracts/gate-result.schema.json b/.wave/contracts/gate-result.schema.json new file mode 100644 index 0000000..167688e --- /dev/null +++ b/.wave/contracts/gate-result.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Gate Result", + "description": "Gate step resolution metadata", + "type": "object", + "required": ["gate_type", "status", "resolved_at"], + "properties": { + "gate_type": { + "type": "string", + "enum": ["approval", "pr_merge", "ci_pass", "timer"] + }, + "status": { + "type": "string", + "enum": ["approved", "rejected", "timed_out", "merged", "checks_passed", "checks_failed"] + }, + "resolved_at": { + "type": "string", + "format": "date-time" + }, + "resolved_by": { + "type": "string", + "description": "User or system that resolved the gate" + }, + "wait_duration_seconds": { + "type": "number", + "minimum": 0 + }, + "metadata": { + "type": "object", + "description": "Gate-type-specific metadata (PR number, check suite ID, etc.)" + } + } +} diff --git a/.wave/contracts/gh-pr-comment-result.schema.json b/.wave/contracts/gh-pr-comment-result.schema.json new file mode 100644 index 0000000..3cb9d8a --- /dev/null +++ b/.wave/contracts/gh-pr-comment-result.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub PR Comment Result", + "description": "Result of posting a code review comment on a GitHub pull request", + "type": "object", + "required": ["comment_url", "pr_number"], + "properties": { + "comment_url": { + "type": "string", + "format": "uri", + "description": "URL to the posted review comment" + }, + "pr_number": { + "type": "integer", + "minimum": 1, + "description": "PR number that was reviewed" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name (owner/repo)" + }, + "summary": { + "type": "string", + "description": "Brief summary of the review" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/github-enhancement-plan.schema.json b/.wave/contracts/github-enhancement-plan.schema.json new file mode 100644 index 0000000..f6ea905 --- /dev/null +++ b/.wave/contracts/github-enhancement-plan.schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Issue Enhancement Plan", + "description": "Schema for GitHub issue enhancement recommendations", + "type": "object", + "required": ["issues_to_enhance"], + "properties": { + "issues_to_enhance": { + "type": "array", + "items": { + "type": "object", + "required": ["issue_number", "enhancements"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "current_title": { + "type": "string" + }, + "suggested_title": { + "type": "string" + }, + "current_body": { + "type": "string" + }, + "body_template": { + "type": ["string", "null"], + "description": "Enhanced body template preserving original content" + }, + "suggested_labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "enhancements": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "description": "List of specific enhancements to apply" + }, + "rationale": { + "type": "string", + "description": "Why these enhancements are recommended" + }, + "priority": { + "type": "string", + "enum": ["high", "medium", "low"] + } + } + } + }, + "total_to_enhance": { + "type": "integer", + "minimum": 0 + }, + "enhancement_strategy": { + "type": "string", + "description": "Overall strategy for enhancements" + } + } +} diff --git a/.wave/contracts/github-enhancement-results.schema.json b/.wave/contracts/github-enhancement-results.schema.json new file mode 100644 index 0000000..1806af6 --- /dev/null +++ b/.wave/contracts/github-enhancement-results.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Issue Enhancement Results", + "description": "Schema for GitHub issue enhancement execution results", + "type": "object", + "required": ["enhanced_issues", "total_attempted", "total_successful"], + "properties": { + "enhanced_issues": { + "type": "array", + "items": { + "type": "object", + "required": ["issue_number", "success", "changes_made", "url"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "success": { + "type": "boolean" + }, + "changes_made": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of changes successfully applied" + }, + "title_updated": { + "type": "boolean" + }, + "body_updated": { + "type": "boolean" + }, + "labels_added": { + "type": "array", + "items": { + "type": "string" + } + }, + "comment_added": { + "type": "boolean" + }, + "error": { + "type": "string", + "description": "Error message if enhancement failed" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "total_attempted": { + "type": "integer", + "minimum": 0 + }, + "total_successful": { + "type": "integer", + "minimum": 0 + }, + "total_failed": { + "type": "integer", + "minimum": 0 + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } +} diff --git a/.wave/contracts/github-issue-analysis.schema.json b/.wave/contracts/github-issue-analysis.schema.json new file mode 100644 index 0000000..d0cbcb3 --- /dev/null +++ b/.wave/contracts/github-issue-analysis.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Issue Analysis", + "description": "Schema for GitHub issue quality analysis results", + "type": "object", + "required": ["repository", "total_issues", "poor_quality_issues"], + "properties": { + "repository": { + "type": "object", + "required": ["owner", "name"], + "properties": { + "owner": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + } + }, + "total_issues": { + "type": "integer", + "minimum": 0 + }, + "analyzed_count": { + "type": "integer", + "minimum": 0 + }, + "poor_quality_issues": { + "type": "array", + "items": { + "type": "object", + "required": ["number", "title", "quality_score", "problems"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "title": { + "type": "string" + }, + "body": { + "type": "string" + }, + "quality_score": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "problems": { + "type": "array", + "items": { + "type": "string" + } + }, + "recommendations": { + "type": "array", + "items": { + "type": "string" + } + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "quality_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } +} diff --git a/.wave/contracts/github-verification-report.schema.json b/.wave/contracts/github-verification-report.schema.json new file mode 100644 index 0000000..9a470ec --- /dev/null +++ b/.wave/contracts/github-verification-report.schema.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Enhancement Verification Report", + "description": "Schema for verifying GitHub issue enhancements were applied", + "type": "object", + "required": ["total_enhanced", "successful_enhancements", "failed_enhancements"], + "properties": { + "total_enhanced": { + "type": "integer", + "minimum": 0 + }, + "successful_enhancements": { + "type": "array", + "items": { + "type": "object", + "required": ["issue_number", "verified_changes"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "verified_changes": { + "type": "array", + "items": { + "type": "string" + } + }, + "quality_score_before": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "quality_score_after": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "failed_enhancements": { + "type": "array", + "items": { + "type": "object", + "required": ["issue_number", "problems"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "problems": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "quality_improvement": { + "type": "object", + "properties": { + "average_score_before": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "average_score_after": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "improvement_percentage": { + "type": "number" + } + } + }, + "summary": { + "type": "string", + "description": "Human-readable summary of verification results" + } + } +} diff --git a/.wave/contracts/hello-world-result.schema.json b/.wave/contracts/hello-world-result.schema.json new file mode 100644 index 0000000..9678072 --- /dev/null +++ b/.wave/contracts/hello-world-result.schema.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Hello World Result", + "description": "Verification result from the hello-world verify step", + "type": "object", + "required": ["success", "message"], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the hello-world pipeline completed successfully" + }, + "message": { + "type": "string", + "description": "Human-readable verification result" + }, + "checks": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Optional list of individual verification checks performed" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/implement-phase.schema.json b/.wave/contracts/implement-phase.schema.json new file mode 100644 index 0000000..afbcba7 --- /dev/null +++ b/.wave/contracts/implement-phase.schema.json @@ -0,0 +1,138 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.recinq.com/wave/implement-phase.schema.json", + "title": "Wave Implement Phase Output Contract", + "description": "Validates implementation phase outputs for prototype-driven development pipeline", + "type": "object", + "properties": { + "phase": { + "type": "string", + "const": "implement", + "description": "Phase identifier" + }, + "artifacts": { + "type": "object", + "properties": { + "implementation_plan": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*implementation-plan\\.md$", + "description": "Path to implementation plan file" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Implementation plan file must exist" + }, + "content_type": { + "type": "string", + "const": "markdown", + "description": "Implementation plan content format" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + }, + "progress_checklist": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*implementation-checklist\\.md$", + "description": "Path to progress checklist file" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Progress checklist file must exist" + }, + "content_type": { + "type": "string", + "const": "markdown", + "description": "Checklist content format" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + } + }, + "required": ["implementation_plan", "progress_checklist"], + "additionalProperties": true + }, + "validation": { + "type": "object", + "properties": { + "tests_executed": { + "type": "boolean", + "description": "Whether test suite was executed" + }, + "test_results": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "minimum": 0, + "description": "Total number of tests" + }, + "passed": { + "type": "integer", + "minimum": 0, + "description": "Number of tests that passed" + }, + "failed": { + "type": "integer", + "minimum": 0, + "description": "Number of tests that failed" + }, + "coverage_percent": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Test coverage percentage" + } + }, + "required": ["total", "passed", "failed"], + "additionalProperties": false + }, + "implementation_readiness": { + "type": "string", + "enum": ["ready", "partial", "needs_work"], + "description": "Assessment of implementation readiness" + } + }, + "required": ["tests_executed", "implementation_readiness"], + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When implementation phase was completed" + }, + "duration_seconds": { + "type": "number", + "minimum": 0, + "description": "Time taken for implementation phase" + }, + "previous_phases": { + "type": "array", + "items": { + "type": "string", + "enum": ["spec", "docs", "dummy"] + }, + "minItems": 3, + "maxItems": 3, + "description": "Previous phases that fed into this implementation" + } + }, + "required": ["timestamp", "previous_phases"], + "additionalProperties": false + } + }, + "required": ["phase", "artifacts", "validation", "metadata"], + "additionalProperties": false +} \ No newline at end of file diff --git a/.wave/contracts/improvement-assessment.schema.json b/.wave/contracts/improvement-assessment.schema.json new file mode 100644 index 0000000..f688646 --- /dev/null +++ b/.wave/contracts/improvement-assessment.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Improvement Assessment", + "description": "Structured assessment of code improvement opportunities", + "type": "object", + "required": ["target", "findings", "summary", "timestamp"], + "properties": { + "target": { "type": "string", "minLength": 1 }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "category", "title", "location", "description", "impact", "effort"], + "properties": { + "id": { "type": "string", "pattern": "^IMP-[0-9]{1,4}$" }, + "category": { + "type": "string", + "enum": ["error_handling", "performance", "testability", "robustness", "maintainability", "code_quality"] + }, + "title": { "type": "string", "minLength": 3 }, + "location": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 10 }, + "current_code": { "type": "string", "description": "Optional: code snippet illustrating the issue" }, + "suggested_fix": { "type": "string", "minLength": 5, "description": "Optional: recommended fix or improvement" }, + "impact": { "type": "string", "enum": ["high", "medium", "low"] }, + "effort": { "type": "string", "enum": ["trivial", "small", "medium", "large"] }, + "risk": { "type": "string", "enum": ["none", "low", "medium", "high"] } + } + } + }, + "summary": { + "type": "object", + "required": ["total_findings", "overall_quality"], + "properties": { + "total_findings": { "type": "integer", "minimum": 0 }, + "by_category": { "type": "object" }, + "quick_wins": { "type": "array", "items": { "type": "string" } }, + "high_impact": { "type": "array", "items": { "type": "string" } }, + "overall_quality": { "type": "string", "minLength": 10 } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/interface-design.schema.json b/.wave/contracts/interface-design.schema.json new file mode 100644 index 0000000..2849af4 --- /dev/null +++ b/.wave/contracts/interface-design.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Interface Design", + "description": "Go interface definitions derived from ontology entities", + "type": "object", + "required": ["package_name", "interfaces"], + "properties": { + "package_name": { + "type": "string" + }, + "interfaces": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "description", "methods"], + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "source_entity": { "type": "string" }, + "methods": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "signature"], + "properties": { + "name": { "type": "string" }, + "signature": { "type": "string" }, + "description": { "type": "string" } + } + } + } + } + } + } + } +} diff --git a/.wave/contracts/issue-assessment.schema.json b/.wave/contracts/issue-assessment.schema.json new file mode 100644 index 0000000..0517cb7 --- /dev/null +++ b/.wave/contracts/issue-assessment.schema.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Issue Implementation Assessment", + "type": "object", + "required": ["implementable", "issue", "assessment"], + "properties": { + "implementable": { + "type": "boolean", + "const": true, + "description": "Must be true for the pipeline to proceed. Set to false if the issue lacks sufficient detail." + }, + "issue": { + "type": "object", + "required": ["number", "title", "body", "repository", "url"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "body": { + "type": "string" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$" + }, + "url": { + "type": "string", + "format": "uri" + }, + "labels": { + "type": "array", + "items": { "type": "string" } + }, + "state": { + "type": "string" + }, + "author": { + "type": "string" + }, + "comments": { + "type": "array", + "items": { "type": "object" } + } + } + }, + "assessment": { + "type": "object", + "required": ["quality_score", "complexity", "skip_steps", "branch_name"], + "properties": { + "quality_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "How well-specified the issue is (0-100)" + }, + "complexity": { + "type": "string", + "enum": ["trivial", "simple", "medium", "complex"], + "description": "Estimated implementation complexity" + }, + "skip_steps": { + "type": "array", + "items": { + "type": "string", + "enum": ["specify", "clarify", "checklist", "analyze"] + }, + "description": "Speckit steps that can be skipped because the issue provides sufficient detail" + }, + "branch_name": { + "type": "string", + "minLength": 1, + "description": "Suggested branch name for the implementation" + }, + "missing_info": { + "type": "array", + "items": { "type": "string" }, + "description": "Information that would improve the issue but isn't blocking" + }, + "summary": { + "type": "string", + "description": "Brief summary of the assessment" + } + } + } + } +} diff --git a/.wave/contracts/issue-content.schema.json b/.wave/contracts/issue-content.schema.json new file mode 100644 index 0000000..9637552 --- /dev/null +++ b/.wave/contracts/issue-content.schema.json @@ -0,0 +1,96 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GitHub Issue Content", + "description": "Parsed content from a GitHub issue for research pipeline processing", + "type": "object", + "required": ["issue_number", "title", "body", "author", "url", "repository"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1, + "description": "GitHub issue number" + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "Issue title" + }, + "body": { + "type": "string", + "description": "Issue body/description text" + }, + "author": { + "type": "object", + "required": ["login"], + "properties": { + "login": { + "type": "string", + "minLength": 1, + "description": "GitHub username of issue author" + } + } + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "Label name" + }, + "color": { + "type": "string", + "description": "Label color hex code" + }, + "description": { + "type": "string", + "description": "Label description" + } + } + }, + "description": "Labels applied to the issue" + }, + "url": { + "type": "string", + "format": "uri", + "description": "HTML URL to the GitHub issue" + }, + "repository": { + "type": "object", + "required": ["owner", "name", "full_name"], + "properties": { + "owner": { + "type": "string", + "description": "Repository owner" + }, + "name": { + "type": "string", + "description": "Repository name" + }, + "full_name": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Full repository name (owner/repo)" + } + } + }, + "state": { + "type": "string", + "enum": ["open", "closed"], + "description": "Issue state" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Issue creation timestamp" + }, + "comments_count": { + "type": "integer", + "minimum": 0, + "description": "Number of comments on the issue" + } + } +} diff --git a/.wave/contracts/issue-impl-plan.schema.json b/.wave/contracts/issue-impl-plan.schema.json new file mode 100644 index 0000000..6d525e1 --- /dev/null +++ b/.wave/contracts/issue-impl-plan.schema.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Issue Implementation Plan", + "type": "object", + "required": ["issue_number", "branch_name", "feature_dir", "tasks"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "branch_name": { + "type": "string", + "minLength": 1 + }, + "feature_dir": { + "type": "string", + "description": "Path to the feature directory under specs/" + }, + "spec_file": { + "type": "string", + "description": "Path to the spec.md file" + }, + "plan_file": { + "type": "string", + "description": "Path to the plan.md file" + }, + "tasks_file": { + "type": "string", + "description": "Path to the tasks.md file" + }, + "tasks": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "title", "description"], + "properties": { + "id": { + "type": "string", + "description": "Task identifier" + }, + "title": { + "type": "string", + "description": "Brief task title" + }, + "description": { + "type": "string", + "description": "Detailed task description" + }, + "file_changes": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "action"], + "properties": { + "path": { "type": "string" }, + "action": { + "type": "string", + "enum": ["create", "modify", "delete"] + } + } + } + } + } + } + }, + "summary": { + "type": "string", + "description": "Brief summary of the implementation plan" + } + } +} diff --git a/.wave/contracts/issue-update-context.schema.json b/.wave/contracts/issue-update-context.schema.json new file mode 100644 index 0000000..ad76624 --- /dev/null +++ b/.wave/contracts/issue-update-context.schema.json @@ -0,0 +1,116 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Issue Update Context", + "description": "Schema for gathered context about a GitHub issue and recent codebase changes", + "type": "object", + "required": ["repository", "issue", "changes_since_creation", "referenced_files"], + "properties": { + "repository": { + "type": "object", + "required": ["owner", "name"], + "properties": { + "owner": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + } + }, + "issue": { + "type": "object", + "required": ["number", "title", "body", "url", "created_at"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "body": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "url": { + "type": "string", + "format": "uri" + }, + "created_at": { + "type": "string" + }, + "comments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "author": { + "type": "string" + }, + "body": { + "type": "string" + }, + "created_at": { + "type": "string" + } + } + } + } + } + }, + "changes_since_creation": { + "type": "object", + "required": ["commits", "commit_count"], + "properties": { + "commits": { + "type": "array", + "items": { + "type": "string" + } + }, + "commit_count": { + "type": "integer", + "minimum": 0 + }, + "releases": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "referenced_files": { + "type": "object", + "required": ["found", "missing"], + "properties": { + "found": { + "type": "array", + "items": { + "type": "string" + } + }, + "missing": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "criticism": { + "type": "string" + }, + "project_context": { + "type": "string" + } + } +} diff --git a/.wave/contracts/issue-update-draft.schema.json b/.wave/contracts/issue-update-draft.schema.json new file mode 100644 index 0000000..5610a4e --- /dev/null +++ b/.wave/contracts/issue-update-draft.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Issue Update Draft", + "description": "Schema for a drafted update to a GitHub issue", + "type": "object", + "required": ["issue_number", "original_title", "updated_title", "title_changed", "original_body", "updated_body", "staleness_assessment", "changes_made"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "original_title": { + "type": "string", + "minLength": 1 + }, + "updated_title": { + "type": "string", + "minLength": 1 + }, + "title_changed": { + "type": "boolean" + }, + "original_body": { + "type": "string" + }, + "updated_body": { + "type": "string", + "minLength": 1 + }, + "staleness_assessment": { + "type": "object", + "required": ["overall_staleness", "sections"], + "properties": { + "overall_staleness": { + "type": "string", + "enum": ["low", "medium", "high", "critical"] + }, + "sections": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "status", "reason"], + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": ["STILL_VALID", "OUTDATED", "INCOMPLETE", "WRONG"] + }, + "reason": { + "type": "string" + } + } + } + } + } + }, + "changes_made": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "criticism_addressed": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/.wave/contracts/issue-update-result.schema.json b/.wave/contracts/issue-update-result.schema.json new file mode 100644 index 0000000..334cb80 --- /dev/null +++ b/.wave/contracts/issue-update-result.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Issue Update Result", + "description": "Schema for the result of applying an issue update", + "type": "object", + "required": ["issue_number", "success", "changes_applied", "verification"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "url": { + "type": "string", + "format": "uri" + }, + "success": { + "type": "boolean" + }, + "title_updated": { + "type": "boolean" + }, + "body_updated": { + "type": "boolean" + }, + "changes_applied": { + "type": "array", + "items": { + "type": "string" + } + }, + "verification": { + "type": "object", + "required": ["title_matches", "body_matches"], + "properties": { + "title_matches": { + "type": "boolean" + }, + "body_matches": { + "type": "boolean" + }, + "discrepancies": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "staleness_assessment": { + "type": "string", + "enum": ["low", "medium", "high", "critical"] + }, + "error": { + "type": ["string", "null"] + } + } +} diff --git a/.wave/contracts/iteration-state.schema.json b/.wave/contracts/iteration-state.schema.json new file mode 100644 index 0000000..e00e4ec --- /dev/null +++ b/.wave/contracts/iteration-state.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Iteration State", + "description": "Per-iteration status for resumability tracking", + "type": "object", + "required": ["step_id", "total_items", "items"], + "properties": { + "step_id": { + "type": "string" + }, + "total_items": { + "type": "integer", + "minimum": 0 + }, + "completed_items": { + "type": "integer", + "minimum": 0 + }, + "items": { + "type": "array", + "items": { + "type": "object", + "required": ["index", "status"], + "properties": { + "index": { "type": "integer", "minimum": 0 }, + "status": { "type": "string", "enum": ["pending", "running", "completed", "failed", "skipped"] }, + "input": { "type": "string" }, + "error": { "type": "string" }, + "pipeline_run_id": { "type": "string" }, + "started_at": { "type": "string", "format": "date-time" }, + "completed_at": { "type": "string", "format": "date-time" } + } + } + } + } +} diff --git a/.wave/contracts/ontology-evolution.schema.json b/.wave/contracts/ontology-evolution.schema.json new file mode 100644 index 0000000..bfca1da --- /dev/null +++ b/.wave/contracts/ontology-evolution.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ontology Evolution", + "description": "Categorized ontology changes with effort and risk assessment", + "type": "object", + "required": ["base_version", "changes", "summary"], + "properties": { + "base_version": { + "type": "string", + "description": "Reference to the base ontology being evolved" + }, + "changes": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "category", "description", "effort", "risk"], + "properties": { + "id": { "type": "string", "pattern": "^EVO-[0-9]+$" }, + "category": { "type": "string", "enum": ["add_entity", "modify_entity", "remove_entity", "add_relationship", "modify_relationship", "remove_relationship", "add_invariant", "modify_boundary"] }, + "description": { "type": "string" }, + "effort": { "type": "string", "enum": ["trivial", "small", "medium", "large", "epic"] }, + "risk": { "type": "string", "enum": ["low", "medium", "high", "critical"] }, + "rationale": { "type": "string" }, + "affected_entities": { "type": "array", "items": { "type": "string" } } + } + } + }, + "summary": { + "type": "object", + "required": ["total_changes", "breaking_changes"], + "properties": { + "total_changes": { "type": "integer", "minimum": 0 }, + "breaking_changes": { "type": "integer", "minimum": 0 }, + "estimated_effort": { "type": "string" } + } + } + } +} diff --git a/.wave/contracts/ontology.schema.json b/.wave/contracts/ontology.schema.json new file mode 100644 index 0000000..d91d620 --- /dev/null +++ b/.wave/contracts/ontology.schema.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ontology Extraction", + "description": "Domain ontology with entities, relationships, invariants, behaviors, and boundaries", + "type": "object", + "required": ["domain", "entities", "relationships", "boundaries"], + "properties": { + "domain": { + "type": "string", + "description": "Domain name being modeled" + }, + "entities": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "type", "description", "properties"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string", "enum": ["aggregate", "entity", "value_object", "event", "service"] }, + "description": { "type": "string" }, + "properties": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "type"], + "properties": { + "name": { "type": "string" }, + "type": { "type": "string" }, + "required": { "type": "boolean" }, + "description": { "type": "string" } + } + } + } + } + } + }, + "relationships": { + "type": "array", + "items": { + "type": "object", + "required": ["from", "to", "type"], + "properties": { + "from": { "type": "string" }, + "to": { "type": "string" }, + "type": { "type": "string", "enum": ["has_many", "has_one", "belongs_to", "depends_on", "produces", "consumes"] }, + "description": { "type": "string" } + } + } + }, + "invariants": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "description", "scope"], + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "scope": { "type": "string" } + } + } + }, + "behaviors": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "actor", "description"], + "properties": { + "name": { "type": "string" }, + "actor": { "type": "string" }, + "description": { "type": "string" }, + "triggers": { "type": "array", "items": { "type": "string" } }, + "outcomes": { "type": "array", "items": { "type": "string" } } + } + } + }, + "boundaries": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "entities"], + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "entities": { "type": "array", "items": { "type": "string" } } + } + } + } + } +} diff --git a/.wave/contracts/plan-exploration.schema.json b/.wave/contracts/plan-exploration.schema.json new file mode 100644 index 0000000..3330045 --- /dev/null +++ b/.wave/contracts/plan-exploration.schema.json @@ -0,0 +1,179 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Plan Exploration", + "description": "Structured codebase exploration output for feature planning", + "type": "object", + "required": ["feature_context", "related_files", "patterns", "affected_modules", "testing_landscape", "risks", "timestamp"], + "properties": { + "feature_context": { + "type": "object", + "required": ["input_summary", "scope_assessment"], + "properties": { + "input_summary": { + "type": "string", + "minLength": 10, + "description": "Brief summary of the feature or task being planned" + }, + "scope_assessment": { + "type": "string", + "enum": ["small", "medium", "large", "epic"], + "description": "Estimated scope of the work" + } + } + }, + "related_files": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "relevance", "reason"], + "properties": { + "path": { + "type": "string", + "description": "File path relative to project root" + }, + "relevance": { + "type": "string", + "enum": ["primary", "secondary", "reference"], + "description": "How relevant this file is to the feature" + }, + "reason": { + "type": "string", + "description": "Why this file is relevant" + }, + "key_symbols": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Important functions, types, or constants in this file" + } + } + }, + "description": "Files related to the feature" + }, + "patterns": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "name", "description", "relevance"], + "properties": { + "id": { + "type": "string", + "pattern": "^PAT-[0-9]{1,4}$", + "description": "Pattern identifier" + }, + "name": { + "type": "string", + "description": "Short name for the pattern" + }, + "description": { + "type": "string", + "description": "What the pattern is and how it works" + }, + "example_file": { + "type": "string", + "description": "File that best demonstrates this pattern" + }, + "relevance": { + "type": "string", + "enum": ["must_follow", "should_follow", "informational"], + "description": "How important it is to follow this pattern" + } + } + }, + "description": "Codebase patterns and conventions discovered" + }, + "affected_modules": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "description", "impact_level"], + "properties": { + "path": { + "type": "string", + "description": "Module or package path" + }, + "description": { + "type": "string", + "description": "What this module does" + }, + "impact_level": { + "type": "string", + "enum": ["direct", "indirect", "none"], + "description": "Level of impact from the planned changes" + }, + "dependencies": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Modules this one depends on" + }, + "dependents": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Modules that depend on this one" + } + } + }, + "description": "Modules affected by the planned changes" + }, + "testing_landscape": { + "type": "object", + "required": ["existing_test_files", "testing_patterns"], + "properties": { + "existing_test_files": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Test files relevant to the feature" + }, + "testing_patterns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Testing patterns used in the codebase (e.g., table-driven, mocks)" + }, + "coverage_gaps": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Areas lacking test coverage" + } + } + }, + "risks": { + "type": "array", + "items": { + "type": "object", + "required": ["description", "severity"], + "properties": { + "description": { + "type": "string", + "description": "What the risk is" + }, + "severity": { + "type": "string", + "enum": ["high", "medium", "low"], + "description": "Risk severity" + }, + "mitigation": { + "type": "string", + "description": "How to mitigate this risk" + } + } + }, + "description": "Risks identified during exploration" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the exploration was completed" + } + } +} diff --git a/.wave/contracts/plan-review.schema.json b/.wave/contracts/plan-review.schema.json new file mode 100644 index 0000000..a8bd5af --- /dev/null +++ b/.wave/contracts/plan-review.schema.json @@ -0,0 +1,235 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Plan Review", + "description": "Structured review of a task breakdown plan", + "type": "object", + "required": ["verdict", "summary", "task_reviews", "cross_cutting_concerns", "recommendations", "risk_assessment", "timestamp", "markdown_content"], + "properties": { + "verdict": { + "type": "object", + "required": ["decision", "confidence", "rationale"], + "properties": { + "decision": { + "type": "string", + "enum": ["approve", "approve_with_notes", "revise"], + "description": "Overall verdict on the plan" + }, + "confidence": { + "type": "string", + "enum": ["high", "medium", "low"], + "description": "Confidence in the verdict" + }, + "rationale": { + "type": "string", + "minLength": 20, + "description": "Explanation for the verdict" + } + } + }, + "summary": { + "type": "object", + "required": ["total_tasks_reviewed", "issues_found", "issues_by_severity", "strengths"], + "properties": { + "total_tasks_reviewed": { + "type": "integer", + "minimum": 1, + "description": "Number of tasks reviewed" + }, + "issues_found": { + "type": "integer", + "minimum": 0, + "description": "Total number of issues found" + }, + "issues_by_severity": { + "type": "object", + "properties": { + "critical": { + "type": "integer", + "minimum": 0 + }, + "high": { + "type": "integer", + "minimum": 0 + }, + "medium": { + "type": "integer", + "minimum": 0 + }, + "low": { + "type": "integer", + "minimum": 0 + } + }, + "description": "Issues broken down by severity" + }, + "strengths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Things the plan does well" + } + } + }, + "task_reviews": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["task_id", "status"], + "properties": { + "task_id": { + "type": "string", + "pattern": "^T[0-9]{2}$", + "description": "ID of the reviewed task" + }, + "status": { + "type": "string", + "enum": ["ok", "needs_refinement", "missing_details", "overcomplicated", "wrong_persona", "bad_dependencies"], + "description": "Review status for this task" + }, + "issues": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "severity", "description", "suggestion"], + "properties": { + "id": { + "type": "string", + "pattern": "^REV-[0-9]{1,4}$", + "description": "Issue identifier" + }, + "severity": { + "type": "string", + "enum": ["critical", "high", "medium", "low"], + "description": "Issue severity" + }, + "description": { + "type": "string", + "description": "What the issue is" + }, + "suggestion": { + "type": "string", + "description": "How to fix it" + } + } + }, + "description": "Issues found in this task" + } + } + }, + "description": "Per-task review results" + }, + "cross_cutting_concerns": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "category", "description", "affected_tasks", "recommendation"], + "properties": { + "id": { + "type": "string", + "pattern": "^CC-[0-9]{1,4}$", + "description": "Concern identifier" + }, + "category": { + "type": "string", + "description": "Category of concern (e.g., testing, security, performance)" + }, + "description": { + "type": "string", + "description": "What the concern is" + }, + "affected_tasks": { + "type": "array", + "items": { + "type": "string", + "pattern": "^T[0-9]{2}$" + }, + "description": "Tasks affected by this concern" + }, + "recommendation": { + "type": "string", + "description": "Recommended action" + } + } + }, + "description": "Concerns that span multiple tasks" + }, + "recommendations": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "priority", "description", "type"], + "properties": { + "id": { + "type": "string", + "pattern": "^REC-[0-9]{1,4}$", + "description": "Recommendation identifier" + }, + "priority": { + "type": "string", + "enum": ["critical", "high", "medium", "low"], + "description": "Priority of the recommendation" + }, + "description": { + "type": "string", + "description": "What should be done" + }, + "type": { + "type": "string", + "enum": ["add_task", "modify_task", "remove_task", "reorder", "split_task", "merge_tasks", "change_persona", "add_dependency"], + "description": "Type of change recommended" + } + } + }, + "description": "Actionable recommendations for improving the plan" + }, + "risk_assessment": { + "type": "object", + "required": ["overall_risk", "key_risks"], + "properties": { + "overall_risk": { + "type": "string", + "enum": ["high", "medium", "low"], + "description": "Overall risk level of the plan" + }, + "key_risks": { + "type": "array", + "items": { + "type": "object", + "required": ["description", "severity", "likelihood"], + "properties": { + "description": { + "type": "string", + "description": "Risk description" + }, + "severity": { + "type": "string", + "enum": ["high", "medium", "low"] + }, + "likelihood": { + "type": "string", + "enum": ["high", "medium", "low"] + }, + "mitigation": { + "type": "string", + "description": "How to mitigate" + } + } + }, + "description": "Key risks identified in the plan" + } + } + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the review was completed" + }, + "markdown_content": { + "type": "string", + "minLength": 50, + "description": "Pre-rendered human-readable review in markdown format" + } + } +} diff --git a/.wave/contracts/plan-status.schema.json b/.wave/contracts/plan-status.schema.json new file mode 100644 index 0000000..1e86863 --- /dev/null +++ b/.wave/contracts/plan-status.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Speckit Plan Status", + "description": "Status report from the plan step: research, design, and implementation plan generation.", + "type": "object", + "required": ["plan_file", "feature_dir", "summary"], + "properties": { + "plan_file": { + "type": "string", + "minLength": 1, + "description": "Path to the generated implementation plan file" + }, + "feature_dir": { + "type": "string", + "minLength": 1, + "description": "Path to the feature directory" + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "Brief description of the plan produced" + }, + "research_file": { + "type": "string", + "description": "Path to research.md if generated" + }, + "data_model_file": { + "type": "string", + "description": "Path to data-model.md if generated" + }, + "constitution_issues": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Constitution compliance issues found during planning" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/plan-tasks.schema.json b/.wave/contracts/plan-tasks.schema.json new file mode 100644 index 0000000..fd48ce9 --- /dev/null +++ b/.wave/contracts/plan-tasks.schema.json @@ -0,0 +1,142 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Plan Tasks", + "description": "Structured task breakdown for feature implementation", + "type": "object", + "required": ["feature_summary", "tasks", "execution_order", "metadata", "markdown_content"], + "properties": { + "feature_summary": { + "type": "object", + "required": ["title", "description", "total_tasks", "estimated_total_complexity"], + "properties": { + "title": { + "type": "string", + "minLength": 5, + "description": "Short title for the feature" + }, + "description": { + "type": "string", + "minLength": 20, + "description": "Brief description of what the feature does" + }, + "total_tasks": { + "type": "integer", + "minimum": 1, + "description": "Total number of tasks in the breakdown" + }, + "estimated_total_complexity": { + "type": "string", + "enum": ["S", "M", "L", "XL"], + "description": "Overall complexity estimate" + } + } + }, + "tasks": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "title", "description", "persona", "complexity", "acceptance_criteria"], + "properties": { + "id": { + "type": "string", + "pattern": "^T[0-9]{2}$", + "description": "Task identifier (T01, T02, etc.)" + }, + "title": { + "type": "string", + "minLength": 5, + "description": "Short task title" + }, + "description": { + "type": "string", + "minLength": 20, + "description": "What needs to be done" + }, + "persona": { + "type": "string", + "enum": ["navigator", "philosopher", "craftsman", "auditor", "planner", "implementer", "reviewer"], + "description": "Which persona should execute this task" + }, + "complexity": { + "type": "string", + "enum": ["S", "M", "L", "XL"], + "description": "Task complexity estimate" + }, + "dependencies": { + "type": "array", + "items": { + "type": "string", + "pattern": "^T[0-9]{2}$" + }, + "description": "Task IDs that must complete before this one" + }, + "acceptance_criteria": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + }, + "description": "How to verify the task is complete" + }, + "affected_files": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Files that will be created or modified" + }, + "risks": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Risks specific to this task" + } + } + }, + "description": "Ordered list of tasks" + }, + "execution_order": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["phase", "task_ids"], + "properties": { + "phase": { + "type": "integer", + "minimum": 1, + "description": "Execution phase number" + }, + "task_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^T[0-9]{2}$" + }, + "description": "Tasks that can run in parallel in this phase" + } + } + }, + "description": "Parallelization groups for task execution" + }, + "metadata": { + "type": "object", + "required": ["generated_at"], + "properties": { + "generated_at": { + "type": "string", + "format": "date-time", + "description": "When the plan was generated" + } + } + }, + "markdown_content": { + "type": "string", + "minLength": 50, + "description": "Pre-rendered human-readable plan in markdown format" + } + } +} diff --git a/.wave/contracts/pr-result.schema.json b/.wave/contracts/pr-result.schema.json new file mode 100644 index 0000000..641e88e --- /dev/null +++ b/.wave/contracts/pr-result.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Pull Request Result", + "description": "Result of creating a pull request. Used by pipeline steps that produce a PR as their final output.", + "type": "object", + "required": ["pr_url", "pr_number", "branch", "summary"], + "properties": { + "pr_url": { + "type": "string", + "format": "uri", + "description": "URL to the created pull request" + }, + "pr_number": { + "type": "integer", + "minimum": 1, + "description": "Pull request number" + }, + "branch": { + "type": "string", + "minLength": 1, + "description": "Branch name for the pull request" + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "Brief description of what the pull request does" + }, + "copilot_review_requested": { + "type": "boolean", + "description": "Whether a Copilot review was requested for the PR" + }, + "closes_issue": { + "type": "boolean", + "description": "Whether the PR closes a linked issue" + }, + "issue_number": { + "type": "integer", + "minimum": 1, + "description": "Issue number that this PR addresses" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/probed-findings.schema.json b/.wave/contracts/probed-findings.schema.json new file mode 100644 index 0000000..e7f9027 --- /dev/null +++ b/.wave/contracts/probed-findings.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Probed Findings", + "description": "Deep-dive analysis from the recinq probe step", + "type": "object", + "required": ["target", "total_probed", "findings"], + "properties": { + "target": { + "type": "string", + "description": "What was analyzed (path, module, or 'whole-project')" + }, + "total_probed": { + "type": "integer", + "description": "Total number of confirmed findings that were probed deeper" + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "deeper_analysis", "files"], + "properties": { + "id": { + "type": "string", + "description": "Original finding ID (e.g. DVG-001)" + }, + "deeper_analysis": { + "type": "string", + "description": "Extended analysis including dependency graph, second-order effects, and cross-finding patterns" + }, + "files": { + "type": "array", + "items": { "type": "string" }, + "description": "File paths involved in this finding and its connections" + } + }, + "additionalProperties": true + }, + "description": "Per-finding deep-dive results" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/project-survey.schema.json b/.wave/contracts/project-survey.schema.json new file mode 100644 index 0000000..25e36e6 --- /dev/null +++ b/.wave/contracts/project-survey.schema.json @@ -0,0 +1,119 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Project Survey", + "description": "Structured survey of a project for onboarding guide generation", + "type": "object", + "required": ["project", "build", "structure", "timestamp"], + "properties": { + "project": { + "type": "object", + "required": ["name", "language", "purpose"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "language": { "type": "string", "minLength": 1 }, + "framework": { "type": "string" }, + "purpose": { "type": "string", "minLength": 5 }, + "license": { "type": "string" } + } + }, + "build": { + "type": "object", + "required": ["commands"], + "properties": { + "commands": { + "type": "object", + "properties": { + "build": { "type": "string" }, + "test": { "type": "string" }, + "run": { "type": "string" } + } + }, + "prerequisites": { "type": "array", "items": { "type": "string" } }, + "ci_system": { "type": "string" } + } + }, + "structure": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["path", "purpose"], + "properties": { + "path": { "type": "string", "minLength": 1 }, + "purpose": { "type": "string", "minLength": 1 }, + "key_files": { "type": "array", "items": { "type": "string" } } + } + } + }, + "architecture": { + "type": "object", + "properties": { + "entry_points": { "type": "array", "items": { "type": "string" } }, + "components": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "role"], + "properties": { + "name": { "type": "string" }, + "package": { "type": "string" }, + "role": { "type": "string" } + } + } + }, + "patterns": { "type": "array", "items": { "type": "string" } } + } + }, + "dependencies": { + "type": "object", + "properties": { + "key_deps": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "purpose"], + "properties": { + "name": { "type": "string" }, + "purpose": { "type": "string" }, + "import": { "type": "string" } + } + } + }, + "total_count": { "type": "integer", "minimum": 0 } + } + }, + "configuration": { + "type": "object", + "properties": { + "env_vars": { "type": "array", "items": { "type": "string" } }, + "config_files": { "type": "array", "items": { "type": "string" } }, + "feature_flags": { "type": "array", "items": { "type": "string" } } + } + }, + "testing": { + "type": "object", + "properties": { + "framework": { "type": "string" }, + "patterns": { "type": "array", "items": { "type": "string" } }, + "run_command": { "type": "string" }, + "coverage_command": { "type": "string" } + } + }, + "workflow": { + "type": "object", + "properties": { + "branch_strategy": { "type": "string" }, + "commit_convention": { "type": "string" }, + "pr_process": { "type": "string" } + } + }, + "documentation": { + "type": "object", + "properties": { + "locations": { "type": "array", "items": { "type": "string" } }, + "quality": { "type": "string", "enum": ["good", "adequate", "sparse", "missing"] } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/recinq-context.schema.json b/.wave/contracts/recinq-context.schema.json new file mode 100644 index 0000000..44ec3ca --- /dev/null +++ b/.wave/contracts/recinq-context.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Recinq Context", + "description": "Parsed input context from the recinq gather step", + "type": "object", + "required": ["input_type", "original_input", "focus_hint"], + "properties": { + "input_type": { + "type": "string", + "enum": ["issue", "pr", "local"], + "description": "The kind of input: GitHub issue, GitHub PR, or local path/description" + }, + "original_input": { + "type": "string", + "description": "The raw input string exactly as given by the user" + }, + "focus_hint": { + "type": "string", + "description": "Summary of what should be simplified or focused on" + }, + "url": { + "type": "string", + "description": "GitHub issue or PR URL (empty for local input)" + }, + "repo": { + "type": "string", + "description": "GitHub owner/repo (empty for local input)" + }, + "number": { + "type": "integer", + "description": "GitHub issue or PR number (0 for local input)" + }, + "title": { + "type": "string", + "description": "GitHub issue or PR title (empty for local input)" + }, + "body": { + "type": "string", + "description": "GitHub issue or PR body (empty for local input)" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/refactor-analysis.schema.json b/.wave/contracts/refactor-analysis.schema.json new file mode 100644 index 0000000..9027347 --- /dev/null +++ b/.wave/contracts/refactor-analysis.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["target_files", "affected_callers", "existing_tests", "integration_points", "risk_assessment"], + "properties": { + "target_files": { + "type": "array", + "items": { "type": "string" } + }, + "affected_callers": { + "type": "array", + "items": { "type": "string" } + }, + "existing_tests": { + "type": "array", + "items": { "type": "string" } + }, + "integration_points": { + "type": "array", + "items": { "type": "string" } + }, + "risk_assessment": { + "type": "string", + "enum": ["low", "medium", "high"] + } + } +} diff --git a/.wave/contracts/research-findings.schema.json b/.wave/contracts/research-findings.schema.json new file mode 100644 index 0000000..6c0578e --- /dev/null +++ b/.wave/contracts/research-findings.schema.json @@ -0,0 +1,178 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Research Findings", + "description": "Web research results organized by topic with sources and insights", + "type": "object", + "required": ["issue_reference", "findings_by_topic", "research_metadata"], + "properties": { + "issue_reference": { + "type": "object", + "required": ["issue_number", "repository"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$" + } + } + }, + "findings_by_topic": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["topic_id", "topic_title", "findings", "confidence_level"], + "properties": { + "topic_id": { + "type": "string", + "pattern": "^TOPIC-[0-9]{1,4}$", + "description": "Reference to topic ID from research-topics" + }, + "topic_title": { + "type": "string", + "description": "Topic title for readability" + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "summary", "source", "relevance_score"], + "properties": { + "id": { + "type": "string", + "pattern": "^FINDING-[0-9]{1,4}$", + "description": "Unique finding identifier" + }, + "summary": { + "type": "string", + "minLength": 20, + "maxLength": 2000, + "description": "Summarized finding content" + }, + "key_points": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Bullet points of key insights" + }, + "source": { + "type": "object", + "required": ["url", "title", "type"], + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Source URL" + }, + "title": { + "type": "string", + "description": "Source page/article title" + }, + "type": { + "type": "string", + "enum": ["official_docs", "github_issue", "github_discussion", "stack_overflow", "blog_post", "tutorial", "api_reference", "academic_paper", "forum_post", "other"], + "description": "Type of source" + }, + "author": { + "type": "string", + "description": "Author or organization name" + }, + "credibility_score": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Source credibility assessment (0-1)" + } + } + }, + "relevance_score": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Relevance to the topic (0-1)" + }, + "quotes": { + "type": "array", + "items": { + "type": "object", + "required": ["text"], + "properties": { + "text": { + "type": "string", + "description": "Direct quote from source" + }, + "context": { + "type": "string", + "description": "Context for the quote" + } + } + }, + "description": "Notable quotes from the source" + } + } + }, + "description": "Individual research findings for this topic" + }, + "confidence_level": { + "type": "string", + "enum": ["high", "medium", "low", "inconclusive"], + "description": "Overall confidence in findings for this topic" + }, + "consensus": { + "type": "string", + "description": "Summary of consensus across sources" + }, + "gaps": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Questions that couldn't be fully answered" + } + } + }, + "description": "Research findings organized by topic" + }, + "research_metadata": { + "type": "object", + "required": ["started_at", "completed_at"], + "properties": { + "started_at": { + "type": "string", + "format": "date-time", + "description": "Research start timestamp" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "description": "Research completion timestamp" + }, + "persona": { + "type": "string", + "description": "Wave persona that conducted research" + }, + "total_sources_consulted": { + "type": "integer", + "minimum": 0, + "description": "Total number of sources examined" + }, + "sources_included": { + "type": "integer", + "minimum": 0, + "description": "Number of sources included in findings" + }, + "search_queries_used": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Search queries that were used" + } + } + } + } +} diff --git a/.wave/contracts/research-report.schema.json b/.wave/contracts/research-report.schema.json new file mode 100644 index 0000000..c352ff1 --- /dev/null +++ b/.wave/contracts/research-report.schema.json @@ -0,0 +1,206 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Research Report", + "description": "Synthesized research report for GitHub issue comment", + "type": "object", + "required": ["issue_reference", "executive_summary", "detailed_findings", "recommendations", "sources", "markdown_content"], + "properties": { + "issue_reference": { + "type": "object", + "required": ["issue_number", "repository", "title"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1 + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + } + }, + "executive_summary": { + "type": "object", + "required": ["overview", "key_findings", "primary_recommendation"], + "properties": { + "overview": { + "type": "string", + "minLength": 50, + "maxLength": 1000, + "description": "Brief overview of research conducted and main conclusions" + }, + "key_findings": { + "type": "array", + "minItems": 1, + "maxItems": 7, + "items": { + "type": "string" + }, + "description": "Top findings as bullet points" + }, + "primary_recommendation": { + "type": "string", + "minLength": 20, + "description": "Single most important recommendation" + }, + "confidence_assessment": { + "type": "string", + "enum": ["high", "medium", "low"], + "description": "Overall confidence in the research conclusions" + } + } + }, + "detailed_findings": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["section_title", "content", "relevance"], + "properties": { + "section_title": { + "type": "string", + "minLength": 3, + "description": "Section heading" + }, + "topic_id": { + "type": "string", + "pattern": "^TOPIC-[0-9]{1,4}$", + "description": "Reference to original topic if applicable" + }, + "content": { + "type": "string", + "minLength": 50, + "description": "Detailed markdown content for this section" + }, + "relevance": { + "type": "string", + "enum": ["critical", "high", "medium", "supplementary"], + "description": "How relevant this section is to the issue" + }, + "code_examples": { + "type": "array", + "items": { + "type": "object", + "required": ["code", "language"], + "properties": { + "code": { + "type": "string", + "description": "Code snippet" + }, + "language": { + "type": "string", + "description": "Language for syntax highlighting" + }, + "caption": { + "type": "string", + "description": "Explanation of the code" + } + } + }, + "description": "Code examples for this section" + }, + "related_sources": { + "type": "array", + "items": { + "type": "string", + "pattern": "^SRC-[0-9]{1,4}$" + }, + "description": "References to sources list" + } + } + }, + "description": "Detailed findings organized by section" + }, + "recommendations": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": { + "type": "object", + "required": ["id", "title", "description", "priority"], + "properties": { + "id": { + "type": "string", + "pattern": "^REC-[0-9]{1,4}$", + "description": "Recommendation identifier" + }, + "title": { + "type": "string", + "minLength": 5, + "maxLength": 100, + "description": "Concise recommendation title" + }, + "description": { + "type": "string", + "minLength": 20, + "description": "Detailed recommendation explanation" + }, + "priority": { + "type": "string", + "enum": ["critical", "high", "medium", "low"], + "description": "Recommendation priority" + }, + "effort_estimate": { + "type": "string", + "enum": ["trivial", "small", "medium", "large", "unknown"], + "description": "Estimated implementation effort" + } + } + }, + "description": "Actionable recommendations based on research" + }, + "sources": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "url", "title"], + "properties": { + "id": { + "type": "string", + "pattern": "^SRC-[0-9]{1,4}$", + "description": "Source identifier for cross-referencing" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Source URL" + }, + "title": { + "type": "string", + "description": "Source title" + }, + "type": { + "type": "string", + "enum": ["official_docs", "github_issue", "github_discussion", "stack_overflow", "blog_post", "tutorial", "api_reference", "academic_paper", "forum_post", "other"] + }, + "reliability": { + "type": "string", + "enum": ["authoritative", "reputable", "community", "unverified"], + "description": "Source reliability assessment" + } + } + }, + "description": "All sources cited in the report" + }, + "markdown_content": { + "type": "string", + "minLength": 100, + "description": "Pre-rendered markdown content ready for GitHub comment" + }, + "follow_up_questions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Questions that warrant further investigation" + } + } +} diff --git a/.wave/contracts/research-topics.schema.json b/.wave/contracts/research-topics.schema.json new file mode 100644 index 0000000..72b9c57 --- /dev/null +++ b/.wave/contracts/research-topics.schema.json @@ -0,0 +1,106 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Research Topics", + "description": "Extracted research topics from GitHub issue analysis", + "type": "object", + "required": ["issue_reference", "topics"], + "properties": { + "issue_reference": { + "type": "object", + "required": ["issue_number", "repository"], + "properties": { + "issue_number": { + "type": "integer", + "minimum": 1, + "description": "Reference to source issue number" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$", + "description": "Repository full name (owner/repo)" + }, + "title": { + "type": "string", + "description": "Issue title for context" + } + } + }, + "topics": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": { + "type": "object", + "required": ["id", "title", "questions", "keywords", "priority"], + "properties": { + "id": { + "type": "string", + "pattern": "^TOPIC-[0-9]{1,4}$", + "description": "Unique topic identifier (e.g., TOPIC-001)" + }, + "title": { + "type": "string", + "minLength": 5, + "maxLength": 200, + "description": "Concise topic title" + }, + "description": { + "type": "string", + "description": "Detailed description of what needs to be researched" + }, + "questions": { + "type": "array", + "minItems": 1, + "maxItems": 5, + "items": { + "type": "string", + "minLength": 10 + }, + "description": "Specific questions to answer through research" + }, + "keywords": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": { + "type": "string", + "minLength": 2 + }, + "description": "Search keywords for web research" + }, + "priority": { + "type": "string", + "enum": ["critical", "high", "medium", "low"], + "description": "Research priority level" + }, + "category": { + "type": "string", + "enum": ["technical", "documentation", "best_practices", "security", "performance", "compatibility", "other"], + "description": "Topic category for organization" + } + } + }, + "description": "List of research topics extracted from the issue" + }, + "extraction_metadata": { + "type": "object", + "properties": { + "extracted_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp when topics were extracted" + }, + "persona": { + "type": "string", + "description": "Wave persona that performed the extraction" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Confidence score for topic relevance (0-1)" + } + } + } + } +} diff --git a/.wave/contracts/scope-plan.schema.json b/.wave/contracts/scope-plan.schema.json new file mode 100644 index 0000000..ef567d2 --- /dev/null +++ b/.wave/contracts/scope-plan.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epic Scope Plan and Results", + "description": "Schema for epic decomposition plan with created sub-issues", + "type": "object", + "required": ["parent_issue", "created_issues", "total_created"], + "properties": { + "parent_issue": { + "type": "object", + "required": ["number", "url", "repository"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "url": { + "type": "string", + "format": "uri" + }, + "repository": { + "type": "string", + "pattern": "^[^/]+/[^/]+$" + } + } + }, + "created_issues": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["number", "title", "url", "success"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "format": "uri" + }, + "labels": { + "type": "array", + "items": { "type": "string" } + }, + "success": { + "type": "boolean" + }, + "complexity": { + "type": "string", + "enum": ["S", "M", "L", "XL"] + }, + "dependencies": { + "type": "array", + "items": { "type": "string" }, + "description": "Titles or numbers of issues this depends on" + } + } + } + }, + "total_created": { + "type": "integer", + "minimum": 0 + }, + "total_failed": { + "type": "integer", + "minimum": 0 + } + } +} diff --git a/.wave/contracts/scope-report.schema.json b/.wave/contracts/scope-report.schema.json new file mode 100644 index 0000000..60f82b9 --- /dev/null +++ b/.wave/contracts/scope-report.schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Epic Scope Verification Report", + "description": "Schema for verification of created sub-issues", + "type": "object", + "required": ["parent_issue", "verified_issues", "summary"], + "properties": { + "parent_issue": { + "type": "object", + "required": ["number", "url"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "url": { + "type": "string", + "format": "uri" + } + } + }, + "verified_issues": { + "type": "array", + "items": { + "type": "object", + "required": ["number", "title", "exists"], + "properties": { + "number": { + "type": "integer", + "minimum": 1 + }, + "title": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "exists": { + "type": "boolean" + }, + "has_acceptance_criteria": { + "type": "boolean" + }, + "references_parent": { + "type": "boolean" + } + } + } + }, + "summary": { + "type": "object", + "required": ["total_verified", "total_valid", "total_issues_created"], + "properties": { + "total_verified": { + "type": "integer", + "minimum": 0 + }, + "total_valid": { + "type": "integer", + "minimum": 0 + }, + "total_issues_created": { + "type": "integer", + "minimum": 0 + }, + "comment_posted": { + "type": "boolean", + "description": "Whether a summary comment was posted on the epic" + }, + "comment_url": { + "type": "string", + "description": "URL of the summary comment if posted" + } + } + } + } +} diff --git a/.wave/contracts/security-scan.schema.json b/.wave/contracts/security-scan.schema.json new file mode 100644 index 0000000..8ac0d56 --- /dev/null +++ b/.wave/contracts/security-scan.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Security Scan Results", + "description": "Output from the security scan step", + "type": "object", + "required": ["target", "findings", "summary", "timestamp"], + "properties": { + "target": { + "type": "string", + "minLength": 1, + "description": "What was scanned" + }, + "attack_surface": { + "type": "array", + "items": { + "type": "object", + "required": ["entry_point", "type", "description"], + "properties": { + "entry_point": { "type": "string", "minLength": 1 }, + "type": { + "type": "string", + "enum": ["http_handler", "cli_input", "file_parser", "db_query", "api_call", "ipc", "other"] + }, + "description": { "type": "string", "minLength": 1 }, + "risk_level": { "type": "string", "enum": ["high", "medium", "low"] } + } + } + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "title", "severity", "category", "location", "description"], + "properties": { + "id": { "type": "string", "pattern": "^SEC-[0-9]{1,4}$" }, + "title": { "type": "string", "minLength": 5 }, + "severity": { "type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"] }, + "category": { "type": "string", "minLength": 1 }, + "location": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 10 }, + "evidence": { "type": "string" }, + "exploitability": { "type": "string" } + } + } + }, + "secrets_check": { + "type": "object", + "properties": { + "hardcoded_secrets": { "type": "array", "items": { "type": "string" } }, + "insecure_configs": { "type": "array", "items": { "type": "string" } } + } + }, + "summary": { + "type": "object", + "required": ["total_findings", "by_severity", "risk_assessment"], + "properties": { + "total_findings": { "type": "integer", "minimum": 0 }, + "by_severity": { + "type": "object", + "required": ["CRITICAL", "HIGH", "MEDIUM", "LOW"], + "properties": { + "CRITICAL": { "type": "integer", "minimum": 0 }, + "HIGH": { "type": "integer", "minimum": 0 }, + "MEDIUM": { "type": "integer", "minimum": 0 }, + "LOW": { "type": "integer", "minimum": 0 } + } + }, + "risk_assessment": { "type": "string", "minLength": 10 } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/spec-phase.schema.json b/.wave/contracts/spec-phase.schema.json new file mode 100644 index 0000000..2cf37d5 --- /dev/null +++ b/.wave/contracts/spec-phase.schema.json @@ -0,0 +1,119 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.recinq.com/wave/spec-phase.schema.json", + "title": "Wave Spec Phase Output Contract", + "description": "Validates spec phase outputs for prototype-driven development pipeline", + "type": "object", + "properties": { + "phase": { + "type": "string", + "const": "spec", + "description": "Phase identifier" + }, + "artifacts": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*spec\\.md$", + "description": "Path to specification file" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Specification file must exist" + }, + "content_type": { + "type": "string", + "const": "markdown", + "description": "Specification content format" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + }, + "requirements": { + "type": "object", + "properties": { + "path": { + "type": "string", + "pattern": "^.*requirements\\.md$", + "description": "Path to requirements file" + }, + "exists": { + "type": "boolean", + "const": true, + "description": "Requirements file must exist" + }, + "content_type": { + "type": "string", + "const": "markdown", + "description": "Requirements content format" + } + }, + "required": ["path", "exists", "content_type"], + "additionalProperties": false + } + }, + "required": ["spec"], + "additionalProperties": true + }, + "validation": { + "type": "object", + "properties": { + "completeness_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Specification completeness rating" + }, + "clarity_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Specification clarity rating" + }, + "testability_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Specification testability rating" + }, + "specification_quality": { + "type": "string", + "enum": ["poor", "fair", "good", "excellent"], + "description": "Overall specification quality assessment" + } + }, + "required": ["specification_quality"], + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When spec phase was completed" + }, + "duration_seconds": { + "type": "number", + "minimum": 0, + "description": "Time taken for spec phase" + }, + "input_description": { + "type": "string", + "minLength": 1, + "description": "Original project description input" + } + }, + "required": ["timestamp", "input_description"], + "additionalProperties": false + } + }, + "required": ["phase", "artifacts", "validation", "metadata"], + "additionalProperties": false +} diff --git a/.wave/contracts/specify-status.schema.json b/.wave/contracts/specify-status.schema.json new file mode 100644 index 0000000..c9cd7f2 --- /dev/null +++ b/.wave/contracts/specify-status.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Speckit Specify Status", + "description": "Status report from the specify step: feature branch creation and spec authoring.", + "type": "object", + "required": ["branch_name", "spec_file", "feature_dir", "summary"], + "properties": { + "branch_name": { + "type": "string", + "minLength": 1, + "description": "Git branch name created for the feature" + }, + "spec_file": { + "type": "string", + "minLength": 1, + "description": "Path to the generated spec.md file" + }, + "feature_dir": { + "type": "string", + "minLength": 1, + "description": "Path to the feature directory" + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "Brief description of what was created" + }, + "checklist_status": { + "type": "string", + "enum": ["pass", "fail"], + "description": "Result of self-validation against the quality checklist" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/supervision-evaluation.schema.json b/.wave/contracts/supervision-evaluation.schema.json new file mode 100644 index 0000000..e32dc40 --- /dev/null +++ b/.wave/contracts/supervision-evaluation.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Supervision Evaluation", + "description": "Quality evaluation of both output and process", + "type": "object", + "required": ["output_quality", "process_quality", "overall_score", "timestamp"], + "properties": { + "output_quality": { + "type": "object", + "required": ["correctness", "completeness", "test_coverage", "code_quality"], + "properties": { + "correctness": { + "type": "object", + "required": ["score", "findings"], + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + }, + "completeness": { + "type": "object", + "required": ["score", "findings"], + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + }, + "test_coverage": { + "type": "object", + "required": ["score", "findings"], + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + }, + "code_quality": { + "type": "object", + "required": ["score", "findings"], + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + } + } + }, + "process_quality": { + "type": "object", + "required": ["efficiency", "scope_discipline", "tool_usage"], + "properties": { + "efficiency": { + "type": "object", + "required": ["score", "findings"], + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + }, + "scope_discipline": { + "type": "object", + "required": ["score", "findings"], + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + }, + "tool_usage": { + "type": "object", + "required": ["score", "findings"], + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + }, + "token_economy": { + "type": "object", + "properties": { + "score": { "type": "string", "enum": ["excellent", "good", "adequate", "poor"] }, + "findings": { "type": "array", "items": { "type": "string" } } + } + } + } + }, + "overall_score": { + "type": "string", + "enum": ["excellent", "good", "adequate", "poor"] + }, + "key_strengths": { + "type": "array", + "items": { "type": "string" } + }, + "key_concerns": { + "type": "array", + "items": { "type": "string" } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/supervision-evidence.schema.json b/.wave/contracts/supervision-evidence.schema.json new file mode 100644 index 0000000..cd193c4 --- /dev/null +++ b/.wave/contracts/supervision-evidence.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Supervision Evidence", + "description": "Evidence gathered for supervising completed work", + "type": "object", + "required": ["scope", "commits", "artifacts", "test_results", "timestamp"], + "properties": { + "scope": { + "type": "object", + "description": "What was inspected and how it was identified", + "required": ["description", "detection_method"], + "properties": { + "description": { "type": "string", "minLength": 5 }, + "detection_method": { + "type": "string", + "enum": ["auto_detect", "pipeline_run", "branch", "pr", "freeform"] + }, + "pipeline_name": { "type": "string" }, + "branch_name": { "type": "string" }, + "pr_number": { "type": "integer" } + } + }, + "commits": { + "type": "array", + "items": { + "type": "object", + "required": ["hash", "message", "files_changed"], + "properties": { + "hash": { "type": "string" }, + "message": { "type": "string" }, + "author": { "type": "string" }, + "files_changed": { "type": "integer" }, + "insertions": { "type": "integer" }, + "deletions": { "type": "integer" }, + "has_session_transcript": { "type": "boolean" }, + "transcript_summary": { "type": "string" } + } + } + }, + "artifacts": { + "type": "array", + "description": "Pipeline workspace artifacts found", + "items": { + "type": "object", + "required": ["path", "type"], + "properties": { + "path": { "type": "string" }, + "type": { "type": "string" }, + "size_bytes": { "type": "integer" }, + "summary": { "type": "string" } + } + } + }, + "test_results": { + "type": "object", + "required": ["ran", "passed"], + "properties": { + "ran": { "type": "boolean" }, + "passed": { "type": "boolean" }, + "total_tests": { "type": "integer" }, + "failed_tests": { "type": "integer" }, + "coverage_percent": { "type": "number" }, + "details": { "type": "string" } + } + }, + "session_transcripts": { + "type": "array", + "description": "Claudit session transcripts from git notes", + "items": { + "type": "object", + "required": ["commit_hash", "summary"], + "properties": { + "commit_hash": { "type": "string" }, + "summary": { "type": "string" }, + "tool_calls_count": { "type": "integer" }, + "notable_detours": { "type": "array", "items": { "type": "string" } }, + "errors_encountered": { "type": "array", "items": { "type": "string" } } + } + } + }, + "timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/.wave/contracts/tasks-status.schema.json b/.wave/contracts/tasks-status.schema.json new file mode 100644 index 0000000..05f32be --- /dev/null +++ b/.wave/contracts/tasks-status.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Speckit Tasks Status", + "description": "Status report from the tasks step: dependency-ordered task breakdown generation.", + "type": "object", + "required": ["total_tasks", "feature_dir", "summary"], + "properties": { + "total_tasks": { + "type": "integer", + "minimum": 1, + "description": "Total number of tasks generated" + }, + "feature_dir": { + "type": "string", + "minLength": 1, + "description": "Path to the feature directory" + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "Brief description of the task breakdown" + }, + "tasks_per_story": { + "type": "object", + "description": "Mapping of user story identifiers to task counts" + }, + "parallel_opportunities": { + "type": "integer", + "description": "Number of tasks that can be executed in parallel" + } + }, + "additionalProperties": true +} diff --git a/.wave/contracts/validated-findings.schema.json b/.wave/contracts/validated-findings.schema.json new file mode 100644 index 0000000..ef67a45 --- /dev/null +++ b/.wave/contracts/validated-findings.schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Validated Findings", + "description": "Convergent validation results from the recinq converge step", + "type": "object", + "required": ["target", "total_findings", "confirmed", "rejected", "findings"], + "properties": { + "target": { + "type": "string", + "description": "What was analyzed (path, module, or 'whole-project')" + }, + "total_findings": { + "type": "integer", + "description": "Total number of divergent findings reviewed" + }, + "confirmed": { + "type": "integer", + "description": "Number of findings classified as CONFIRMED or PARTIALLY_CONFIRMED" + }, + "rejected": { + "type": "integer", + "description": "Number of findings classified as REJECTED" + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "status", "rationale"], + "properties": { + "id": { + "type": "string", + "description": "Original finding ID (e.g. DVG-001)" + }, + "status": { + "type": "string", + "enum": ["CONFIRMED", "PARTIALLY_CONFIRMED", "REJECTED"], + "description": "Validation classification" + }, + "rationale": { + "type": "string", + "description": "Explanation of why the finding was classified this way" + } + }, + "additionalProperties": true + }, + "description": "Per-finding validation results" + } + }, + "additionalProperties": true +} diff --git a/.wave/logs/audit-doc-20260328-173838-e10a.log b/.wave/logs/audit-doc-20260328-173838-e10a.log new file mode 100644 index 0000000..3e00f81 --- /dev/null +++ b/.wave/logs/audit-doc-20260328-173838-e10a.log @@ -0,0 +1,106 @@ +[17:38:38] → scan-changes (navigator) [claude-haiku via claude] +[17:38:38] scan-changes Executing agent +[17:39:00] scan-changes Bash → git log --oneline --name-status main... +[17:39:01] scan-changes Bash → git status --porcelain +[17:39:02] scan-changes Glob → **/*.md +[17:39:02] scan-changes Glob → docs/**/* +[17:39:09] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:11] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:12] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:12] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:14] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:14] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:15] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:22] scan-changes Glob → schemas/**/* +[17:39:23] scan-changes Glob → src/**/*.js +[17:39:25] scan-changes Bash → cat /home/mwc/Coding/code-crispies/.... +[17:39:26] scan-changes Glob → lessons/**/*.json +[17:39:31] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:32] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:33] scan-changes Grep → @(param|returns|typedef|type|descrip... +[17:39:34] scan-changes Grep → ^\s*(export\s+)?(function|const|clas... +[17:39:52] scan-changes Bash → ls /home/mwc/Coding/code-crispies/.w... +[17:39:54] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:39:56] scan-changes Glob → CONTRIBUTING.md +[17:39:57] scan-changes Glob → CHANGELOG.md +[17:40:11] scan-changes Bash → ls /home/mwc/Coding/code-crispies/.w... +[17:40:12] scan-changes Bash → ls /home/mwc/Coding/code-crispies/.w... +[17:40:15] scan-changes Read → /home/mwc/Coding/code-crispies/.wave... +[17:40:57] scan-changes Write → /home/mwc/Coding/code-crispies/.wav... +[17:41:07] scan-changes Processing results +[17:41:07] scan-changes validating contract +[17:41:07] ✓ scan-changes completed (148.9s, 63.6k in / 7.6k out) +[17:41:07] ├─ artifact: .wave/output/scan-results.json (written) +[17:41:07] └─ contract: doc-scan-results.schema.json ✓ valid +[17:41:07] → analyze-consistency (reviewer) +[17:41:07] analyze-consistency Executing agent +[17:41:15] analyze-consistency Bash → ls -la /home/mwc/Coding/code-crispie... +[17:41:16] analyze-consistency Bash → ls -la /home/mwc/Coding/code-crispie... +[17:41:21] analyze-consistency Bash → file /home/mwc/Coding/code-crispies/... +[17:41:22] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:27] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:28] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:28] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:42] analyze-consistency Agent → +[17:41:44] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:45] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:46] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:46] analyze-consistency Glob → /home/mwc/Coding/code-crispies/.wave... +[17:41:48] analyze-consistency Glob → /home/mwc/Coding/code-crispies/.wave... +[17:41:48] analyze-consistency Glob → /home/mwc/Coding/code-crispies/.wave... +[17:41:51] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:52] analyze-consistency Grep → process\.env|import\.meta\.env +[17:41:52] analyze-consistency Grep → SUPABASE|supabase|auth|AUTH +[17:41:54] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:55] analyze-consistency Read → /home/mwc/Coding/code-crispies/.wave... +[17:41:58] analyze-consistency Grep → VITE_|process\.env +[17:42:22] analyze-consistency Bash → mkdir -p /home/mwc/Coding/code-crisp... +[17:43:07] analyze-consistency Write → /home/mwc/Coding/code-crispies/.wav... +[17:43:18] analyze-consistency Bash → node -e " +const report = require('./... +[17:43:25] analyze-consistency Processing results +[17:43:25] analyze-consistency validating contract +[17:43:25] ✓ analyze-consistency completed (138.1s, 27.3k in / 6.1k out) +[17:43:25] ├─ artifact: .wave/output/consistency-report.json (written) +[17:43:25] └─ contract: doc-consistency-report.schema.json ✓ valid +[17:43:25] → compose-report (navigator) [claude-haiku via claude] +[17:43:25] compose-report Executing agent +[17:43:32] compose-report Bash → ls .wave/artifacts/report +[17:43:36] compose-report Read → /home/mwc/Coding/code-crispies/.wave... +[17:43:40] compose-report Bash → ls .wave/output 2>/dev/null || mkdir... +[17:43:52] compose-report Write → /home/mwc/Coding/code-crispies/.wav... +[17:44:00] compose-report Processing results +[17:44:00] compose-report validating contract +[17:44:00] ✓ compose-report completed (34.9s, 8.0k in / 1.7k out) +[17:44:00] ├─ artifact: .wave/output/report.md (written) +[17:44:00] └─ contract: non_empty_file ✓ valid +[17:44:00] → publish (craftsman) [claude-opus via claude] +[17:44:00] publish Executing agent +[17:44:07] publish Read → /home/mwc/Coding/code-crispies/.wave... +[17:44:08] publish Bash → gh repo view --json nameWithOwner --... +[17:44:14] publish Bash → gh issue create \ + --title "docs: d... +[17:44:22] publish Bash → ls /home/mwc/Coding/code-crispies/.w... +[17:44:26] publish Write → /home/mwc/Coding/code-crispies/.wav... +[17:44:29] publish Processing results +[17:44:29] publish validating contract +[17:44:29] ✓ publish completed (28.9s, 5.8k in / 1.0k out) +[17:44:29] ├─ artifact: .wave/output/issue-result.json (written) +[17:44:29] └─ contract: doc-issue-result.schema.json ✓ valid +[17:44:29] ⚠ publish [publish] outcome: .issue_url at .wave/output/issue-result.json: key "issue_url" not found +2026/03/28 17:44:29 [retro] quantitative retrospective saved for run audit-doc-20260328-173838-e10a + + ✓ Pipeline 'audit-doc' completed successfully (351.2s, 121.1k tokens) + + Outcomes + ✓ Branch: audit-doc-20260328-173838-e10a (local only) + + ! [publish] outcome: .issue_url at .wave/output/issue-result.json: key "issue_url" not found + + 4 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-doc-20260328-173838-e10a/__wt_audit-doc-20260328-173838-e10a/.wave/output/scan-results.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-doc-20260328-173838-e10a/__wt_audit-doc-20260328-173838-e10a/.wave/output/consistency-report.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-doc-20260328-173838-e10a/__wt_audit-doc-20260328-173838-e10a/.wave/output/report.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-doc-20260328-173838-e10a/__wt_audit-doc-20260328-173838-e10a/.wave/output/issue-result.json + + diff --git a/.wave/logs/audit-dx-20260328-135948-267b.log b/.wave/logs/audit-dx-20260328-135948-267b.log new file mode 100644 index 0000000..2f26b22 --- /dev/null +++ b/.wave/logs/audit-dx-20260328-135948-267b.log @@ -0,0 +1,101 @@ +[13:59:48] → audit (navigator) [claude-haiku via claude] +[13:59:48] audit Executing agent +[14:00:04] audit Agent → +[14:00:10] audit Agent → +[14:00:16] audit Agent → +[14:00:21] audit Agent → +[14:00:24] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:24] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:25] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:25] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:26] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:27] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:27] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:28] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:29] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:29] audit Bash → ls /home/mwc/Coding/code-crispies/.w... +[14:00:30] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:30] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:31] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:31] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:32] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:33] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:33] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:34] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:36] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:36] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:36] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:38] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:38] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:39] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:40] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:41] audit Bash → wc -l /home/mwc/Coding/code-crispies... +[14:00:41] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:41] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:42] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:43] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:44] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:45] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:45] audit Bash → tree -L 2 /home/mwc/Coding/code-cris... +[14:00:46] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:47] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:47] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:48] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:50] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:50] audit Bash → wc -l /home/mwc/Coding/code-crispies... +[14:00:52] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:00:53] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:53] audit Bash → grep -n "validate\|Validate" /home/m... +[14:00:55] audit Bash → ls -la /home/mwc/Coding/code-crispie... +[14:00:56] audit Bash → head -2000 /home/mwc/Coding/code-cri... +[14:00:57] audit Bash → find /home/mwc/Coding/code-crispies/... +[14:00:58] audit Bash → ls /home/mwc/Coding/code-crispies/.w... +[14:00:58] audit Bash → wc -l /home/mwc/Coding/code-crispies... +[14:00:59] audit Grep → ^import |^export |^from " +[14:01:00] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:01:01] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:01:02] audit Bash → grep -n "^function \|^export functio... +[14:01:02] audit Bash → grep -n "loadModuleFromUrl\|addCusto... +[14:01:05] audit Bash → grep -n "TODO\|FIXME\|HACK\|BUG\|XXX... +[14:01:05] audit Bash → ls /home/mwc/Coding/code-crispies/.w... +[14:01:07] audit Bash → grep -n "loadModules\|setModules\|ad... +[14:01:07] audit Bash → grep -n "try \|catch \|if (!.*) \|if... +[14:01:09] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:01:10] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:01:12] audit Bash → cd /home/mwc/Coding/code-crispies/.w... +[14:01:14] audit Bash → grep -A 5 "case \"" /home/mwc/Coding... +[14:01:14] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:01:16] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:01:18] audit Bash → head -100 /home/mwc/Coding/code-cris... +[14:02:04] audit Bash → cd /home/mwc/Coding/code-crispies/.w... +[14:02:05] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:02:06] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:02:12] audit Bash → npm install 2>&1 | tail -5 +[14:02:18] audit Bash → cd /home/mwc/Coding/code-crispies &&... +[14:02:21] audit Bash → cd /home/mwc/Coding/code-crispies &&... +[14:02:22] audit Bash → cd /home/mwc/Coding/code-crispies &&... +[14:02:42] audit Bash → cd /home/mwc/Coding/code-crispies &&... +[14:02:43] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:02:43] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:03:17] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:03:17] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:03:18] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:03:21] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:03:22] audit Read → /home/mwc/Coding/code-crispies/.wave... +[14:03:26] audit Bash → cd /home/mwc/Coding/code-crispies &&... +[14:03:28] audit Bash → cd /home/mwc/Coding/code-crispies &&... +[14:03:32] audit Bash → ls /home/mwc/Coding/code-crispies/.w... +[14:05:44] audit Write → /home/mwc/Coding/code-crispies/.wav... +[14:05:51] audit Processing results +[14:05:51] audit validating contract +[14:05:51] ✓ audit completed (362.6s, 52.1k in / 9.9k out) +[14:05:51] ├─ artifact: .wave/output/dx-audit-report.md (written) +[14:05:51] └─ contract: non_empty_file ✓ valid +2026/03/28 14:05:51 [retro] quantitative retrospective saved for run audit-dx-20260328-135948-267b + + ✓ Pipeline 'audit-dx' completed successfully (362.8s, 62.0k tokens) + + 1 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-dx-20260328-135948-267b/audit/.wave/output/dx-audit-report.md + + diff --git a/.wave/logs/audit-pedagogy-20260328-175313-79a5.log b/.wave/logs/audit-pedagogy-20260328-175313-79a5.log new file mode 100644 index 0000000..e2d95ee --- /dev/null +++ b/.wave/logs/audit-pedagogy-20260328-175313-79a5.log @@ -0,0 +1,144 @@ +[17:53:13] → scan-lessons (navigator) [claude-haiku via claude] +[17:53:13] scan-lessons Executing agent +[17:53:19] scan-lessons Glob → lessons/**/*.json +[17:53:20] scan-lessons Bash → ls -la /home/mwc/Coding/code-crispie... +[17:53:23] scan-lessons Glob → **/*.json +[17:53:27] scan-lessons Glob → lessons/**/*.json +[17:53:32] scan-lessons Bash → ls -la /home/mwc/Coding/code-crispie... +[17:53:33] scan-lessons Glob → **/lessons/**/*.json +[17:53:53] scan-lessons Agent → +[17:53:57] scan-lessons Agent → +[17:54:03] scan-lessons Agent → +[17:54:07] scan-lessons Agent → +[17:54:11] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:11] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:12] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:13] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:13] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:14] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:14] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:15] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:17] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:18] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:18] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:18] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:19] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:21] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:21] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:26] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:26] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[17:54:27] scan-lessons Grep → "solution" +[18:04:37] scan-lessons Read → /home/mwc/.claude/projects/-home-mwc... +[18:04:39] scan-lessons Read → /home/mwc/.claude/projects/-home-mwc... +[18:04:40] scan-lessons Read → /home/mwc/.claude/projects/-home-mwc... +[18:09:51] scan-lessons Read → /home/mwc/.claude/projects/-home-mwc... +[18:09:59] scan-lessons Read → /home/mwc/.claude/projects/-home-mwc... +[18:10:08] scan-lessons Bash → cat /home/mwc/.claude/projects/-home... +[18:10:25] scan-lessons Bash → python3 << 'PYEOF' +import json, re, ... +[18:11:15] scan-lessons Bash → python3 << 'PYEOF' +import json, re, ... +[18:11:49] scan-lessons Bash → python3 << 'PYEOF' +import json + +path... +[18:12:17] scan-lessons Bash → python3 << 'PYEOF' +import json, re + +... +[18:12:31] scan-lessons Bash → python3 << 'PYEOF' +import json + +path... +[18:12:37] scan-lessons Processing results +[18:12:37] scan-lessons validating contract +[18:12:37] ✓ scan-lessons completed (1164.0s, 122.1k in / 14.1k out) +[18:12:37] ├─ artifact: .wave/output/lesson-inventory.json (written) +[18:12:37] └─ contract: lesson-inventory.schema.json ⚠ soft failure +[18:12:37] → pedagogy-audit (pedagogy-auditor) [claude-opus via claude] +[18:12:37] pedagogy-audit Executing agent +[18:12:42] pedagogy-audit Bash → ls .wave/artifacts/lessons/ +[18:12:42] pedagogy-audit Bash → ls .wave/output/ 2>/dev/null || echo... +[18:12:45] pedagogy-audit Bash → file .wave/artifacts/lessons && wc -... +[18:12:48] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[18:12:51] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[18:12:56] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[18:13:01] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[18:13:02] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[18:13:03] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[18:13:05] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[18:13:49] pedagogy-audit ToolSearch → select:TodoWrite +[18:13:54] pedagogy-audit TodoWrite → Write pedagogy-report.json with... +[18:18:19] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[18:18:31] pedagogy-audit TodoWrite → Write pedagogy-report.md with h... +[18:19:55] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[18:20:01] pedagogy-audit TodoWrite → 3/3 tasks +[18:20:20] pedagogy-audit Processing results +[18:20:20] ✓ pedagogy-audit completed (462.8s, 154.1k in / 26.7k out) +[18:20:20] ├─ artifact: .wave/output/pedagogy-report.md (written) +[18:20:20] └─ artifact: .wave/output/pedagogy-report.json (written) +[18:20:20] → improvement-plan (planner) [claude-haiku via claude] +[18:20:20] improvement-plan Executing agent +[18:20:30] improvement-plan Agent → +[18:20:35] improvement-plan Agent → +[18:20:38] improvement-plan Bash → find /home/mwc/Coding/code-crispies ... +[18:20:38] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[18:20:40] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[18:20:40] improvement-plan Glob → /home/mwc/Coding/code-crispies/lesso... +[18:20:41] improvement-plan Bash → pwd && ls -la +[18:20:42] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[18:20:42] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[18:20:44] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[18:20:46] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:46] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:47] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:48] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:48] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:49] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[18:20:52] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[18:20:53] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:53] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:53] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:54] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:58] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:58] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:59] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:20:59] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:21:00] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:21:03] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:21:03] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:21:04] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:21:26] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[18:21:55] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:21:55] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:21:56] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:22:02] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:22:03] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:22:04] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:22:09] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:22:10] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:22:10] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[18:22:17] improvement-plan Read → /home/mwc/Coding/code-crispies/src/h... +[18:22:19] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[18:22:24] improvement-plan ToolSearch → select:TodoWrite +[18:22:29] improvement-plan TodoWrite → Write improvement-plan.json wit... +[18:26:22] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[18:26:29] improvement-plan TodoWrite → Write improvement-plan.md as hu... +[18:27:51] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[18:27:58] improvement-plan TodoWrite → 4/4 tasks +[18:28:08] improvement-plan Processing results +[18:28:08] ✓ improvement-plan completed (468.6s, 125.0k in / 22.6k out) +[18:28:08] ├─ artifact: .wave/output/improvement-plan.md (written) +[18:28:08] └─ artifact: .wave/output/improvement-plan.json (written) +2026/03/28 18:28:08 [retro] quantitative retrospective saved for run audit-pedagogy-20260328-175313-79a5 + + ✓ Pipeline 'audit-pedagogy' completed successfully (2095.6s, 464.5k tokens) + + 5 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-175313-79a5/scan-lessons/.wave/output/lesson-inventory.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-175313-79a5/pedagogy-audit/.wave/output/pedagogy-report.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-175313-79a5/pedagogy-audit/.wave/output/pedagogy-report.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-175313-79a5/improvement-plan/.wave/output/improvement-plan.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-175313-79a5/improvement-plan/.wave/output/improvement-plan.json + + diff --git a/.wave/logs/audit-pedagogy-20260328-194243-c189.log b/.wave/logs/audit-pedagogy-20260328-194243-c189.log new file mode 100644 index 0000000..41352e3 --- /dev/null +++ b/.wave/logs/audit-pedagogy-20260328-194243-c189.log @@ -0,0 +1,136 @@ +[19:42:43] → scan-lessons (navigator) [claude-haiku via claude] +[19:42:43] scan-lessons Executing agent +[19:42:50] scan-lessons Glob → lessons/**/*.json +[19:42:50] scan-lessons ToolSearch → select:TodoWrite +[19:42:56] scan-lessons Bash → ls /home/mwc/Coding/code-crispies/le... +[19:42:56] scan-lessons Glob → **/lessons/**/*.json +[19:43:06] scan-lessons TodoWrite → Read all English lesson JSON files +[19:43:12] scan-lessons Agent → +[19:43:14] scan-lessons Agent → +[19:43:17] scan-lessons Agent → +[19:43:19] scan-lessons Agent → +[19:43:21] scan-lessons Agent → +[19:43:23] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:43:23] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:43:24] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:43:24] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:43:25] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:43:25] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:00] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:01] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:02] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:11] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:15] scan-lessons Read → /home/mwc/.claude/projects/-home-mwc... +[19:47:22] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:23] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:23] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:27] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:27] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:28] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[19:47:49] scan-lessons TodoWrite → Generate lesson inventory via s... +[19:47:54] scan-lessons Bash → mkdir -p /home/mwc/Coding/code-crisp... +[19:48:16] scan-lessons Write → /home/mwc/Coding/code-crispies/.wav... +[19:48:20] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[19:48:38] scan-lessons Bash → node -e "const d=JSON.parse(require(... +[19:48:59] scan-lessons TodoWrite → Verify output quality and compl... +[19:49:11] scan-lessons Bash → node -e " +const d=JSON.parse(require... +[19:49:20] scan-lessons Bash → wc -c /home/mwc/Coding/code-crispies... +[19:49:26] scan-lessons TodoWrite → 3/3 tasks +[19:49:37] scan-lessons Processing results +[19:49:37] scan-lessons validating contract +[19:49:37] ✓ scan-lessons completed (414.0s, 198.4k in / 8.1k out) +[19:49:37] ├─ artifact: .wave/output/lesson-inventory.json (written) +[19:49:37] └─ contract: lesson-inventory.schema.json ⚠ soft failure +[19:49:37] → pedagogy-audit (pedagogy-auditor) [claude-opus via claude] +[19:49:37] pedagogy-audit Executing agent +[19:49:43] pedagogy-audit Bash → ls .wave/artifacts/lessons/ +[19:49:44] pedagogy-audit Bash → ls .wave/output/ 2>/dev/null || echo... +[19:49:48] pedagogy-audit Bash → file .wave/artifacts/lessons && wc -... +[19:49:51] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:49:55] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:01] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:02] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:04] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:04] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:05] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:06] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:07] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:08] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:09] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:50:09] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[19:51:25] pedagogy-audit ToolSearch → select:TodoWrite +[19:51:32] pedagogy-audit TodoWrite → Write JSON audit report to .wav... +[19:55:57] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[19:56:03] pedagogy-audit TodoWrite → Write markdown summary to .wave... +[19:57:30] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[19:57:36] pedagogy-audit TodoWrite → 3/3 tasks +[19:57:46] pedagogy-audit Processing results +[19:57:46] ✓ pedagogy-audit completed (488.8s, 223.6k in / 29.9k out) +[19:57:46] ├─ artifact: .wave/output/pedagogy-report.md (written) +[19:57:46] └─ artifact: .wave/output/pedagogy-report.json (written) +[19:57:46] → improvement-plan (planner) [claude-haiku via claude] +[19:57:46] improvement-plan Executing agent +[19:57:57] improvement-plan Agent → +[19:57:59] improvement-plan Agent → +[19:58:01] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[19:58:01] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[19:58:02] improvement-plan Glob → **/lessons/**/*.json +[19:58:04] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[19:58:04] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[19:58:05] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[19:58:06] improvement-plan Bash → pwd && ls -la +[19:58:07] improvement-plan Bash → ls -R /home/mwc/Coding/code-crispies... +[19:58:07] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[19:58:14] improvement-plan Bash → find /home/mwc/Coding/code-crispies ... +[19:58:16] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[19:58:17] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[19:58:17] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[19:58:18] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:19] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:20] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[19:58:22] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:22] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:23] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:28] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:58:29] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:30] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:30] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:34] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:34] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:58:35] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[19:59:49] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:00:09] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:09] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:09] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:15] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:16] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:23] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:23] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:24] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:24] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:32] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:00:33] improvement-plan Read → /home/mwc/Coding/code-crispies/schem... +[20:00:53] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[20:00:58] improvement-plan ToolSearch → select:TodoWrite +[20:01:04] improvement-plan TodoWrite → Write improvement-plan.json wit... +[20:04:22] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[20:04:28] improvement-plan TodoWrite → Write improvement-plan.md as hu... +[20:06:04] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[20:06:11] improvement-plan TodoWrite → 2/2 tasks +[20:06:22] improvement-plan Processing results +[20:06:22] ✓ improvement-plan completed (515.7s, 122.8k in / 21.3k out) +[20:06:22] ├─ artifact: .wave/output/improvement-plan.md (written) +[20:06:22] └─ artifact: .wave/output/improvement-plan.json (written) +2026/03/28 20:06:22 [retro] quantitative retrospective saved for run audit-pedagogy-20260328-194243-c189 + + ✓ Pipeline 'audit-pedagogy' completed successfully (1418.7s, 604.1k tokens) + + 5 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-194243-c189/scan-lessons/.wave/output/lesson-inventory.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-194243-c189/pedagogy-audit/.wave/output/pedagogy-report.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-194243-c189/pedagogy-audit/.wave/output/pedagogy-report.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-194243-c189/improvement-plan/.wave/output/improvement-plan.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-194243-c189/improvement-plan/.wave/output/improvement-plan.json + + diff --git a/.wave/logs/audit-pedagogy-20260328-213312-b6f8.log b/.wave/logs/audit-pedagogy-20260328-213312-b6f8.log new file mode 100644 index 0000000..71991fd --- /dev/null +++ b/.wave/logs/audit-pedagogy-20260328-213312-b6f8.log @@ -0,0 +1,231 @@ + warning: input looks like free text — consider using: impl-feature, impl-hotfix, impl-improve +[21:33:12] → scan-lessons (navigator) [claude-haiku via claude] +[21:33:12] scan-lessons Executing agent +[21:33:22] scan-lessons Glob → lessons/**/*.json +[21:33:22] scan-lessons ToolSearch → select:TodoWrite +[21:33:29] scan-lessons Glob → **/lessons/**/*.json +[21:33:31] scan-lessons Bash → ls /home/mwc/Coding/code-crispies/le... +[21:33:39] scan-lessons TodoWrite → Read all English lesson JSON fi... +[21:33:45] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:45] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:46] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:47] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:47] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:48] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:48] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:57] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:58] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:33:59] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:00] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:00] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:01] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:01] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:10] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:11] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:12] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:13] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:21] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:21] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:22] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:23] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:24] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:24] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:33] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:33] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:34] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:35] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:36] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:49] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:34:57] scan-lessons TodoWrite → Analyze each exercise and write... +[21:35:05] scan-lessons Bash → ls -d /home/mwc/Coding/code-crispies... +[21:35:42] scan-lessons Bash → mkdir -p /home/mwc/Coding/code-crisp... +[21:36:00] scan-lessons Agent → +[21:36:07] scan-lessons Bash → ls /home/mwc/Coding/code-crispies/le... +[21:36:07] scan-lessons Bash → ls /home/mwc/Coding/code-crispies/le... +[21:36:12] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:12] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:13] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:14] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:15] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:15] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:26] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:26] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:27] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:27] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:28] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:29] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:45] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:46] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:47] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:47] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:48] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:48] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:49] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:54] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:54] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:55] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:56] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:36:57] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:03] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:03] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:04] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:05] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:05] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:06] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:12] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:37:17] scan-lessons Bash → mkdir -p /home/mwc/Coding/code-crisp... +[21:38:01] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[21:38:08] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[21:38:18] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[21:38:28] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[21:38:39] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[21:38:45] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[21:38:54] scan-lessons Bash → wc -l /home/mwc/Coding/code-crispies... +[21:39:10] scan-lessons Bash → python3 -c "import json; data = json... +[21:39:16] scan-lessons Bash → python3 -c " +import json +data = json... +[21:39:22] scan-lessons TodoWrite → 2/2 tasks +[21:39:28] scan-lessons Processing results +[21:39:28] scan-lessons validating contract +[21:39:28] ✓ scan-lessons completed (375.4s, 199.3k in / 5.1k out) +[21:39:28] ├─ artifact: .wave/output/lesson-inventory.json (written) +[21:39:28] └─ contract: lesson-inventory.schema.json ⚠ soft failure +[21:39:28] → pedagogy-audit (pedagogy-auditor) [claude-opus via claude] +[21:39:28] pedagogy-audit Executing agent +[21:39:34] pedagogy-audit Bash → ls .wave/artifacts/lessons/ +[21:39:35] pedagogy-audit Bash → ls .wave/output/ 2>/dev/null || echo... +[21:39:39] pedagogy-audit Bash → file .wave/artifacts/lessons && wc -... +[21:39:43] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[21:39:47] pedagogy-audit Bash → ls .wave/output/ +[21:39:50] pedagogy-audit Bash → ls .wave/artifacts/ +[21:39:55] pedagogy-audit Glob → **/*.json +[21:39:55] pedagogy-audit Glob → lessons/**/*.json +[21:39:59] pedagogy-audit Glob → **/*.json +[21:40:03] pedagogy-audit Glob → lessons/*.json +[21:40:03] pedagogy-audit Glob → src/lessons/*.json +[21:40:09] pedagogy-audit Bash → find /home/mwc/Coding/code-crispies ... +[21:40:13] pedagogy-audit Bash → find /home/mwc/Coding/code-crispies/... +[21:40:19] pedagogy-audit ToolSearch → select:TodoWrite +[21:40:28] pedagogy-audit TodoWrite → Read all 41 lesson module JSON ... +[21:40:36] pedagogy-audit Agent → +[21:40:37] pedagogy-audit Agent → +[21:40:39] pedagogy-audit Agent → +[21:40:41] pedagogy-audit Agent → +[21:40:43] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:44] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:44] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:44] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:45] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:46] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:46] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:47] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:50] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:40:50] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:47:51] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:47:52] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:47:53] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:47:54] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:47:54] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:02] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:03] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:03] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:04] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:04] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:17] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:18] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:19] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:19] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:19] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:26] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:27] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:27] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:27] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:48:28] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:49:54] pedagogy-audit TodoWrite → Analyze each module for Bloom's... +[21:49:58] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:49:59] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:50:00] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:50:00] pedagogy-audit Read → /home/mwc/Coding/code-crispies/lesso... +[21:50:09] pedagogy-audit TodoWrite → Write pedagogy-report.json +[21:54:18] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[21:54:25] pedagogy-audit TodoWrite → Write pedagogy-report.md +[21:55:59] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[21:56:06] pedagogy-audit TodoWrite → 4/4 tasks +[21:56:16] pedagogy-audit Processing results +[21:56:16] ✓ pedagogy-audit completed (1008.3s, 258.9k in / 32.5k out) +[21:56:16] ├─ artifact: .wave/output/pedagogy-report.md (written) +[21:56:16] └─ artifact: .wave/output/pedagogy-report.json (written) +[21:56:16] → improvement-plan (planner) [claude-haiku via claude] +[21:56:16] improvement-plan Executing agent +[21:56:24] improvement-plan Agent → +[21:56:26] improvement-plan Agent → +[21:56:28] improvement-plan Glob → lessons/**/*.json +[21:56:28] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[21:56:29] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[21:56:30] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[21:56:30] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[21:56:31] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[21:56:32] improvement-plan Bash → find /home/mwc/Coding/code-crispies ... +[21:56:33] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[21:56:35] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[21:56:36] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[21:56:37] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:56:38] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:56:38] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:39] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:39] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:42] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:43] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:43] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:46] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:47] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:50] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:50] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:51] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:53] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:54] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:55] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:57] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:58] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:56:58] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:57:02] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:57:02] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:57:03] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:57:44] improvement-plan Glob → .wave/artifacts/audit/**/* +[21:57:47] improvement-plan Glob → **/.wave/artifacts/audit/**/* +[21:57:52] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[21:57:56] improvement-plan Bash → find /home/mwc/Coding/code-crispies/... +[21:57:59] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:58:00] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:58:06] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:07] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:08] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:13] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:14] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:15] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:15] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:21] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[21:58:27] improvement-plan Bash → ls /home/mwc/Coding/code-crispies/.w... +[21:58:31] improvement-plan ToolSearch → select:TodoWrite +[21:58:36] improvement-plan TodoWrite → Write improvement-plan.json wit... +[22:01:46] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[22:01:52] improvement-plan TodoWrite → Write improvement-plan.md as hu... +[22:03:42] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[22:03:48] improvement-plan TodoWrite → 2/2 tasks +[22:03:59] improvement-plan Processing results +[22:03:59] ✓ improvement-plan completed (463.2s, 121.1k in / 20.5k out) +[22:03:59] ├─ artifact: .wave/output/improvement-plan.md (written) +[22:03:59] └─ artifact: .wave/output/improvement-plan.json (written) +2026/03/28 22:03:59 [retro] quantitative retrospective saved for run audit-pedagogy-20260328-213312-b6f8 + + ✓ Pipeline 'audit-pedagogy' completed successfully (1847.1s, 637.3k tokens) + + 5 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-213312-b6f8/scan-lessons/.wave/output/lesson-inventory.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-213312-b6f8/pedagogy-audit/.wave/output/pedagogy-report.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-213312-b6f8/pedagogy-audit/.wave/output/pedagogy-report.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-213312-b6f8/improvement-plan/.wave/output/improvement-plan.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260328-213312-b6f8/improvement-plan/.wave/output/improvement-plan.json + + diff --git a/.wave/logs/audit-pedagogy-20260329-001752-e77c.log b/.wave/logs/audit-pedagogy-20260329-001752-e77c.log new file mode 100644 index 0000000..30078d7 --- /dev/null +++ b/.wave/logs/audit-pedagogy-20260329-001752-e77c.log @@ -0,0 +1,115 @@ + warning: input looks like free text — consider using: impl-feature, impl-hotfix, impl-improve +[00:17:52] → scan-lessons (navigator) [claude-haiku via claude] +[00:17:52] scan-lessons Executing agent +[00:17:58] scan-lessons Glob → lessons/**/*.json +[00:17:59] scan-lessons ToolSearch → select:TodoWrite +[00:18:03] scan-lessons Bash → ls /home/mwc/Coding/code-crispies/le... +[00:18:04] scan-lessons Glob → **/*.json +[00:18:22] scan-lessons TodoWrite → Read sample lesson file to unde... +[00:18:22] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:18:53] scan-lessons TodoWrite → Read and analyze all 41 English... +[00:19:10] scan-lessons Agent → +[00:19:15] scan-lessons Agent → +[00:19:20] scan-lessons Agent → +[00:19:25] scan-lessons Agent → +[00:19:31] scan-lessons Agent → +[00:19:34] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:19:35] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:19:35] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:19:36] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:19:36] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:19:37] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:19:37] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:19:38] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[00:24:06] scan-lessons Read → /home/mwc/.claude/projects/-home-mwc... +[00:24:35] scan-lessons TodoWrite → Compile results and write lesso... +[00:24:37] scan-lessons Bash → mkdir -p /home/mwc/Coding/code-crisp... +[00:25:20] scan-lessons Write → /home/mwc/Coding/code-crispies/.wav... +[00:25:26] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:25:33] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:25:43] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:26:27] scan-lessons Read → /home/mwc/Coding/code-crispies/.wave... +[00:26:53] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:27:21] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:27:27] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:28:24] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:28:34] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:28:38] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:28:47] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:30:09] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:31:26] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:31:31] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:31:39] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:32:02] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[00:32:06] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:32:19] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:32:24] scan-lessons Bash → ls -la /home/mwc/Coding/code-crispie... +[00:32:33] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:32:39] scan-lessons Bash → cd /home/mwc/Coding/code-crispies &&... +[00:32:44] scan-lessons TodoWrite → 3/3 tasks +[00:32:54] scan-lessons Processing results +[00:32:54] scan-lessons validating contract +[00:32:54] ✓ scan-lessons completed (902.2s, 193.8k in / 39.0k out) +[00:32:54] ├─ artifact: .wave/output/lesson-inventory.json (written) +[00:32:54] └─ contract: lesson-inventory.schema.json ⚠ soft failure +[00:32:54] → pedagogy-audit (pedagogy-auditor) [claude-opus via claude] +[00:32:54] pedagogy-audit Executing agent +[00:33:01] pedagogy-audit Bash → ls .wave/artifacts/lessons/ +[00:33:01] pedagogy-audit Bash → ls .wave/output/ 2>/dev/null || echo... +[00:33:05] pedagogy-audit Bash → file .wave/artifacts/lessons && wc -... +[00:33:10] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[00:33:14] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[00:33:23] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[00:33:23] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[00:33:24] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[00:33:25] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[00:33:25] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[00:34:18] pedagogy-audit Bash → mkdir -p .wave/output +[00:34:22] pedagogy-audit ToolSearch → select:TodoWrite +[00:34:27] pedagogy-audit TodoWrite → Write pedagogy-report.json with... +[00:38:55] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[00:39:04] pedagogy-audit TodoWrite → Write pedagogy-report.md with h... +[00:40:39] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[00:40:44] pedagogy-audit TodoWrite → 3/3 tasks +[00:40:53] pedagogy-audit Processing results +[00:40:53] ✓ pedagogy-audit completed (478.6s, 145.6k in / 26.8k out) +[00:40:53] ├─ artifact: .wave/output/pedagogy-report.md (written) +[00:40:53] └─ artifact: .wave/output/pedagogy-report.json (written) +[00:40:53] → improvement-plan (planner) [claude-haiku via claude] +[00:40:53] improvement-plan Executing agent +[00:40:59] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[00:41:00] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[00:41:05] improvement-plan Bash → file /home/mwc/Coding/code-crispies/... +[00:41:09] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[00:41:28] improvement-plan Agent → +[00:41:29] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[00:41:30] improvement-plan Glob → /home/mwc/Coding/code-crispies/lesso... +[00:41:50] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[00:41:50] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[00:41:51] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[00:41:57] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[00:41:58] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[00:42:03] improvement-plan Read → /home/mwc/Coding/code-crispies/src/h... +[00:42:03] improvement-plan Read → /home/mwc/Coding/code-crispies/lesso... +[00:42:28] improvement-plan ToolSearch → select:TodoWrite +[00:42:33] improvement-plan TodoWrite → Write improvement-plan.json wit... +[00:46:43] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[00:46:55] improvement-plan TodoWrite → Write improvement-plan.md as hu... +[00:48:38] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[00:48:44] improvement-plan TodoWrite → 4/4 tasks +[00:48:56] improvement-plan Processing results +[00:48:56] ✓ improvement-plan completed (483.6s, 123.8k in / 25.9k out) +[00:48:56] ├─ artifact: .wave/output/improvement-plan.md (written) +[00:48:56] └─ artifact: .wave/output/improvement-plan.json (written) +2026/03/29 00:48:56 [retro] quantitative retrospective saved for run audit-pedagogy-20260329-001752-e77c + + ✓ Pipeline 'audit-pedagogy' completed successfully (1864.7s, 554.9k tokens) + + 5 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-001752-e77c/scan-lessons/.wave/output/lesson-inventory.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-001752-e77c/pedagogy-audit/.wave/output/pedagogy-report.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-001752-e77c/pedagogy-audit/.wave/output/pedagogy-report.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-001752-e77c/improvement-plan/.wave/output/improvement-plan.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-001752-e77c/improvement-plan/.wave/output/improvement-plan.json + + diff --git a/.wave/logs/audit-pedagogy-20260329-210909-22e5.log b/.wave/logs/audit-pedagogy-20260329-210909-22e5.log new file mode 100644 index 0000000..50794d7 --- /dev/null +++ b/.wave/logs/audit-pedagogy-20260329-210909-22e5.log @@ -0,0 +1,103 @@ + warning: input looks like free text — consider using: impl-feature, impl-hotfix, impl-improve +[21:09:10] → scan-lessons (navigator) [claude-haiku via claude] +[21:09:10] scan-lessons Executing agent +[21:09:18] scan-lessons Glob → lessons/**/*.json +[21:09:18] scan-lessons ToolSearch → select:TodoWrite +[21:09:26] scan-lessons Glob → **/lessons/**/*.json +[21:09:26] scan-lessons Glob → **/lessons/*.json +[21:09:27] scan-lessons Bash → ls -la /home/mwc/Coding/code-crispie... +[21:09:59] scan-lessons TodoWrite → Read all English lesson JSON files +[21:14:22] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:22] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:23] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:24] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:24] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:25] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:25] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:39] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:40] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:40] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:42] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:42] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:43] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:44] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:14:44] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:32] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:33] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:34] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:34] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:37] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:37] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:53] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:15:53] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:17:23] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:17:38] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:17:38] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:17:43] scan-lessons Read → /home/mwc/Coding/code-crispies/lesso... +[21:17:48] scan-lessons TodoWrite → Analyze each exercise and build... +[21:17:57] scan-lessons Bash → mkdir -p /home/mwc/Coding/code-crisp... +[21:25:07] scan-lessons Write → /home/mwc/Coding/code-crispies/.wav... +[21:25:24] scan-lessons Bash → python3 -c "import json; data = json... +[21:25:43] scan-lessons TodoWrite → 3/3 tasks +[21:25:57] scan-lessons Edit → /home/mwc/Coding/code-crispies/.wave... +[21:26:09] scan-lessons Processing results +[21:26:09] scan-lessons validating contract +[21:26:09] ✓ scan-lessons completed (1018.4s, 391.2k in / 43.1k out) +[21:26:09] ├─ artifact: .wave/output/lesson-inventory.json (written) +[21:26:09] └─ contract: lesson-inventory.schema.json ⚠ soft failure +[21:26:09] → pedagogy-audit (pedagogy-auditor) [claude-opus via claude] +[21:26:09] pedagogy-audit Executing agent +[21:26:22] pedagogy-audit Bash → ls .wave/artifacts/lessons/ +[21:26:22] pedagogy-audit Bash → ls .wave/output/ 2>/dev/null || echo... +[21:26:30] pedagogy-audit Bash → file .wave/artifacts/lessons && wc -... +[21:26:39] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[21:27:46] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[21:28:34] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[21:32:34] pedagogy-audit Read → /home/mwc/Coding/code-crispies/.wave... +[21:39:02] pedagogy-audit ToolSearch → select:TodoWrite +[21:39:09] pedagogy-audit TodoWrite → Write pedagogy-report.json with... +[21:45:02] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[21:45:09] pedagogy-audit TodoWrite → Write pedagogy-report.md with h... +[21:46:37] pedagogy-audit Write → /home/mwc/Coding/code-crispies/.wav... +[21:46:43] pedagogy-audit TodoWrite → 3/3 tasks +[21:46:55] pedagogy-audit Processing results +[21:46:55] ✓ pedagogy-audit completed (1246.0s, 124.2k in / 34.8k out) +[21:46:55] ├─ artifact: .wave/output/pedagogy-report.md (written) +[21:46:55] └─ artifact: .wave/output/pedagogy-report.json (written) +[21:46:55] → improvement-plan (planner) [claude-haiku via claude] +[21:46:55] improvement-plan Executing agent +[21:47:02] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[21:47:03] improvement-plan Bash → ls -la /home/mwc/Coding/code-crispie... +[21:47:06] improvement-plan Bash → file /home/mwc/Coding/code-crispies/... +[21:47:09] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:13] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:17] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:20] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:23] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:27] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:31] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:34] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:39] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:47:45] improvement-plan Read → /home/mwc/Coding/code-crispies/.wave... +[21:48:08] improvement-plan ToolSearch → select:TodoWrite +[21:48:41] improvement-plan TodoWrite → Write improvement-plan.json wit... +[21:52:56] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[21:53:03] improvement-plan TodoWrite → Write improvement-plan.md as hu... +[21:54:52] improvement-plan Write → /home/mwc/Coding/code-crispies/.wav... +[21:54:57] improvement-plan TodoWrite → 3/3 tasks +[21:55:07] improvement-plan Processing results +[21:55:07] ✓ improvement-plan completed (492.6s, 79.8k in / 26.8k out) +[21:55:07] ├─ artifact: .wave/output/improvement-plan.md (written) +[21:55:07] └─ artifact: .wave/output/improvement-plan.json (written) +2026/03/29 21:55:07 [retro] quantitative retrospective saved for run audit-pedagogy-20260329-210909-22e5 + + ✓ Pipeline 'audit-pedagogy' completed successfully (2757.8s, 700.1k tokens) + + 5 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-210909-22e5/scan-lessons/.wave/output/lesson-inventory.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-210909-22e5/pedagogy-audit/.wave/output/pedagogy-report.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-210909-22e5/pedagogy-audit/.wave/output/pedagogy-report.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-210909-22e5/improvement-plan/.wave/output/improvement-plan.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/audit-pedagogy-20260329-210909-22e5/improvement-plan/.wave/output/improvement-plan.json + + diff --git a/.wave/logs/audit-security-20260328-152711-3121.log b/.wave/logs/audit-security-20260328-152711-3121.log new file mode 100644 index 0000000..4c62911 --- /dev/null +++ b/.wave/logs/audit-security-20260328-152711-3121.log @@ -0,0 +1,225 @@ +[15:27:11] → scan (navigator) [claude-haiku via claude] +[15:27:11] scan Executing agent +[15:27:29] scan Agent → +[15:27:31] scan Bash → find /home/mwc/Coding/code-crispies/... +[15:27:33] scan Bash → ls -la /home/mwc/Coding/code-crispie... +[15:27:36] scan Grep → innerHTML|outerHTML|insertAdjacentHTML +[15:27:37] scan Grep → document\.write +[15:27:38] scan Grep → createElement\s*\(\s*['\"]?script['\... +[15:27:38] scan Grep → postMessage +[15:27:46] scan Grep → srcdoc|iframe.*src|blob:|data: +[15:27:47] scan Grep → URL\.parse|URLSearchParams|location\... +[15:27:47] scan Grep → \$\{.*\}|template.*inject +[15:27:50] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:27:54] scan Bash → grep -n "iframe\|srcdoc\|contentWind... +[15:28:01] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:10] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:14] scan Grep → \.write\(|innerHTML.*=|\.innerHTML\s... +[15:28:14] scan Glob → src/**/*.js +[15:28:17] scan Bash → find /home/mwc/Coding/code-crispies/... +[15:28:22] scan Bash → grep -rn "addEventListener.*message\... +[15:28:24] scan Bash → grep -n "marked\|sanitize\|DOMPurify... +[15:28:27] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:30] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:33] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:35] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:38] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:42] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:45] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:47] scan Bash → grep -n "sectionContent\s*=" /home/m... +[15:28:50] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:55] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:28:57] scan Bash → grep -n "dataset\|getAttribute\|data... +[15:29:08] scan Bash → grep -n "window\.location\|location\... +[15:29:11] scan Read → /home/mwc/Coding/code-crispies/.wave... +[15:29:15] scan Bash → grep -rn "marked\." /home/mwc/Coding... +[15:29:18] scan Bash → grep -rn "script\|&1 +[14:03:11] implement Bash → npx prettier --write "lessons/50-js-... +[14:03:22] implement TodoWrite → Commit changes to worktree branch +[14:03:26] implement Bash → git status +[14:03:31] implement Bash → git log --oneline -5 +[14:03:37] implement Bash → git add \ + package.json \ + package... +[14:03:45] implement Bash → git commit -m "$(cat <<'EOF' +feat: a... +[14:03:52] implement TodoWrite → Write result output to .wave/ou... +[14:03:58] implement Bash → ls -d /home/mwc/Coding/code-crispies... +[14:04:17] implement Write → /home/mwc/Coding/code-crispies/.wav... +[14:04:25] implement TodoWrite → 14/14 tasks +[14:04:34] implement Processing results +[14:04:34] implement validating contract +[14:04:39] ✓ implement completed (1210.7s, 172.2k in / 38.0k out) +[14:04:39] ├─ artifact: .wave/output/result.md (written) +[14:04:39] └─ contract: test_suite ✓ valid +[14:04:39] → publish (craftsman) [claude-opus via claude] +[14:04:39] publish Executing agent +[14:04:46] publish Read → /home/mwc/Coding/code-crispies/.wave... +[14:04:47] publish Bash → git log --oneline main..HEAD +[14:04:53] publish Bash → git push -u origin HEAD +[14:05:00] publish Bash → COMMIT_SUBJECT=$(git log --format=%s... +[14:05:05] publish Bash → git remote -v +[14:05:09] publish Bash → git remote -v +[14:05:13] publish Bash → cat .git/config +[14:05:16] publish Bash → cat .git +[14:05:20] publish Read → /home/mwc/Coding/code-crispies/.git/... +[14:05:34] publish Bash → curl -s "https://git.librete.ch/api/... +[14:05:35] publish Bash → gh auth status 2>&1; echo "---"; gh ... +[14:05:42] publish Bash → git push -u github HEAD +[14:05:52] publish Bash → COMMIT_SUBJECT=$(git log --format=%s... +[14:05:59] publish Bash → ls .wave/output/ 2>/dev/null || echo... +[14:06:07] publish Write → /home/mwc/Coding/code-crispies/.wav... +[14:06:12] publish Processing results +[14:06:12] publish validating contract +[14:06:12] ✓ publish completed (92.6s, 16.1k in / 2.3k out) +[14:06:12] ├─ artifact: .wave/output/pr-result.json (written) +[14:06:12] └─ contract: pr-result.schema.json ✓ valid +2026/03/28 14:06:12 [retro] quantitative retrospective saved for run impl-feature-20260328-133515-7069 + + ✓ Pipeline 'impl-feature' completed successfully (1857.0s, 365.6k tokens) + + Outcomes + ✓ Branch: feat/impl-feature-20260328-133515-7069 (local only) + ✓ Pull Request: https://github.com/nextlevelshit/code-crispies/pull/3 + + 4 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-133515-7069/explore/.wave/output/exploration.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-133515-7069/plan/.wave/output/plan.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-133515-7069/__wt_feat-impl-feature-20260328-133515-7069/.wave/output/result.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-133515-7069/__wt_feat-impl-feature-20260328-133515-7069/.wave/output/pr-result.json + + Next Steps + → Review the pull request: Pull Request + https://github.com/nextlevelshit/code-crispies/pull/3 + + diff --git a/.wave/logs/impl-feature-20260328-200101-ecae.log b/.wave/logs/impl-feature-20260328-200101-ecae.log new file mode 100644 index 0000000..47e8f15 --- /dev/null +++ b/.wave/logs/impl-feature-20260328-200101-ecae.log @@ -0,0 +1,244 @@ +[20:01:01] → explore (navigator) [claude-haiku via claude] +[20:01:01] explore Executing agent +[20:01:11] explore Glob → **/*.{js,json} +[20:01:13] explore Read → /home/mwc/Coding/code-crispies/src/c... +[20:01:13] explore Read → /home/mwc/Coding/code-crispies/src/c... +[20:01:20] explore Glob → lessons/*.json +[20:01:22] explore Read → /home/mwc/Coding/code-crispies/src/i... +[20:01:22] explore Glob → src/**/*.js +[20:01:29] explore Bash → ls /home/mwc/Coding/code-crispies/le... +[20:01:30] explore Bash → ls /home/mwc/Coding/code-crispies/sr... +[20:01:30] explore Glob → schemas/*.json +[20:01:35] explore Bash → ls /home/mwc/Coding/code-crispies/le... +[20:01:36] explore Read → /home/mwc/Coding/code-crispies/lesso... +[20:01:37] explore Bash → ls /home/mwc/Coding/code-crispies/sr... +[20:01:37] explore Bash → ls /home/mwc/Coding/code-crispies/sc... +[20:01:42] explore Read → /home/mwc/Coding/code-crispies/schem... +[20:01:42] explore Read → /home/mwc/Coding/code-crispies/src/h... +[20:01:49] explore Read → /home/mwc/Coding/code-crispies/src/a... +[20:01:51] explore Glob → tests/**/*.{js,test.js} +[20:01:55] explore Glob → tests/**/* +[20:01:56] explore Read → /home/mwc/Coding/code-crispies/src/a... +[20:01:57] explore Read → /home/mwc/Coding/code-crispies/lesso... +[20:02:03] explore Grep → renderModuleList|section-header|sect... +[20:02:04] explore Read → /home/mwc/Coding/code-crispies/src/a... +[20:02:11] explore Read → /home/mwc/Coding/code-crispies/src/i... +[20:02:12] explore Grep → module-list|moduleList +[20:02:20] explore Read → /home/mwc/Coding/code-crispies/src/a... +[20:02:20] explore Read → /home/mwc/Coding/code-crispies/src/a... +[20:02:21] explore Read → /home/mwc/Coding/code-crispies/src/h... +[20:02:28] explore Bash → find /home/mwc/Coding/code-crispies/... +[20:02:28] explore Read → /home/mwc/Coding/code-crispies/src/i... +[20:02:30] explore Grep → markdown|javascript|mode.*js +[20:02:36] explore Read → /home/mwc/Coding/code-crispies/tests... +[20:02:37] explore Read → /home/mwc/Coding/code-crispies/tests... +[20:02:45] explore Grep → section-header|section-group|sidebar... +[20:02:45] explore Read → /home/mwc/Coding/code-crispies/src/a... +[20:02:53] explore Bash → mkdir -p /home/mwc/Coding/code-crisp... +[20:03:55] explore Write → /home/mwc/Coding/code-crispies/.wav... +[20:04:06] explore Processing results +[20:04:06] explore validating contract +[20:04:06] ✓ explore completed (184.2s, 96.7k in / 7.7k out) +[20:04:06] ├─ artifact: .wave/output/exploration.json (written) +[20:04:06] └─ contract: feature-exploration.schema.json ✓ valid +[20:04:06] → plan (planner) [claude-haiku via claude] +[20:04:06] plan Executing agent +[20:04:13] plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:04:13] plan Glob → .wave/artifacts/context/**/* +[20:04:26] plan Agent → +[20:04:27] plan Read → /home/mwc/Coding/code-crispies/src/c... +[20:04:28] plan Read → /home/mwc/Coding/code-crispies/src/h... +[20:04:31] plan Read → /home/mwc/Coding/code-crispies/src/i... +[20:04:31] plan Read → /home/mwc/Coding/code-crispies/src/i... +[20:04:32] plan Read → /home/mwc/Coding/code-crispies/src/a... +[20:04:34] plan Read → /home/mwc/Coding/code-crispies/src/i... +[20:04:34] plan Read → /home/mwc/Coding/code-crispies/src/m... +[20:04:35] plan Read → /home/mwc/Coding/code-crispies/schem... +[20:04:37] plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:04:37] plan Bash → cd /home/mwc/Coding/code-crispies &&... +[20:04:40] plan Bash → cd /home/mwc/Coding/code-crispies &&... +[20:04:41] plan Read → /home/mwc/Coding/code-crispies/src/i... +[20:04:43] plan Read → /home/mwc/Coding/code-crispies/src/i... +[20:04:43] plan Read → /home/mwc/Coding/code-crispies/src/i... +[20:04:46] plan Read → /home/mwc/Coding/code-crispies/src/i... +[20:04:47] plan Bash → cd /home/mwc/Coding/code-crispies &&... +[20:04:49] plan Bash → cd /home/mwc/Coding/code-crispies &&... +[20:04:51] plan Read → /home/mwc/Coding/code-crispies/src/a... +[20:04:54] plan Bash → cd /home/mwc/Coding/code-crispies &&... +[20:04:56] plan Bash → cd /home/mwc/Coding/code-crispies &&... +[20:06:03] plan Bash → ls /home/mwc/Coding/code-crispies/.w... +[20:07:12] plan Write → /home/mwc/Coding/code-crispies/.wav... +[20:07:19] plan Bash → python3 -c "import json; data = json... +[20:07:27] plan Processing results +[20:07:27] plan validating contract +[20:07:27] ✓ plan completed (201.1s, 26.2k in / 6.8k out) +[20:07:27] ├─ artifact: .wave/output/plan.json (written) +[20:07:27] └─ contract: feature-plan.schema.json ✓ valid +[20:07:27] → implement (craftsman) [claude-opus via claude] +[20:07:27] implement Executing agent +[20:07:33] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:33] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:41] implement Agent → +[20:07:43] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:44] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:45] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:48] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:48] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:52] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:53] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:53] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:57] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:57] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:00] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:01] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:06] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:06] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:07] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:33] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:34] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:35] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:36] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:42] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:43] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:50] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:50] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:59] implement Grep → modeConfig|updateEditorForMode|secti... +[20:09:00] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:01] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:02] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:02] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:09] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:10] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:11] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:18] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:19] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:19] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:09:27] implement Grep → nav-link\[data-section|data-section.... +[20:09:28] implement Grep → body\[data-section +[20:09:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:09:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:09:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:09:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:09:59] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:10:00] implement Bash → npm install @codemirror/lang-javascr... +[20:10:35] implement Write → /home/mwc/Coding/code-crispies/.wav... +[20:10:53] implement Write → /home/mwc/Coding/code-crispies/.wav... +[20:11:11] implement Write → /home/mwc/Coding/code-crispies/.wav... +[20:11:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:11:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:11:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:11:45] implement Grep → jsVariablesEN|jsDomEN|jsEventsEN +[20:11:50] implement Grep → jsVariablesEN|jsDomEN|jsEventsEN +[20:11:58] implement Grep → // Markdown|// Outro|// JavaScript +[20:12:03] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:12:10] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:12:14] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:12:21] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:12:24] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:12:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:12:33] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:12:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:12:47] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:12:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:05] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:31] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:48] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:13:55] implement Grep → ^const sectionContent|^};$ +[20:13:59] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:14:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:14:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:14:25] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:14:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:14:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:14:52] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:15:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:15:18] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:15:21] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:15:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:15:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:15:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:15:48] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:15:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:11] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:16:15] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:16:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:31] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:16:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:47] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:16:53] implement Grep → \.module-container|\.module-list +[20:17:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:11] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:36] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:17:59] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:18:04] implement Bash → npm run test 2>&1 +[20:18:16] implement Bash → npm run build 2>&1 | tail -10 +[20:22:34] implement Bash → git status +[20:22:35] implement Bash → git diff --stat +[20:22:41] implement Bash → git add package.json package-lock.js... +[20:22:50] implement Bash → git commit -m "$(cat <<'EOF' +feat: a... +[20:22:57] implement Bash → ls /home/mwc/Coding/code-crispies/.w... +[20:23:15] implement Write → /home/mwc/Coding/code-crispies/.wav... +[20:23:22] implement Processing results +[20:23:22] implement validating contract +[20:23:29] ✓ implement completed (955.1s, 173.8k in / 34.5k out) +[20:23:29] ├─ artifact: .wave/output/result.md (written) +[20:23:29] └─ contract: test_suite ✓ valid +[20:23:29] → publish (craftsman) [claude-opus via claude] +[20:23:29] publish Executing agent +[20:23:36] publish Read → /home/mwc/Coding/code-crispies/.wave... +[20:23:37] publish Bash → git log --format=%s -1 +[20:23:41] publish Bash → git push -u origin HEAD +[20:23:48] publish Bash → gh pr create --title "feat: add Java... +[20:23:52] publish Bash → git remote -v +[20:23:56] publish Bash → git remote -v +[20:24:00] publish Bash → git config remote.origin.url +[20:24:06] publish Bash → gh auth status 2>&1; echo "---"; gh ... +[20:24:11] publish Bash → GIT_HOST=git.librete.ch gh api repos... +[20:24:16] publish Bash → curl -s "https://git.librete.ch/api/... +[20:24:23] publish Bash → cat ~/.config/tea/config.yml 2>/dev/... +[20:24:29] publish Bash → BODY=$(cat .wave/artifacts/result | ... +[20:24:37] publish Bash → ls -d /home/mwc/Coding/code-crispies... +[20:24:42] publish Write → /home/mwc/Coding/code-crispies/.wav... +[20:24:46] publish Processing results +[20:24:46] publish validating contract +[20:24:46] ✓ publish completed (76.9s, 17.2k in / 2.3k out) +[20:24:46] ├─ artifact: .wave/output/pr-result.json (written) +[20:24:46] └─ contract: pr-result.schema.json ✓ valid +2026/03/28 20:24:46 [retro] quantitative retrospective saved for run impl-feature-20260328-200101-ecae + + ✓ Pipeline 'impl-feature' completed successfully (1425.0s, 365.0k tokens) + + Outcomes + ✓ Branch: feat/impl-feature-20260328-200101-ecae (local only) + ✓ Pull Request: https://git.librete.ch/libretech/code-crispies/pulls/8 + + 4 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-200101-ecae/explore/.wave/output/exploration.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-200101-ecae/plan/.wave/output/plan.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-200101-ecae/__wt_feat-impl-feature-20260328-200101-ecae/.wave/output/result.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-feature-20260328-200101-ecae/__wt_feat-impl-feature-20260328-200101-ecae/.wave/output/pr-result.json + + Next Steps + → Review the pull request: Pull Request + https://git.librete.ch/libretech/code-crispies/pulls/8 + + diff --git a/.wave/logs/impl-hotfix-20260328-162004-c571.log b/.wave/logs/impl-hotfix-20260328-162004-c571.log new file mode 100644 index 0000000..cffd010 --- /dev/null +++ b/.wave/logs/impl-hotfix-20260328-162004-c571.log @@ -0,0 +1,113 @@ +[16:20:04] → investigate (navigator) [claude-haiku via claude] +[16:20:04] investigate Executing agent +[16:20:15] investigate Read → /home/mwc/Coding/code-crispies/.wave... +[16:20:16] investigate Grep → createElement\(['"]iframe['"]\) +[16:20:17] investigate Bash → git log --oneline -20 2>/dev/null ||... +[16:20:22] investigate Read → /home/mwc/Coding/code-crispies/.wave... +[16:20:23] investigate Read → /home/mwc/Coding/code-crispies/.wave... +[16:20:24] investigate Grep → sandbox|iframe|playground +[16:20:25] investigate Bash → cd /home/mwc/Coding/code-crispies &&... +[16:20:33] investigate Grep → createElement\(['"]iframe|\.sandbox|... +[16:20:35] investigate Grep → iframe|sandbox +[16:20:35] investigate Bash → cd /home/mwc/Coding/code-crispies &&... +[16:20:55] investigate Bash → cd /home/mwc/Coding/code-crispies &&... +[16:20:56] investigate Read → /home/mwc/Coding/code-crispies/.wave... +[16:20:57] investigate Grep → localStorage|auth|token|cookie +[16:21:13] investigate Grep → contentDocument|contentWindow +[16:21:18] investigate Bash → ls /home/mwc/Coding/code-crispies/.w... +[16:21:40] investigate Write → /home/mwc/Coding/code-crispies/.wav... +[16:21:48] investigate Processing results +[16:21:48] investigate validating contract +[16:21:48] ✓ investigate completed (103.8s, 22.0k in / 4.5k out) +[16:21:48] ├─ artifact: .wave/output/findings.json (written) +[16:21:48] └─ contract: findings.schema.json ✓ valid +[16:21:48] → fix (craftsman) [claude-opus via claude] +[16:21:48] fix Executing agent +[16:21:54] fix Bash → ls .wave/artifacts/investigation/ +[16:22:04] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:22:09] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:22:09] fix Glob → tests/**/*.{js,ts} +[16:22:12] fix Bash → ls /home/mwc/Coding/code-crispies/.w... +[16:22:16] fix Bash → ls /home/mwc/Coding/code-crispies/.w... +[16:22:20] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:22:20] fix Glob → tests/**/*.{js,ts} +[16:22:26] fix Glob → **/*.test.{js,ts} +[16:22:26] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:22:27] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:22:30] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:22:37] fix Grep → LessonEngine +[16:22:38] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:22:44] fix Read → /home/mwc/Coding/code-crispies/.wave... +[16:23:21] fix Edit → /home/mwc/Coding/code-crispies/.wave... +[16:23:44] fix Edit → /home/mwc/Coding/code-crispies/.wave... +[16:24:05] fix Write → /home/mwc/Coding/code-crispies/.wav... +[16:24:08] fix Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:24:30] fix Processing results +[16:24:30] fix validating contract +[16:24:37] ✓ fix completed (161.7s, 58.3k in / 9.6k out) +[16:24:37] └─ contract: test_suite ✓ valid +[16:24:44] ✓ run-tests completed (0.0s, 0 tokens) +[16:24:44] → verify (reviewer) +[16:24:44] verify Executing agent +[16:24:50] verify Bash → git log --oneline -20 +[16:24:51] verify Bash → git diff HEAD~1 --stat +[16:24:52] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:24:57] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:24:58] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:24:58] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:25:02] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:25:03] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:25:03] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:25:09] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:25:09] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:25:14] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:25:15] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:25:16] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:25:20] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:25:21] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:25:32] verify Agent → +[16:25:39] verify Agent → +[16:25:45] verify Agent → +[16:25:49] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:25:51] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:25:51] verify Grep → createElement\((['"])iframe\1\) +[16:25:52] verify Grep → contentDocument +[16:25:52] verify Bash → diff -rq /home/mwc/Coding/code-crisp... +[16:25:53] verify Grep → \.write\( +[16:25:54] verify Grep → postMessage +[16:25:55] verify Grep → innerHTML|insertAdjacentHTML +[16:25:57] verify Grep → eval\(|new Function +[16:25:57] verify Grep → srcdoc +[16:25:58] verify Grep → userCode|user_code +[16:25:59] verify Bash → diff /home/mwc/Coding/code-crispies/... +[16:26:00] verify Bash → for f in lessons.test.js renderer-ex... +[16:26:04] verify Grep → sandbox|srcdoc|iframe|security|local... +[16:26:05] verify Read → /home/mwc/.claude/projects/-home-mwc... +[16:26:05] verify Read → /home/mwc/.claude/projects/-home-mwc... +[16:26:06] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:26:07] verify Grep → sandbox|srcdoc|iframe +[16:26:11] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:26:15] verify Grep → createElement\((['"])iframe\1\)|cont... +[16:26:16] verify Grep → innerHTML|insertAdjacentHTML +[16:26:18] verify Grep → custom.*function|function.*validator... +[16:26:24] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:26:31] verify Grep → marked +[16:26:32] verify Grep → sandbox +[16:26:38] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:27:39] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:27:43] verify Bash → mkdir -p /home/mwc/Coding/code-crisp... +[16:28:14] verify Write → /home/mwc/Coding/code-crispies/.wav... +[16:28:21] verify Processing results +[16:28:21] verify validating contract +[16:28:21] ✓ verify completed (217.4s, 30.1k in / 5.1k out) +[16:28:21] ├─ artifact: .wave/output/verdict.md (written) +[16:28:21] └─ contract: non_empty_file ✓ valid +2026/03/28 16:28:21 [retro] quantitative retrospective saved for run impl-hotfix-20260328-162004-c571 + + ✓ Pipeline 'impl-hotfix' completed successfully (497.1s, 129.6k tokens) + + 2 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-hotfix-20260328-162004-c571/investigate/.wave/output/findings.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-hotfix-20260328-162004-c571/verify/.wave/output/verdict.md + + diff --git a/.wave/logs/impl-improve-20260328-163932-ceeb.log b/.wave/logs/impl-improve-20260328-163932-ceeb.log new file mode 100644 index 0000000..b533fb1 --- /dev/null +++ b/.wave/logs/impl-improve-20260328-163932-ceeb.log @@ -0,0 +1,140 @@ +[16:39:33] → assess (navigator) [claude-haiku via claude] +[16:39:33] assess Executing agent +[16:39:40] assess Read → /home/mwc/Coding/code-crispies/src/i... +[16:39:46] assess Agent → +[16:39:48] assess Bash → find /home/mwc/Coding/code-crispies ... +[16:39:54] assess Read → /home/mwc/Coding/code-crispies/.env +[16:39:54] assess Bash → ls -la /home/mwc/Coding/code-crispie... +[16:39:57] assess Grep → supabase|SUPABASE|\.supabase\.co +[16:39:57] assess Grep → CSP|Content-Security-Policy|meta.*ht... +[16:39:58] assess Grep → eval\(|innerHTML\s*=|dangerouslySetI... +[16:39:58] assess Grep → style|cssText|insertRule +[16:40:01] assess Read → /home/mwc/Coding/code-crispies/src/i... +[16:40:01] assess Read → /home/mwc/Coding/code-crispies/src/i... +[16:40:05] assess Bash → grep -r "cssText\|insertRule\|styles... +[16:40:05] assess Bash → find /home/mwc/Coding/code-crispies ... +[16:40:07] assess Read → /home/mwc/Coding/code-crispies/vite.... +[16:40:07] assess Bash → ls /home/mwc/Coding/code-crispies/sr... +[16:40:10] assess Grep → eval\(|new Function|execScript +[16:40:11] assess Grep → script.*src|appendChild.*script|crea... +[16:40:13] assess Read → /home/mwc/Coding/code-crispies/src/h... +[16:40:13] assess Read → /home/mwc/Coding/code-crispies/src/i... +[16:40:16] assess Read → /home/mwc/Coding/code-crispies/packa... +[16:40:17] assess Bash → find /home/mwc/Coding/code-crispies ... +[16:40:19] assess Read → /home/mwc/Coding/code-crispies/src/s... +[16:40:20] assess Bash → grep -rn "blob:" /home/mwc/Coding/co... +[16:40:23] assess Read → /home/mwc/Coding/code-crispies/src/i... +[16:41:31] assess Read → /home/mwc/Coding/code-crispies/src/i... +[16:41:31] assess Read → /home/mwc/Coding/code-crispies/src/h... +[16:42:17] assess Bash → ls /home/mwc/Coding/code-crispies/.w... +[16:43:10] assess Write → /home/mwc/Coding/code-crispies/.wav... +[16:43:20] assess Processing results +[16:43:20] assess validating contract +[16:43:20] ✓ assess completed (226.9s, 39.3k in / 7.4k out) +[16:43:20] ├─ artifact: .wave/output/assessment.json (written) +[16:43:20] └─ contract: improvement-assessment.schema.json ✓ valid +[16:43:20] → implement (craftsman) [claude-opus via claude] +[16:43:20] implement Executing agent +[16:43:27] implement Bash → ls .wave/artifacts/findings/ +[16:43:27] implement Read → /home/mwc/Coding/code-crispies/.wave... +[16:43:31] implement Bash → ls -la .wave/artifacts/ && cat .wave... +[16:43:31] implement Bash → ls project/ +[16:43:48] implement Read → /home/mwc/Coding/code-crispies/.wave... +[16:43:50] implement Glob → project/src/**/app.js +[16:43:53] implement Read → /home/mwc/Coding/code-crispies/.wave... +[16:43:57] implement ToolSearch → select:TodoWrite +[16:43:57] implement Read → /home/mwc/Coding/code-crispies/.wave... +[16:44:05] implement TodoWrite → 0/4 tasks +[16:44:12] implement TodoWrite → IMP-2: Remove inline onclick ha... +[16:44:17] implement Grep → help.support|support_click|help-supp... +[16:44:21] implement Grep → helpDialog|help-dialog +[16:44:27] implement Read → /home/mwc/Coding/code-crispies/.wave... +[16:44:32] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[16:44:38] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[16:44:42] implement TodoWrite → IMP-5: Add Vite plugin to injec... +[16:44:46] implement Read → /home/mwc/Coding/code-crispies/.wave... +[16:44:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[16:45:02] implement TodoWrite → Run tests to verify no regressions +[16:45:07] implement Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:45:21] implement Bash → head -5 dist/index.html +[16:45:25] implement Bash → grep -c "onclick" dist/index.html +[16:45:28] implement Bash → npm run test 2>&1 +[16:45:44] implement TodoWrite → 4/4 tasks +[16:45:52] implement Processing results +[16:45:52] implement validating contract +[16:46:06] ✓ implement completed (152.4s, 27.2k in / 5.1k out) +[16:46:06] └─ contract: test_suite ✓ valid +[16:46:06] → verify (reviewer) +[16:46:06] verify Executing agent +[16:46:15] verify Agent → +[16:46:17] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:17] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:17] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:21] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:22] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:24] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:25] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:27] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:27] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:29] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:46:30] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:30] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:32] verify Bash → git log --oneline -20 +[16:46:33] verify Bash → git diff HEAD~5..HEAD --stat +[16:46:33] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:36] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:36] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:40] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:46:40] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:46:48] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:46:48] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:46:50] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:46:51] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:46:51] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:46:53] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:46:53] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:46:56] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:46:57] verify Bash → ls -lh /home/mwc/Coding/code-crispie... +[16:47:12] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:47:13] verify Bash → find /home/mwc/Coding/code-crispies/... +[16:52:35] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:52:36] verify Bash → ls -la /home/mwc/Coding/code-crispie... +[16:52:37] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:52:42] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:52:43] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:52:52] verify Agent → +[16:52:53] verify Bash → diff /home/mwc/Coding/code-crispies/... +[16:52:55] verify Read → /home/mwc/Coding/code-crispies/vite.... +[16:52:56] verify Read → /home/mwc/Coding/code-crispies/src/a... +[16:52:56] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:53:00] verify Grep → onclick|umami|track +[16:53:00] verify Grep → onclick|umami|track +[16:53:15] verify Bash → diff /home/mwc/Coding/code-crispies/... +[16:53:15] verify Bash → diff /home/mwc/Coding/code-crispies/... +[16:53:20] verify Grep → Content-Security-Policy +[16:53:21] verify Bash → cd /home/mwc/Coding/code-crispies/.w... +[16:53:22] verify Grep → onclick +[16:53:35] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:53:36] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:53:36] verify Grep → onclick +[16:53:41] verify Grep → librete\.ch|liberapay\.com|supabase\.co +[16:53:42] verify Grep → supabase\.co +[16:53:54] verify Grep → supabase +[16:53:58] verify Grep → git\.librete\.ch +[16:54:05] verify Read → /home/mwc/Coding/code-crispies/.wave... +[16:54:09] verify Bash → ls -d /home/mwc/Coding/code-crispies... +[16:55:02] verify Write → /home/mwc/Coding/code-crispies/.wav... +[16:55:10] verify Processing results +[16:55:10] verify validating contract +[16:55:10] ✓ verify completed (543.8s, 40.5k in / 6.2k out) +[16:55:10] ├─ artifact: .wave/output/verification.md (written) +[16:55:10] └─ contract: non_empty_file ✓ valid +2026/03/28 16:55:10 [retro] quantitative retrospective saved for run impl-improve-20260328-163932-ceeb + + ✓ Pipeline 'impl-improve' completed successfully (937.3s, 125.8k tokens) + + 2 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-improve-20260328-163932-ceeb/assess/.wave/output/assessment.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-improve-20260328-163932-ceeb/verify/.wave/output/verification.md + + diff --git a/.wave/logs/impl-issue-20260328-191418-09a9.log b/.wave/logs/impl-issue-20260328-191418-09a9.log new file mode 100644 index 0000000..9fd2cca --- /dev/null +++ b/.wave/logs/impl-issue-20260328-191418-09a9.log @@ -0,0 +1,114 @@ +[19:14:19] → fetch-assess (implementer) [claude-haiku via claude] +[19:14:19] fetch-assess Executing agent +[19:14:27] fetch-assess Bash → tea issues view 3 --repo libretech/c... +[19:14:34] fetch-assess Bash → curl -s "https://git.librete.ch/api/... +[19:14:51] fetch-assess Grep → flexbox|flex +[19:14:56] fetch-assess Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:09] fetch-assess Bash → ls /home/mwc/Coding/code-crispies/.w... +[19:15:23] fetch-assess Write → /home/mwc/Coding/code-crispies/.wav... +[19:15:28] fetch-assess Processing results +[19:15:28] fetch-assess validating contract +[19:15:28] ✓ fetch-assess completed (69.4s, 18.4k in / 2.6k out) +[19:15:28] ├─ artifact: .wave/output/issue-assessment.json (written) +[19:15:28] └─ contract: issue-assessment.schema.json ✓ valid +[19:15:28] → plan (implementer) [claude-opus via claude] +[19:15:28] plan Executing agent +[19:15:35] plan Bash → ls .wave/artifacts/issue_assessment/ +[19:15:36] plan Bash → ls specs/ 2>/dev/null || echo "No sp... +[19:15:41] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:44] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:49] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:49] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:16:04] plan Grep → flexbox +[19:16:08] plan Grep → flexbox +[19:16:46] plan Bash → git checkout -b 003-flexbox-task-wor... +[19:16:49] plan Bash → mkdir -p specs/003-flexbox-task-word... +[19:17:02] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:17:24] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:17:28] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:17:43] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:17:48] plan Bash → git add specs/003-flexbox-task-wordi... +[19:17:54] plan Processing results +[19:17:54] plan validating contract +[19:17:54] ✓ plan completed (146.3s, 33.1k in / 6.8k out) +[19:17:54] ├─ artifact: .wave/output/impl-plan.json (written) +[19:17:54] └─ contract: issue-impl-plan.schema.json ✓ valid +[19:17:54] → implement (craftsman) [claude-opus via claude] +[19:17:54] implement Executing agent +[19:18:02] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:02] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:08] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:09] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:10] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:10] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:32] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:39] implement Grep → regex.*validation|case.*regex|type.*... +[19:18:42] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:46] implement ToolSearch → select:TodoWrite +[19:18:51] implement TodoWrite → Rewrite task text for all 6 fle... +[19:18:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:15] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:31] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:47] implement TodoWrite → Run tests and validate +[19:24:51] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:24:51] implement Bash → npm test 2>&1 +[19:25:04] implement Bash → npm run format.lessons 2>&1 +[19:25:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:23] implement Bash → git add -A && git reset HEAD -- .wav... +[19:25:26] implement Bash → git diff --cached --name-only | head... +[19:25:32] implement Bash → git commit -m "$(cat <<'EOF' +fix: re... +[19:25:36] implement TodoWrite → 5/5 tasks +[19:25:49] implement Processing results +[19:25:49] implement validating contract +[19:25:57] ✓ implement completed (474.7s, 43.9k in / 7.6k out) +[19:25:57] └─ contract: test_suite ✓ valid +[19:25:57] → create-pr (gitea-commenter) +[19:25:57] create-pr Executing agent +[19:26:05] create-pr Bash → ls .wave/artifacts/issue_assessment/ +[19:26:05] create-pr Bash → git log --oneline -10 +[19:26:06] create-pr Bash → git branch -vv +[19:26:13] create-pr Read → /home/mwc/Coding/code-crispies/.wave... +[19:26:19] create-pr Bash → git log main..HEAD --oneline +[19:26:20] create-pr Bash → git diff --stat main..HEAD +[19:26:25] create-pr Bash → git push -u origin 003-flexbox-task-... +[19:26:30] create-pr Bash → which tea 2>/dev/null && tea --versi... +[19:26:40] create-pr Bash → cat > /tmp/pr-body.md <<'EOF' +## Sum... +[19:26:46] create-pr Bash → git remote get-url origin +[19:26:47] create-pr Bash → tea logins list 2>/dev/null || echo ... +[19:26:54] create-pr Bash → cat /tmp/pr-body.md | tea pulls crea... +[19:27:00] create-pr Bash → ls .wave/output/ 2>/dev/null || mkdi... +[19:27:08] create-pr Write → /home/mwc/Coding/code-crispies/.wav... +[19:27:14] create-pr Processing results +[19:27:14] create-pr validating contract +[19:27:14] ✓ create-pr completed (76.9s, 16.6k in / 2.2k out) +[19:27:14] ├─ artifact: .wave/output/pr-result.json (written) +[19:27:14] └─ contract: pr-result.schema.json ✓ valid +2026/03/28 19:27:14 [retro] quantitative retrospective saved for run impl-issue-20260328-191418-09a9 + + ✓ Pipeline 'impl-issue' completed successfully (775.2s, 131.0k tokens) + + Outcomes + ✓ Branch: impl-issue-20260328-191418-09a9 (local only) + ✓ Pull Request: https://git.librete.ch/libretech/code-crispies/pulls/5 + + 3 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-191418-09a9/__wt_impl-issue-20260328-191418-09a9/.wave/output/issue-assessment.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-191418-09a9/__wt_impl-issue-20260328-191418-09a9/.wave/output/impl-plan.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-191418-09a9/__wt_impl-issue-20260328-191418-09a9/.wave/output/pr-result.json + + Next Steps + → Review the pull request: Pull Request + https://git.librete.ch/libretech/code-crispies/pulls/5 + + diff --git a/.wave/logs/impl-issue-20260328-191418-09bb.log b/.wave/logs/impl-issue-20260328-191418-09bb.log new file mode 100644 index 0000000..dc09c82 --- /dev/null +++ b/.wave/logs/impl-issue-20260328-191418-09bb.log @@ -0,0 +1,637 @@ +[19:14:19] → fetch-assess (implementer) [claude-haiku via claude] +[19:14:19] fetch-assess Executing agent +[19:14:28] fetch-assess Bash → tea issues view 4 --repo libretech/c... +[19:14:28] fetch-assess Bash → curl -s "https://git.librete.ch/api/... +[19:14:45] fetch-assess Glob → lessons/**/*.json +[19:14:51] fetch-assess Bash → grep -rl '"message"' lessons/flexbox... +[19:14:54] fetch-assess Grep → "message" +[19:14:55] fetch-assess Grep → "message" +[19:15:00] fetch-assess Grep → "message".*Set .*" +[19:15:07] fetch-assess Bash → ls /home/mwc/Coding/code-crispies/.w... +[19:15:21] fetch-assess Write → /home/mwc/Coding/code-crispies/.wav... +[19:15:27] fetch-assess Processing results +[19:15:27] fetch-assess validating contract +[19:15:27] ✓ fetch-assess completed (68.1s, 16.8k in / 2.5k out) +[19:15:27] ├─ artifact: .wave/output/issue-assessment.json (written) +[19:15:27] └─ contract: issue-assessment.schema.json ✓ valid +[19:15:27] → plan (implementer) [claude-opus via claude] +[19:15:27] plan Executing agent +[19:15:33] plan Bash → ls .wave/artifacts/issue_assessment/ +[19:15:34] plan Bash → ls .wave/output/ 2>/dev/null || echo... +[19:15:39] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:39] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:43] plan Glob → lessons/**/*.json +[19:15:44] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:50] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:15:52] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:16:02] plan Agent → +[19:16:02] plan Glob → lessons/pl/*.json +[19:16:04] plan Bash → find /home/mwc/Coding/code-crispies/... +[19:16:06] plan Bash → ls -la /home/mwc/Coding/code-crispie... +[19:16:08] plan Bash → ls -la /home/mwc/Coding/code-crispie... +[19:16:09] plan Glob → lessons/*.json +[19:16:11] plan Glob → lessons/ar/*.json +[19:16:12] plan Glob → lessons/es/*.json +[19:16:12] plan Glob → lessons/pl/*.json +[19:16:15] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:16:18] plan Bash → find lessons -name "*.json" -type f ... +[19:16:20] plan Bash → grep -r '"message":' lessons/**/*.js... +[19:16:22] plan Bash → grep -r '"message":' /home/mwc/Codin... +[19:16:24] plan Bash → grep -roh '"message": "[^"]*' /home/... +[19:16:26] plan Bash → grep -roh '"message": "[^"]*' /home/... +[19:16:36] plan Bash → cat > /tmp/extract_validations.py <<... +[19:16:38] plan Read → /home/mwc/.claude/projects/-home-mwc... +[19:16:46] plan Bash → cat > /tmp/extract_validations_summa... +[19:16:56] plan Bash → cat > /tmp/detailed_breakdown.py << ... +[19:17:02] plan Bash → cat > /tmp/comprehensive_listing.py ... +[19:17:05] plan Bash → wc -l /tmp/comprehensive_listing.txt... +[19:17:32] plan Bash → cat > /tmp/final_audit_report.txt <<... +[19:17:54] plan Bash → cat > /tmp/file_reference_guide.txt ... +[19:18:17] plan Bash → cat > /tmp/worst_offenders_detailed.... +[19:18:35] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:35] plan Glob → lessons/uk/*.json +[19:18:36] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:18:46] plan Agent → +[19:18:48] plan Bash → find /home/mwc/Coding/code-crispies/... +[19:18:50] plan Bash → pwd && ls -la +[19:18:52] plan Bash → find /home/mwc/Coding/code-crispies/... +[19:19:00] plan Grep → "message":\s*"[^"]*(?:Set|Add|Use)\s... +[19:19:02] plan Grep → Set |Add |Use +[19:19:03] plan Read → /home/mwc/Coding/code-crispies/.wave... +[19:19:06] plan Bash → grep -o '"message":\s*"[^"]*"' /home... +[19:19:11] plan Bash → cat > /tmp/count_messages.sh << 'SCR... +[19:19:15] plan Bash → cat > /tmp/detailed_count.sh << 'SCR... +[19:19:17] plan Bash → grep '"message"' /home/mwc/Coding/co... +[19:19:23] plan Bash → cat > /tmp/final_report.sh << 'SCRIP... +[19:19:42] plan Bash → git checkout -b 004-pedagogical-mess... +[19:19:42] plan Bash → mkdir -p /home/mwc/Coding/code-crisp... +[19:19:58] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:20:25] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:20:34] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:21:14] plan Write → /home/mwc/Coding/code-crispies/.wav... +[19:21:23] plan Bash → git add specs/004-pedagogical-messag... +[19:21:32] plan Processing results +[19:21:32] plan validating contract +[19:21:32] ✓ plan completed (365.2s, 60.0k in / 10.5k out) +[19:21:32] ├─ artifact: .wave/output/impl-plan.json (written) +[19:21:32] └─ contract: issue-impl-plan.schema.json ✓ valid +[19:21:32] → implement (craftsman) [claude-opus via claude] +[19:21:32] implement Executing agent +[19:21:38] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:21:38] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:21:44] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:21:45] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:21:45] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:21:57] implement Agent → +[19:21:58] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:21:59] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:21:59] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:22:00] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:22:02] implement Glob → tests/**/*.test.{js,ts,jsx,tsx} +[19:22:02] implement Bash → find /home/mwc/Coding/code-crispies/... +[19:22:05] implement Grep → \.message +[19:22:06] implement Grep → Set (padding|margin|color|font|width... +[19:22:09] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:22:09] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:22:12] implement Grep → lesson.*message|message.*lesson +[19:22:13] implement Grep → validations.*message|message.*valida... +[19:22:15] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:22:16] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:22:19] implement Grep → \.message|expect.*message +[19:22:19] implement Grep → \.message|expect.*message +[19:22:41] implement ToolSearch → select:TodoWrite +[19:22:48] implement TodoWrite → Rewrite priority English module... +[19:23:01] implement Agent → +[19:23:09] implement Agent → +[19:23:12] implement Agent → +[19:23:15] implement Agent → +[19:23:18] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:23:19] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:23:20] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:23:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:31] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:34] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:34] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:38] implement Bash → cat -A /home/mwc/Coding/code-crispie... +[19:23:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:23:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:10] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:19] implement Grep → Set padding: 1rem +[19:24:23] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:24:26] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:24:32] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:24:39] implement Grep → "message": +[19:24:47] implement Bash → python3 -m json.tool /home/mwc/Codin... +[19:25:00] implement TodoWrite → Rewrite remaining English modul... +[19:25:09] implement Agent → +[19:25:12] implement Agent → +[19:25:14] implement Agent → +[19:25:18] implement Agent → +[19:25:22] implement Agent → +[19:25:27] implement Agent → +[19:25:30] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:31] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:31] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:32] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:32] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:33] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:34] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:25:57] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:25:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:00] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:26:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:07] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:11] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:15] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:15] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:16] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:18] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:20] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:20] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:25] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:26:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:28] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:31] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:33] implement Bash → cat -A /home/mwc/Coding/code-crispie... +[19:26:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:38] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:41] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:26:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:47] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:48] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:51] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:51] implement Grep → "message": +[19:26:51] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:55] implement Bash → node -e "JSON.parse(require('fs').re... +[19:26:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:26:59] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:04] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:04] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:05] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:07] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:07] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:13] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:16] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:16] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:24] implement Bash → node -e "JSON.parse(require('fs').re... +[19:27:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:31] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:34] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:38] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:41] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:45] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:48] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:52] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:27:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:27:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:28:03] implement Bash → python3 -c " +import json +files = ['l... +[19:28:08] implement Bash → python3 -c " +import json +files = ['l... +[19:28:30] implement TodoWrite → Update localized variants (ar, ... +[19:28:42] implement Agent → +[19:28:48] implement Agent → +[19:28:53] implement Agent → +[19:28:58] implement Agent → +[19:29:03] implement Agent → +[19:29:06] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:07] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:07] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:08] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:08] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:09] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:09] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:10] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:10] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:11] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:16] implement Glob → lessons/en/*.json +[19:29:16] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:17] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:18] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:18] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:19] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:19] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:20] implement Glob → lessons/*.json +[19:29:21] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:22] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:23] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:23] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:24] implement Glob → lessons/en/*.json +[19:29:25] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:26] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:26] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:27] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:27] implement Grep → "message": +[19:29:28] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:31] implement Glob → lessons/**/*.json +[19:29:34] implement Grep → "message": +[19:29:34] implement Grep → "message": +[19:29:36] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:37] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:37] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:29:39] implement Grep → "message": +[19:29:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:44] implement Grep → "message": +[19:29:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:45] implement Grep → "message": +[19:29:46] implement Grep → "message": +[19:29:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:47] implement Grep → "message": +[19:29:48] implement Grep → "message": +[19:29:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:51] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:29:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:03] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:05] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:05] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:10] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:30:12] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:30:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:16] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:18] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:20] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:20] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:25] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:29] implement Bash → cat -A /home/mwc/Coding/code-crispie... +[19:30:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:34] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:30:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:48] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:51] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:59] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:30:59] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:01] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:03] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:05] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:15] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:28] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:32] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:36] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:48] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:31:59] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:03] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:11] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:15] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:16] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:18] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:20] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:20] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:25] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:25] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:34] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:34] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:38] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:51] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:32:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:01] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:03] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:03] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:05] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:07] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:10] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:10] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:15] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:16] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:20] implement Grep → "message": "Dodaj +[19:33:21] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:21] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:25] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:26] implement Grep → "message": +[19:33:28] implement Grep → "message": +[19:33:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:30] implement Grep → "message": +[19:33:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:32] implement Grep → "message": +[19:33:34] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:36] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:38] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:48] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:57] implement Bash → for f in lessons/ar/flexbox.json les... +[19:33:57] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:33:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:01] implement Bash → python3 -c " +import json, sys + +files... +[19:34:01] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:02] implement Grep → "message": "اضبط [^<]*" +[19:34:03] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:06] implement Grep → "message": "أضف [^<]*" +[19:34:07] implement Grep → "message": "(Встановіть " +[19:34:11] implement Grep → "message":.*" +[19:34:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:16] implement Grep → "message":.*" +[19:34:21] implement Grep → "message": "Establece +[19:34:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:23] implement Grep → "message": "عيّن" +[19:34:27] implement Grep → "message": "عيّن" +[19:34:28] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:30] implement Grep → "message": "(Configura|Añade )" +[19:34:31] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:34:32] implement Grep → "message": "(Configura|Añade )" +[19:34:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:36] implement Grep → "message": +[19:34:37] implement Grep → "message": +[19:34:38] implement Grep → "message": +[19:34:40] implement Grep → "message": +[19:34:41] implement Grep → "message": +[19:34:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:34:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:05] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:09] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:34] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:35:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:00] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:16] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:32] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:36] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:36:41] implement Grep → Setze +[19:36:48] implement Grep → "message": "Füge.*.*" +[19:36:54] implement Grep → "message": "Füge.*.*" +[19:37:00] implement Grep → "message":.*" +[19:37:09] implement Grep → "message":.*" +[19:37:14] implement Grep → "message":.*"Stelle +[19:37:19] implement Grep → "message":.*"Set +[19:37:27] implement Bash → for f in lessons/de/flexbox.json les... +[19:37:34] implement Bash → npm run test 2>&1 | tail -30 +[19:38:09] implement TodoWrite → Format, test, audit, and commit +[19:38:14] implement Skill → +[19:38:24] implement Bash → npx prettier --write "lessons/**/*.j... +[19:38:33] implement Bash → npm test 2>&1 +[19:38:34] implement Grep → Set +[19:39:08] implement Grep → "message": "Set +[19:39:20] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:39:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:39:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[19:39:30] implement Read → /home/mwc/Coding/code-crispies/.wave... +[19:39:41] implement Write → /home/mwc/Coding/code-crispies/.wav... +[19:39:48] implement Bash → npm test 2>&1 +[19:40:08] implement Bash → git add -A && git reset HEAD -- .wav... +[19:40:16] implement Bash → git diff --cached --name-only | head... +[19:40:20] implement Bash → git diff --cached --name-only | tail... +[19:40:28] implement Bash → git commit -m "$(cat <<'EOF' +feat: i... +[19:40:33] implement TodoWrite → 5/5 tasks +[19:40:41] implement Processing results +[19:40:41] implement validating contract +[19:40:47] ✓ implement completed (1148.6s, 120.6k in / 15.2k out) +[19:40:47] └─ contract: test_suite ✓ valid +[19:40:47] → create-pr (gitea-commenter) +[19:40:47] create-pr Executing agent +[19:40:54] create-pr Bash → ls .wave/artifacts/issue_assessment/ +[19:40:55] create-pr Bash → git branch -vv +[19:41:00] create-pr Read → /home/mwc/Coding/code-crispies/.wave... +[19:41:04] create-pr Bash → git diff main...HEAD --stat +[19:41:08] create-pr Bash → git push -u origin 004-pedagogical-m... +[19:41:15] create-pr Bash → which tea 2>&1 && tea --version 2>&1 +[19:41:24] create-pr Write → /tmp/pr-body.md +[19:41:30] create-pr Bash → cat > /tmp/pr-body.md <<'EOF' +## Sum... +[19:41:35] create-pr Bash → tea pulls create --repo libretech/co... +[19:41:41] create-pr Bash → tea pulls create --repo libretech/co... +[19:41:52] create-pr Bash → tea logins ls 2>&1 | head -20 +[19:41:57] create-pr Bash → cat ~/.config/tea/config.yml 2>/dev/... +[19:42:03] create-pr Bash → GITEA_TOKEN="5b4032475572ae48c389bc6... +[19:42:09] create-pr Bash → ls .wave/output/ 2>/dev/null || mkdi... +[19:42:16] create-pr Write → /home/mwc/Coding/code-crispies/.wav... +[19:42:21] create-pr Processing results +[19:42:21] create-pr validating contract +[19:42:21] ✓ create-pr completed (93.1s, 19.0k in / 2.6k out) +[19:42:21] ├─ artifact: .wave/output/pr-result.json (written) +[19:42:21] └─ contract: pr-result.schema.json ✓ valid +2026/03/28 19:42:21 [retro] quantitative retrospective saved for run impl-issue-20260328-191418-09bb + + ✓ Pipeline 'impl-issue' completed successfully (1682.1s, 247.3k tokens) + + Outcomes + ✓ Branch: impl-issue-20260328-191418-09bb (local only) + ✓ Pull Request: https://git.librete.ch/libretech/code-crispies/pulls/6 + + 3 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-191418-09bb/__wt_impl-issue-20260328-191418-09bb/.wave/output/issue-assessment.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-191418-09bb/__wt_impl-issue-20260328-191418-09bb/.wave/output/impl-plan.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-191418-09bb/__wt_impl-issue-20260328-191418-09bb/.wave/output/pr-result.json + + Next Steps + → Review the pull request: Pull Request + https://git.librete.ch/libretech/code-crispies/pulls/6 + + diff --git a/.wave/logs/impl-issue-20260328-200048-e45d.log b/.wave/logs/impl-issue-20260328-200048-e45d.log new file mode 100644 index 0000000..455beb6 --- /dev/null +++ b/.wave/logs/impl-issue-20260328-200048-e45d.log @@ -0,0 +1,226 @@ +[20:00:48] → fetch-assess (implementer) [claude-haiku via claude] +[20:00:48] fetch-assess Executing agent +[20:01:03] fetch-assess Bash → tea issues view 4 --repo libretech/c... +[20:01:03] fetch-assess Bash → ls -la .wave/output/ 2>/dev/null || ... +[20:01:10] fetch-assess Bash → curl -s "https://git.librete.ch/api/... +[20:01:10] fetch-assess Bash → curl -s "https://git.librete.ch/api/... +[20:01:27] fetch-assess Glob → lessons/**/*.json +[20:01:30] fetch-assess Grep → "message" +[20:01:31] fetch-assess Grep → "message" +[20:01:52] fetch-assess Write → /home/mwc/Coding/code-crispies/.wav... +[20:01:56] fetch-assess Processing results +[20:01:56] fetch-assess validating contract +[20:01:56] ✓ fetch-assess completed (68.0s, 15.2k in / 2.3k out) +[20:01:56] ├─ artifact: .wave/output/issue-assessment.json (written) +[20:01:56] └─ contract: issue-assessment.schema.json ✓ valid +[20:01:56] → plan (implementer) [claude-opus via claude] +[20:01:56] plan Executing agent +[20:02:03] plan Bash → ls .wave/artifacts/issue_assessment/ +[20:02:03] plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:02:12] plan Agent → +[20:02:14] plan Glob → **/lessons/**/*.json +[20:02:15] plan Bash → find /home/mwc/Coding/code-crispies ... +[20:02:16] plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:02:17] plan Bash → find /home/mwc/Coding/code-crispies/... +[20:02:19] plan Bash → find /home/mwc/Coding/code-crispies/... +[20:02:20] plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:02:23] plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:02:23] plan Read → /home/mwc/Coding/code-crispies/lesso... +[20:02:24] plan Bash → find /home/mwc/Coding/code-crispies/... +[20:02:26] plan Bash → ls -la /home/mwc/Coding/code-crispie... +[20:02:27] plan Bash → ls -la /home/mwc/Coding/code-crispie... +[20:02:29] plan Bash → find /home/mwc/Coding/code-crispies/... +[20:02:30] plan Bash → ls -d /home/mwc/Coding/code-crispies... +[20:02:31] plan Bash → find /home/mwc/Coding/code-crispies/... +[20:03:02] plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:03:03] plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:03:04] plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:03:09] plan Glob → lessons/**/*.json +[20:03:12] plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:03:12] plan Read → /home/mwc/Coding/code-crispies/.wave... +[20:03:19] plan Glob → lessons/{es,pl,uk}/01-box-model.json +[20:03:20] plan Grep → validation.*message|message.*validation +[20:03:23] plan Glob → lessons/uk/01-box-model.json +[20:03:24] plan Grep → "message" +[20:03:30] plan Bash → git checkout -b 004-validation-messages +[20:03:33] plan Bash → mkdir -p specs/004-validation-messag... +[20:03:53] plan Write → /home/mwc/Coding/code-crispies/.wav... +[20:04:20] plan Write → /home/mwc/Coding/code-crispies/.wav... +[20:04:24] plan Write → /home/mwc/Coding/code-crispies/.wav... +[20:04:41] plan Write → /home/mwc/Coding/code-crispies/.wav... +[20:04:46] plan Bash → python3 -c "import json; d=json.load... +[20:04:47] plan Bash → git branch --show-current +[20:04:55] plan Processing results +[20:04:55] plan validating contract +[20:04:55] ✓ plan completed (178.3s, 40.8k in / 6.9k out) +[20:04:55] ├─ artifact: .wave/output/impl-plan.json (written) +[20:04:55] └─ contract: issue-impl-plan.schema.json ✓ valid +[20:04:55] → implement (craftsman) [claude-opus via claude] +[20:04:55] implement Executing agent +[20:05:01] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:02] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:05] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:05] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:06] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:10] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:11] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:12] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:13] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:14] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:15] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:05:31] implement ToolSearch → select:TodoWrite +[20:05:39] implement TodoWrite → Rewrite 11 validation messages ... +[20:05:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:05:47] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:05:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:05:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:05:51] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:05:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:05:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:05:59] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:06:04] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:06:10] implement Bash → cat -A /home/mwc/Coding/code-crispie... +[20:06:18] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:06:23] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:06:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:06:36] implement TodoWrite → Rewrite 4 validation messages i... +[20:06:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:06:39] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:06:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:06:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:06:48] implement TodoWrite → Update Arabic box-model messages +[20:07:00] implement Agent → +[20:07:08] implement Agent → +[20:07:13] implement Agent → +[20:07:21] implement Agent → +[20:07:28] implement Agent → +[20:07:31] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:31] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:32] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:43] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:47] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:47] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:49] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:51] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:54] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:07:59] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:07:59] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:00] implement Grep → Ustaw padding: 1rem +[20:08:00] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:01] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:02] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:02] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:03] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:05] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:06] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:06] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:08] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:10] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:11] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:12] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:12] implement Read → /home/mwc/Coding/code-crispies/.wave... +[20:08:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:16] implement Bash → cat -A /home/mwc/Coding/code-crispie... +[20:08:17] implement Bash → cat -A /home/mwc/Coding/code-crispie... +[20:08:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:22] implement Bash → sed -n '25p' /home/mwc/Coding/code-c... +[20:08:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[20:08:25] implement Bash → sed -n '22,28p' /home/mwc/Coding/cod... +[20:08:27] implement Grep → "message": +[20:08:28] implement Bash → sed -i '25s/"Ustaw padding: 1re... +[20:08:29] implement Bash → sed -i '175s/"Ustaw padding: 1r... +[20:08:30] implement Bash → sed -i '25s/"message": "اضبط &1 +[20:09:18] implement Bash → npm run test 2>&1 +[20:09:36] implement TodoWrite → Update tasks.md and commit +[20:09:41] implement Write → /home/mwc/Coding/code-crispies/.wav... +[20:09:55] implement Bash → git add -A && git reset HEAD -- .wav... +[20:09:58] implement Bash → git diff --cached --name-only | head... +[20:10:04] implement Bash → git commit -m "$(cat <<'EOF' +fix: re... +[20:10:09] implement TodoWrite → 9/9 tasks +[20:10:17] implement Processing results +[20:10:17] implement validating contract +[20:10:24] ✓ implement completed (322.4s, 81.0k in / 13.2k out) +[20:10:24] └─ contract: test_suite ✓ valid +[20:10:24] → create-pr (gitea-commenter) +[20:10:24] create-pr Executing agent +[20:10:30] create-pr Bash → ls .wave/artifacts/issue_assessment/ +[20:10:31] create-pr Bash → git branch -v +[20:10:35] create-pr Read → /home/mwc/Coding/code-crispies/.wave... +[20:10:39] create-pr Bash → git log main..HEAD --oneline +[20:10:40] create-pr Bash → git diff main..HEAD --stat +[20:10:43] create-pr Bash → git remote -v +[20:10:47] create-pr Bash → git push -u origin 004-validation-me... +[20:10:57] create-pr Bash → cat > /tmp/pr-body.md <<'EOF' +## Sum... +[20:11:06] create-pr Bash → tea login list 2>/dev/null | head -20 +[20:11:12] create-pr Bash → cat > /tmp/pr-body.md <<'EOF' +## Sum... +[20:11:19] create-pr Bash → ls .wave/output/ 2>/dev/null || mkdi... +[20:11:32] create-pr Write → /home/mwc/Coding/code-crispies/.wav... +[20:11:42] create-pr Processing results +[20:11:42] create-pr validating contract +[20:11:42] ✓ create-pr completed (78.5s, 15.5k in / 2.3k out) +[20:11:42] ├─ artifact: .wave/output/pr-result.json (written) +[20:11:42] └─ contract: pr-result.schema.json ✓ valid +2026/03/28 20:11:43 [retro] quantitative retrospective saved for run impl-issue-20260328-200048-e45d + + ✓ Pipeline 'impl-issue' completed successfully (654.2s, 177.1k tokens) + + Outcomes + ✓ Branch: impl-issue-20260328-200048-e45d (local only) + ✓ Pull Request: https://git.librete.ch/libretech/code-crispies/pulls/7 + + 3 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-200048-e45d/__wt_impl-issue-20260328-200048-e45d/.wave/output/issue-assessment.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-200048-e45d/__wt_impl-issue-20260328-200048-e45d/.wave/output/impl-plan.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-200048-e45d/__wt_impl-issue-20260328-200048-e45d/.wave/output/pr-result.json + + Next Steps + → Review the pull request: Pull Request + https://git.librete.ch/libretech/code-crispies/pulls/7 + + diff --git a/.wave/logs/impl-issue-20260328-234704-0b6f.log b/.wave/logs/impl-issue-20260328-234704-0b6f.log new file mode 100644 index 0000000..521c25b --- /dev/null +++ b/.wave/logs/impl-issue-20260328-234704-0b6f.log @@ -0,0 +1,171 @@ +[23:47:05] → fetch-assess (implementer) [claude-haiku via claude] +[23:47:05] fetch-assess Executing agent +[23:47:16] fetch-assess Bash → tea issues view 9 --repo libretech/c... +[23:47:21] fetch-assess Bash → curl -s "https://git.librete.ch/api/... +[23:47:32] fetch-assess Glob → lessons/**/*.json +[23:47:39] fetch-assess Bash → ls -d /home/mwc/Coding/code-crispies... +[23:47:47] fetch-assess Write → /home/mwc/Coding/code-crispies/.wav... +[23:47:53] fetch-assess Processing results +[23:47:53] fetch-assess validating contract +[23:47:53] ✓ fetch-assess completed (48.3s, 15.6k in / 1.8k out) +[23:47:53] ├─ artifact: .wave/output/issue-assessment.json (written) +[23:47:53] └─ contract: issue-assessment.schema.json ✓ valid +[23:47:53] → plan (implementer) [claude-opus via claude] +[23:47:53] plan Executing agent +[23:47:58] plan Bash → ls .wave/artifacts/issue_assessment/ +[23:47:59] plan Bash → ls .wave/output/ 2>/dev/null || echo... +[23:48:03] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:03] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:09] plan Bash → git checkout -b 009-colors-boxmodel-... +[23:48:09] plan Glob → lessons/**/*.json +[23:48:10] plan Bash → git log --oneline --all | grep -i fl... +[23:48:14] plan Bash → git show 61acd69 --stat +[23:48:15] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:15] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:21] plan Bash → git show 61acd69 -- lessons/flexbox.... +[23:48:22] plan Glob → lessons/**/03-colors.json +[23:48:23] plan Bash → ls specs/ +[23:48:31] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:32] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:32] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:39] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:40] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:41] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:48:42] plan Bash → ls specs/003-flexbox-task-wording/ +[23:49:11] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:49:12] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:49:52] plan Bash → mkdir -p specs/009-colors-boxmodel-t... +[23:50:10] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:50:37] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:50:47] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:51:07] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:51:12] plan Bash → git add specs/009-colors-boxmodel-ta... +[23:51:21] plan Processing results +[23:51:21] plan validating contract +[23:51:21] ✓ plan completed (208.3s, 71.6k in / 10.9k out) +[23:51:21] ├─ artifact: .wave/output/impl-plan.json (written) +[23:51:21] └─ contract: issue-impl-plan.schema.json ✓ valid +[23:51:21] → implement (craftsman) [claude-opus via claude] +[23:51:21] implement Executing agent +[23:51:27] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:27] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:30] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:31] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:32] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:36] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:37] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:43] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:44] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:45] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:46] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:51:46] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:52:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:52:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:52:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:52:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:52:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:52:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:52:56] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:11] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:29] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:41] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:46] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:53:52] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:54:15] implement Agent → +[23:54:27] implement Agent → +[23:54:36] implement Agent → +[23:54:49] implement Agent → +[23:55:02] implement Agent → +[23:55:06] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:55:06] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:55:07] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:55:13] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:14] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:15] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:16] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:19] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:20] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:21] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:22] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:23] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:24] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:25] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:25] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:26] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:27] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:28] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:32] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:33] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:37] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:38] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:39] implement Grep → "task": +[23:55:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:42] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:44] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:47] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:48] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:50] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:52] implement Bash → node -e "const f = require('./lesson... +[23:55:53] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:55:58] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:56:02] implement Grep → "task": +[23:56:14] implement Bash → npm run test 2>&1 +[23:56:16] implement Bash → npm run format.lessons 2>&1 +[23:56:38] implement Write → /home/mwc/Coding/code-crispies/.wav... +[23:56:43] implement Bash → git add -A && git reset HEAD -- .wav... +[23:56:48] implement Bash → git diff --cached --name-only | head... +[23:56:55] implement Bash → git commit -m "$(cat <<'EOF' +feat: i... +[23:57:06] implement Processing results +[23:57:06] implement validating contract +[23:57:13] ✓ implement completed (344.2s, 88.5k in / 15.1k out) +[23:57:13] └─ contract: test_suite ✓ valid +[23:57:13] → create-pr (gitea-commenter) +[23:57:13] create-pr Executing agent +[23:57:20] create-pr Bash → ls .wave/artifacts/issue_assessment/ +[23:57:21] create-pr Bash → git branch -vv +[23:57:26] create-pr Read → /home/mwc/Coding/code-crispies/.wave... +[23:57:32] create-pr Bash → git diff main...HEAD --stat +[23:57:32] create-pr Bash → git log main...HEAD --oneline +[23:57:37] create-pr Bash → git push -u origin 009-colors-boxmod... +[23:57:57] create-pr Bash → cat > /tmp/pr-body.md <<'EOF' +## Sum... +[23:58:03] create-pr Bash → tea login list 2>&1 | head -5 +[23:58:07] create-pr Bash → cat ~/.config/tea/config.yml 2>/dev/... +[23:58:17] create-pr Bash → GITEA_TOKEN="5b4032475572ae48c389bc6... +[23:58:25] create-pr Bash → ls .wave/output/ 2>/dev/null || mkdi... +[23:58:32] create-pr Write → /home/mwc/Coding/code-crispies/.wav... +[23:58:39] create-pr Processing results +[23:58:39] create-pr validating contract +[23:58:39] ✓ create-pr completed (86.2s, 17.3k in / 2.5k out) +[23:58:39] ├─ artifact: .wave/output/pr-result.json (written) +[23:58:39] └─ contract: pr-result.schema.json ✓ valid +2026/03/28 23:58:39 [retro] quantitative retrospective saved for run impl-issue-20260328-234704-0b6f + + ✓ Pipeline 'impl-issue' completed successfully (694.3s, 223.3k tokens) + + Outcomes + ✓ Branch: impl-issue-20260328-234704-0b6f (local only) + ✓ Pull Request: https://git.librete.ch/libretech/code-crispies/pulls/10 + + 3 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-234704-0b6f/__wt_impl-issue-20260328-234704-0b6f/.wave/output/issue-assessment.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-234704-0b6f/__wt_impl-issue-20260328-234704-0b6f/.wave/output/impl-plan.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260328-234704-0b6f/__wt_impl-issue-20260328-234704-0b6f/.wave/output/pr-result.json + + Next Steps + → Review the pull request: Pull Request + https://git.librete.ch/libretech/code-crispies/pulls/10 + + diff --git a/.wave/logs/impl-issue-20260329-215756-8d2f.log b/.wave/logs/impl-issue-20260329-215756-8d2f.log new file mode 100644 index 0000000000000000000000000000000000000000..5797a19da295fe3a03d2fc08b137e723c857ec80 GIT binary patch literal 3836 zcmeH}v2NQi5QaPVDGr$uqY@>>N@5KiS`=N2E*%1+peZI|MTr7Q*%>l)>I1YH^K^ca zkW{4Dv6(c117wI6%QpG(?*6-9I!)p%J zB^s312QKX*H|Q0iPR8xAG*5<0;%*X}-Jb6IO+bL{kh* zsg^2p7F|FbLl8*X2;gicsiverVHgg`D~K z+S+-r=l)yi7Hbk7LSHv20+oZXct ziY5UOTu2qn#6@K$iU%62(#(AyNgR1=Gr)SYz!Ht_CGwQ*ZMnHU^`8D*Wn$-kTa|fD z$m5+Sp6tHMUjpeK20N7}^|$bI6;J)+{W_3I7DZXI)5IO}0jnI`+`N5&dEAiFJo6x! zK@`V9XA3H@Mx{EBF%DypaTfXQYCu+H&Ljj4-1^owV&}dNnfT>-#v6r0rhX@SHOM53 hcO7MtJy6R*9;-Oc^M}+atvv7EuD1%jRp6y7a06;Ic{~6B literal 0 HcmV?d00001 diff --git a/.wave/logs/impl-issue-20260329-232103-73b5.log b/.wave/logs/impl-issue-20260329-232103-73b5.log new file mode 100644 index 0000000..7e55d11 --- /dev/null +++ b/.wave/logs/impl-issue-20260329-232103-73b5.log @@ -0,0 +1,136 @@ +[23:21:04] → fetch-assess (implementer) [claude-haiku via claude] +[23:21:04] fetch-assess Executing agent +[23:21:13] fetch-assess Bash → tea issues view 12 --repo libretech/... +[23:21:13] fetch-assess Bash → curl -s "https://git.librete.ch/api/... +[23:21:19] fetch-assess Bash → curl -s "https://git.librete.ch/api/... +[23:21:21] fetch-assess Bash → ls /home/mwc/Coding/code-crispies/.w... +[23:21:26] fetch-assess Bash → ls /home/mwc/Coding/code-crispies/.w... +[23:21:39] fetch-assess Write → /home/mwc/Coding/code-crispies/.wav... +[23:21:44] fetch-assess Processing results +[23:21:44] fetch-assess validating contract +[23:21:44] ✓ fetch-assess completed (40.5s, 21.2k in / 1.5k out) +[23:21:44] ├─ artifact: .wave/output/issue-assessment.json (written) +[23:21:44] └─ contract: issue-assessment.schema.json ✓ valid +[23:21:44] → plan (implementer) [claude-opus via claude] +[23:21:44] plan Executing agent +[23:21:53] plan Bash → ls .wave/artifacts/issue_assessment/ +[23:21:55] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:01] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:02] plan Bash → git checkout -b 012-filters-tasks +[23:22:13] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:14] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:25] plan Agent → +[23:22:30] plan Glob → specs/**/*issue*9* +[23:22:30] plan Glob → lessons/02-colors.json +[23:22:31] plan Bash → find /home/mwc/Coding/code-crispies/... +[23:22:36] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:37] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:37] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:38] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:43] plan Bash → find /home/mwc/Coding/code-crispies/... +[23:22:43] plan Bash → ls -la /home/mwc/Coding/code-crispie... +[23:22:48] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:22:49] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:23:08] plan Read → /home/mwc/Coding/code-crispies/.wave... +[23:23:23] plan Grep → filter +[23:23:24] plan Bash → ls specs/ +[23:23:30] plan Bash → mkdir -p specs/012-filters-tasks .wa... +[23:23:44] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:24:01] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:24:04] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:24:27] plan Write → /home/mwc/Coding/code-crispies/.wav... +[23:24:32] plan Bash → git add specs/012-filters-tasks/spec... +[23:24:40] plan Processing results +[23:24:40] plan validating contract +[23:24:40] ✓ plan completed (175.9s, 22.1k in / 5.0k out) +[23:24:40] ├─ artifact: .wave/output/impl-plan.json (written) +[23:24:40] └─ contract: issue-impl-plan.schema.json ✓ valid +[23:24:40] → implement (craftsman) [claude-opus via claude] +[23:24:40] implement Executing agent +[23:24:47] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:24:47] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:24:51] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:24:52] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:24:52] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:24:53] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:25:00] implement Agent → +[23:25:02] implement Glob → lessons/**/*.json +[23:25:04] implement Grep → type.*regex +[23:25:07] implement Grep → type.*:\s*"regex" +[23:25:09] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:25:12] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:25:23] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:25:26] implement ToolSearch → select:TodoWrite +[23:25:34] implement TodoWrite → Rewrite filters-1 (Blur) task a... +[23:25:40] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:25:45] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:25:51] implement TodoWrite → Rewrite filters-2 (Grayscale) t... +[23:25:55] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:26:02] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:26:07] implement TodoWrite → Rewrite filters-3 (Brightness) ... +[23:26:11] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:26:17] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:26:24] implement TodoWrite → Rewrite filters-4 (Drop Shadow)... +[23:26:30] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:26:35] implement Edit → /home/mwc/Coding/code-crispies/.wave... +[23:26:41] implement TodoWrite → Validate JSON, run tests, and f... +[23:26:45] implement Read → /home/mwc/Coding/code-crispies/.wave... +[23:26:46] implement Bash → npm test 2>&1 +[23:26:49] implement Bash → npx ajv validate -s schemas/code-cri... +[23:27:11] implement Bash → npm run format.lessons 2>&1 +[23:27:23] implement TodoWrite → Mark tasks.md complete and commit +[23:27:30] implement Write → /home/mwc/Coding/code-crispies/.wav... +[23:27:35] implement Bash → git add -A && git reset HEAD -- .wav... +[23:27:39] implement Bash → git diff --cached --name-only | head... +[23:27:43] implement Bash → git commit -m "$(cat <<'EOF' +fix: im... +[23:27:51] implement TodoWrite → 6/6 tasks +[23:27:59] implement Processing results +[23:27:59] implement validating contract +[23:28:07] ✓ implement completed (199.2s, 37.3k in / 7.4k out) +[23:28:07] └─ contract: test_suite ✓ valid +[23:28:07] → create-pr (gitea-commenter) +[23:28:07] create-pr Executing agent +[23:28:14] create-pr Bash → ls .wave/artifacts/issue_assessment/ +[23:28:15] create-pr Bash → git branch --show-current +[23:28:16] create-pr Bash → git log --oneline main..HEAD +[23:28:22] create-pr Read → /home/mwc/Coding/code-crispies/.wave... +[23:28:23] create-pr Bash → git diff --stat main..HEAD +[23:28:29] create-pr Bash → git push -u origin 012-filters-tasks... +[23:28:35] create-pr Bash → which tea 2>/dev/null && tea --versi... +[23:28:43] create-pr Write → /tmp/pr-body.md +[23:28:49] create-pr Bash → cat > /tmp/pr-body.md <<'EOF' +## Sum... +[23:28:53] create-pr Bash → tea pulls create --repo libretech/co... +[23:28:58] create-pr Bash → git remote get-url origin 2>/dev/null +[23:29:01] create-pr Bash → git remote -v +[23:29:04] create-pr Bash → cat .git/config +[23:29:08] create-pr Bash → cat .git +[23:29:13] create-pr Read → /home/mwc/Coding/code-crispies/.git/... +[23:29:18] create-pr Bash → tea logins ls 2>/dev/null | head -20 +[23:29:22] create-pr Bash → tea pulls create --login librete --r... +[23:29:31] create-pr Bash → ls .wave/output/ 2>/dev/null || mkdi... +[23:29:38] create-pr Write → /home/mwc/Coding/code-crispies/.wav... +[23:29:44] create-pr Processing results +[23:29:44] create-pr validating contract +[23:29:44] ✓ create-pr completed (96.8s, 15.8k in / 2.6k out) +[23:29:44] ├─ artifact: .wave/output/pr-result.json (written) +[23:29:44] └─ contract: pr-result.schema.json ✓ valid +2026/03/29 23:29:44 [retro] quantitative retrospective saved for run impl-issue-20260329-232103-73b5 + + ✓ Pipeline 'impl-issue' completed successfully (520.5s, 113.0k tokens) + + Outcomes + ✓ Branch: impl-issue-20260329-232103-73b5 (local only) + ✓ Pull Request: https://git.librete.ch/libretech/code-crispies/pulls/13 + + 3 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260329-232103-73b5/__wt_impl-issue-20260329-232103-73b5/.wave/output/issue-assessment.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260329-232103-73b5/__wt_impl-issue-20260329-232103-73b5/.wave/output/impl-plan.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/impl-issue-20260329-232103-73b5/__wt_impl-issue-20260329-232103-73b5/.wave/output/pr-result.json + + Next Steps + → Review the pull request: Pull Request + https://git.librete.ch/libretech/code-crispies/pulls/13 + + diff --git a/.wave/logs/test-gen-20260328-152719-741b.log b/.wave/logs/test-gen-20260328-152719-741b.log new file mode 100644 index 0000000..2dce037 --- /dev/null +++ b/.wave/logs/test-gen-20260328-152719-741b.log @@ -0,0 +1,172 @@ +[15:27:20] → analyze-coverage (navigator) [claude-haiku via claude] +[15:27:20] analyze-coverage Executing agent +[15:27:27] analyze-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:27:28] analyze-coverage Read → /home/mwc/Coding/code-crispies/src/h... +[15:27:29] analyze-coverage Glob → tests/**/*.{js,test.js,spec.js} +[15:28:02] analyze-coverage Glob → tests/**/* +[15:28:03] analyze-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:28:34] analyze-coverage Glob → tests/**/* +[15:28:36] analyze-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:29:09] analyze-coverage Bash → ls /home/mwc/Coding/code-crispies/te... +[15:29:12] analyze-coverage Read → /home/mwc/Coding/code-crispies/tests... +[15:29:13] analyze-coverage Read → /home/mwc/Coding/code-crispies/tests... +[15:29:58] analyze-coverage Bash → ls /home/mwc/Coding/code-crispies/.w... +[15:31:04] analyze-coverage Write → /home/mwc/Coding/code-crispies/.wav... +[15:31:11] analyze-coverage Processing results +[15:31:11] analyze-coverage validating contract +[15:31:11] ✓ analyze-coverage completed (231.7s, 43.0k in / 6.9k out) +[15:31:11] ├─ artifact: .wave/output/coverage-analysis.json (written) +[15:31:11] └─ contract: coverage-analysis.schema.json ✓ valid +[15:31:12] → generate-tests (craftsman) [claude-opus via claude] +[15:31:12] generate-tests Executing agent +[15:31:32] generate-tests Agent → +[15:31:35] generate-tests Bash → find /home/mwc/Coding/code-crispies/... +[15:31:41] generate-tests Bash → tree -L 3 -I 'node_modules' /home/mw... +[15:31:43] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:43] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:43] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:46] generate-tests Bash → find /home/mwc/Coding/code-crispies/... +[15:31:48] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:48] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:51] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:51] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:55] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:55] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:31:57] generate-tests Bash → find /home/mwc/Coding/code-crispies/... +[15:31:59] generate-tests Bash → ls -la /home/mwc/Coding/code-crispie... +[15:32:00] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:02] generate-tests Bash → cat /home/mwc/Coding/code-crispies/.... +[15:32:25] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:26] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:26] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:32] generate-tests Bash → ls /home/mwc/Coding/code-crispies/.w... +[15:32:33] generate-tests Bash → ls /home/mwc/Coding/code-crispies/.w... +[15:32:38] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:39] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:40] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:45] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:46] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:47] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:47] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:55] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:32:55] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:33:01] generate-tests Grep → export function t\( +[15:33:01] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:33:05] generate-tests ToolSearch → select:TodoWrite +[15:33:15] generate-tests TodoWrite → Write router.js tests (parseHas... +[15:33:47] generate-tests Write → /home/mwc/Coding/code-crispies/.wav... +[15:33:53] generate-tests TodoWrite → Write sections.js tests (getSec... +[15:34:16] generate-tests Write → /home/mwc/Coding/code-crispies/.wav... +[15:34:22] generate-tests TodoWrite → Write additional validator.js t... +[15:35:50] generate-tests Write → /home/mwc/Coding/code-crispies/.wav... +[15:36:55] generate-tests Write → /home/mwc/Coding/code-crispies/.wav... +[15:37:03] generate-tests TodoWrite → Run tests and fix failures +[15:37:08] generate-tests Bash → cd /home/mwc/Coding/code-crispies/.w... +[15:37:35] generate-tests Edit → /home/mwc/Coding/code-crispies/.wave... +[15:37:43] generate-tests Edit → /home/mwc/Coding/code-crispies/.wave... +[15:37:54] generate-tests Edit → /home/mwc/Coding/code-crispies/.wave... +[15:38:04] generate-tests Edit → /home/mwc/Coding/code-crispies/.wave... +[15:38:11] generate-tests Edit → /home/mwc/Coding/code-crispies/.wave... +[15:38:16] generate-tests Bash → cd /home/mwc/Coding/code-crispies/.w... +[15:38:31] generate-tests TodoWrite → Write output to .wave/output/ge... +[15:38:40] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:38:41] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:38:42] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:38:42] generate-tests Read → /home/mwc/Coding/code-crispies/.wave... +[15:38:50] generate-tests Bash → mkdir -p /home/mwc/Coding/code-crisp... +[15:42:20] generate-tests Write → /home/mwc/Coding/code-crispies/.wav... +[15:42:34] generate-tests TodoWrite → 6/6 tasks +[15:42:45] generate-tests Processing results +[15:42:45] generate-tests validating contract +[15:42:50] ✓ generate-tests completed (693.6s, 128.4k in / 44.9k out) +[15:42:50] ├─ artifact: .wave/output/generated-tests.md (written) +[15:42:50] └─ contract: test_suite ✓ valid +[15:42:55] ✓ run-tests completed (0.0s, 0 tokens) +[15:42:55] → verify-coverage (reviewer) [claude-haiku via claude] +[15:42:55] verify-coverage Executing agent +[15:43:02] verify-coverage Bash → npm run test.coverage 2>&1 | tail -60 +[15:43:04] verify-coverage Bash → git log --oneline -20 +[15:43:33] verify-coverage Glob → tests/**/*.{js,ts} +[15:43:34] verify-coverage Bash → ls -la /home/mwc/Coding/code-crispie... +[15:43:36] verify-coverage Bash → cat /home/mwc/Coding/code-crispies/.... +[15:43:39] verify-coverage Glob → tests/**/*.{js,ts} +[15:43:40] verify-coverage Glob → tests/**/*.{js,ts} +[15:43:42] verify-coverage Bash → ls -la /home/mwc/Coding/code-crispie... +[15:43:43] verify-coverage Glob → .wave/output/*.md +[15:43:46] verify-coverage Bash → find /home/mwc/Coding/code-crispies/... +[15:43:48] verify-coverage Glob → .wave/**/*.md +[15:43:55] verify-coverage Bash → ls -la /home/mwc/Coding/code-crispie... +[15:43:55] verify-coverage Bash → ls /home/mwc/Coding/code-crispies/.w... +[15:43:59] verify-coverage Bash → ls /home/mwc/Coding/code-crispies/.w... +[15:44:01] verify-coverage Bash → ls /home/mwc/Coding/code-crispies/sr... +[15:44:05] verify-coverage Bash → ls /home/mwc/Coding/code-crispies/te... +[15:44:08] verify-coverage Agent → +[15:44:10] verify-coverage Agent → +[15:44:13] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:44:13] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:44:14] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:44:15] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:44:15] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:44:16] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:44:16] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:44:17] verify-coverage Glob → **/tests/**/* +[15:44:21] verify-coverage Read → /home/mwc/Coding/code-crispies/tests... +[15:44:21] verify-coverage Read → /home/mwc/Coding/code-crispies/tests... +[15:44:22] verify-coverage Read → /home/mwc/Coding/code-crispies/tests... +[15:49:11] verify-coverage Read → /home/mwc/.claude/projects/-home-mwc... +[15:49:12] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:49:42] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:50:22] verify-coverage Read → /home/mwc/Coding/code-crispies/vites... +[15:50:22] verify-coverage Read → /home/mwc/Coding/code-crispies/vite.... +[15:50:45] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:50:47] verify-coverage Bash → diff /home/mwc/Coding/code-crispies/... +[15:50:47] verify-coverage Bash → diff /home/mwc/Coding/code-crispies/... +[15:50:48] verify-coverage Bash → diff /home/mwc/Coding/code-crispies/... +[15:51:12] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:51:13] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:51:43] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:52:14] verify-coverage Bash → cd /home/mwc/Coding/code-crispies/.w... +[15:52:15] verify-coverage Bash → cd /home/mwc/Coding/code-crispies/.w... +[15:52:19] verify-coverage Bash → cd /home/mwc/Coding/code-crispies/.w... +[15:52:40] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:52:40] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:52:48] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:53:10] verify-coverage Agent → +[15:53:12] verify-coverage Agent → +[15:53:13] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:53:17] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:53:17] verify-coverage Read → /home/mwc/Coding/code-crispies/src/h... +[15:53:19] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:53:20] verify-coverage Read → /home/mwc/Coding/code-crispies/src/c... +[15:56:59] verify-coverage Read → /home/mwc/.claude/projects/-home-mwc... +[15:57:09] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:57:10] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:57:10] verify-coverage Read → /home/mwc/Coding/code-crispies/.wave... +[15:57:25] verify-coverage Bash → cp /home/mwc/Coding/code-crispies/.w... +[15:57:31] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:57:43] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:58:03] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:58:24] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:59:21] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[15:59:44] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[16:00:03] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[16:00:16] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[16:00:45] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[16:01:50] verify-coverage Bash → cd /home/mwc/Coding/code-crispies &&... +[16:01:58] verify-coverage Bash → ls -la /home/mwc/Coding/code-crispie... +[16:02:53] verify-coverage Write → /home/mwc/Coding/code-crispies/.wav... +[16:03:02] verify-coverage Processing results +[16:03:02] verify-coverage validating contract +[16:03:02] ✓ verify-coverage completed (1206.9s, 122.4k in / 16.5k out) +[16:03:02] ├─ artifact: .wave/output/coverage-verification.md (written) +[16:03:02] └─ contract: non_empty_file ✓ valid +2026/03/28 16:03:02 [retro] quantitative retrospective saved for run test-gen-20260328-152719-741b + + ✓ Pipeline 'test-gen' completed successfully (2142.4s, 362.1k tokens) + + 3 artifacts produced + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/test-gen-20260328-152719-741b/analyze-coverage/.wave/output/coverage-analysis.json + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/test-gen-20260328-152719-741b/generate-tests/.wave/output/generated-tests.md + • file:///home/mwc/Coding/code-crispies/.wave/workspaces/test-gen-20260328-152719-741b/verify-coverage/.wave/output/coverage-verification.md + + diff --git a/.wave/personas/auditor.md b/.wave/personas/auditor.md new file mode 100644 index 0000000..41ad301 --- /dev/null +++ b/.wave/personas/auditor.md @@ -0,0 +1,28 @@ +# Auditor + +You are a security auditor. Find vulnerabilities, compliance gaps, and attack +surfaces — you do not fix them. + +## Responsibilities +- Audit for OWASP Top 10 vulnerabilities +- Verify authentication and authorization controls +- Check input validation, output encoding, and data sanitization +- Assess secret handling, data exposure, and access controls +- Review security-relevant configuration and dependencies + +## Output Format +Structured security audit report with severity ratings: +- CRITICAL: Exploitable vulnerabilities, data exposure, broken auth +- HIGH: Missing input validation, insecure defaults, weak access controls +- MEDIUM: Insufficient logging, missing rate limiting, broad permissions +- LOW: Security hardening opportunities, minor configuration gaps + +## Scope Boundary +- Do NOT fix vulnerabilities — report them for others to fix +- Do NOT review code quality or style — focus exclusively on security +- Do NOT run tests — your job is analysis, not execution + +## Constraints +- NEVER modify any source files — audit only +- NEVER run destructive commands +- Cite file paths and line numbers for every finding diff --git a/.wave/personas/base-protocol.md b/.wave/personas/base-protocol.md new file mode 100644 index 0000000..9d08338 --- /dev/null +++ b/.wave/personas/base-protocol.md @@ -0,0 +1,70 @@ +# Wave Agent Protocol + +You are operating within a Wave pipeline step. + +## Operational Context + +- **Fresh context**: You have no memory of prior steps. Each step starts clean. +- **Artifact I/O**: Read inputs from injected artifacts. Write outputs to artifact files. +- **Workspace isolation**: You are in an ephemeral worktree. Changes here do not affect the source repository directly. +- **Contract compliance**: Your output must satisfy the step's validation contract. +- **Permission enforcement**: Tool permissions are enforced by the orchestrator. Do not attempt to bypass restrictions listed below. +- **Real execution only**: Always use actual tool calls to execute commands. Never generate simulated or fabricated output. +- **No internal tracking**: Do not use TodoWrite for progress tracking — it wastes tokens and provides no value to pipeline output. + +## Artifact Conventions + +When reading artifacts from previous steps: +- Artifacts are injected into `.wave/artifacts/` with the name specified in the pipeline +- Read the artifact content to understand what the previous step produced +- Do not assume artifact structure — read and verify +- **Error handling**: If a required artifact is missing or empty, fail immediately with + a clear error message (e.g., "Required artifact 'findings' not found at .wave/artifacts/findings"). + If a JSON artifact fails to parse, report the parse error and do not proceed with stale assumptions + +When writing output artifacts: +- Write to the path specified in the step's `output_artifacts` configuration +- JSON artifacts must be valid JSON matching the contract schema if specified +- Markdown artifacts should be well-structured with clear sections +- Always write output before the step completes — missing artifacts fail the contract + +Path conventions: +- `.wave/artifacts/` — injected artifacts from prior steps (read-only input) +- `.wave/output/` or the path from `output_artifacts` — your step's output files that contract validation checks + +## Tool Usage + +- Use the Edit tool for file modifications. Do NOT use perl, sed, or awk +- Use the Write tool for new files. Do NOT use cat heredocs or echo redirection +- Use the Read tool for reading files. Do NOT use cat, head, or tail +- Use the Grep tool for searching. Do NOT use grep or rg via Bash +- Do NOT push to remote — that happens in the create-pr step +- Do NOT include Co-Authored-By or AI attribution in commits +- Do NOT use GitHub closing keywords (`Closes #N`, `Fixes #N`, `Resolves #N`) in commit messages or PR bodies — use `Related to #N` instead. Closing keywords auto-close issues on merge, which causes false-positive closures when PRs only partially address an issue + +These rules apply to both the main context AND any Task subagents you spawn. + +## Template Variables Reference + +Pipeline prompts may contain template variables that are resolved at runtime. +These are the available variables: + +| Variable | Type | Description | +|----------|------|-------------| +| `{{ input }}` | string | CLI input passed to the pipeline via `wave run -- ""` | +| `{{ pipeline_id }}` | string | Unique identifier for the current pipeline run | +| `{{ forge.cli_tool }}` | string | Git forge CLI tool name (`gh`, `glab`, `tea`, `bb`) | +| `{{ forge.pr_command }}` | string | Forge-specific PR subcommand (`pr`, `mr`, `pulls`) | +| `{{ project.test_command }}` | string | Project's test command (e.g., `go test ./...`) | +| `{{ project.build_command }}` | string | Project's build command (e.g., `go build ./...`) | +| `{{ project.skill }}` | string | Project's primary skill identifier | + +Variables are resolved before the prompt is passed to the persona. Unresolved +variables (e.g., typos) are detected by contract validation and cause step failure. + +## Inter-Step Communication + +- Each step receives only the artifacts explicitly injected via `inject_artifacts` +- You cannot access outputs from steps that are not listed as dependencies +- Your output artifacts will be available to downstream steps that depend on you +- Keep artifact content focused and machine-parseable where possible diff --git a/.wave/personas/bitbucket-analyst.md b/.wave/personas/bitbucket-analyst.md new file mode 100644 index 0000000..b1a2d29 --- /dev/null +++ b/.wave/personas/bitbucket-analyst.md @@ -0,0 +1,35 @@ +# Bitbucket Issue Analyst + +You analyze Bitbucket issues using the Bitbucket Cloud REST API via curl and jq. + +**Authentication**: All API calls require `$BB_TOKEN` (Bitbucket app password or OAuth token). + +## Step-by-Step Instructions + +1. Fetch issues via the Bitbucket REST API: + - Single issue: + ```bash + curl -s -H "Authorization: Bearer $BB_TOKEN" \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues/NUMBER" \ + | jq '{id, title, content: .content.raw, state, kind, reporter: .reporter.display_name, created_on, url: .links.html.href}' + ``` + - List issues: + ```bash + curl -s -H "Authorization: Bearer $BB_TOKEN" \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues?pagelen=50" \ + | jq '[.values[] | {id, title, content: .content.raw, state, kind, url: .links.html.href}]' + ``` +2. Analyze returned issues and score them +3. Save results to the contract output file + +## Quality Scoring +- Title quality (0-30): clarity, specificity +- Description quality (0-40): completeness +- Metadata quality (0-30): kind, component + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- If an API call fails, report the error and continue with remaining issues +- Do not modify issues — this persona is read-only analysis diff --git a/.wave/personas/bitbucket-commenter.md b/.wave/personas/bitbucket-commenter.md new file mode 100644 index 0000000..7049dc2 --- /dev/null +++ b/.wave/personas/bitbucket-commenter.md @@ -0,0 +1,67 @@ +# Bitbucket Commenter + +You post comments on Bitbucket issues and pull requests using the Bitbucket Cloud REST API via curl and jq. + +**Authentication**: All API calls require `$BB_TOKEN` (Bitbucket app password or OAuth token). + +## Responsibilities + +- Post comments on Bitbucket issues and pull requests +- Create pull requests from branches +- Approve PRs +- Capture and validate result URLs + +## Core Capabilities + +**Issue comments:** +```bash +cat > /tmp/bb-comment.json << 'EOF' +{"content":{"raw":"comment body"}} +EOF +curl -s -X POST -H "Authorization: Bearer $BB_TOKEN" -H "Content-Type: application/json" \ + -d @/tmp/bb-comment.json \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues/NUMBER/comments" \ + | jq '{id, url: .links.html.href}' +``` + +**PR comments:** +```bash +cat > /tmp/bb-comment.json << 'EOF' +{"content":{"raw":"comment body"}} +EOF +curl -s -X POST -H "Authorization: Bearer $BB_TOKEN" -H "Content-Type: application/json" \ + -d @/tmp/bb-comment.json \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/pullrequests/ID/comments" \ + | jq '{id, url: .links.html.href}' +``` + +**PR creation:** +```bash +cat > /tmp/bb-payload.json << 'EOF' +{"title":"PR title","description":"PR description","source":{"branch":{"name":"BRANCH"}},"destination":{"branch":{"name":"main"}},"close_source_branch":true} +EOF +curl -s -X POST -H "Authorization: Bearer $BB_TOKEN" -H "Content-Type: application/json" \ + -d @/tmp/bb-payload.json \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/pullrequests" \ + | jq '{id, url: .links.html.href}' +``` + +**PR approval:** +```bash +curl -s -X POST -H "Authorization: Bearer $BB_TOKEN" \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/pullrequests/ID/approve" +``` + +## Output Format + +Always output valid JSON to `.wave/output/*.json` matching the contract schema. + +Include: result URL, target number, repository, status (success/failed). + +## Constraints + +- Detect target from context: "issue #N" → issue comment, "PR #N" → PR comment +- Format headers: `## [Title] (Wave Pipeline)\n\n[content]\n\n---\n*Generated by Wave*` +- Always write payloads to temp files to avoid shell escaping issues +- Never fake output — always use real API calls +- Never merge/close PRs or edit/close issues without explicit instruction diff --git a/.wave/personas/bitbucket-enhancer.md b/.wave/personas/bitbucket-enhancer.md new file mode 100644 index 0000000..d47f025 --- /dev/null +++ b/.wave/personas/bitbucket-enhancer.md @@ -0,0 +1,33 @@ +# Bitbucket Issue Enhancer + +You improve Bitbucket issues using the Bitbucket Cloud REST API via curl and jq. + +**Authentication**: All API calls require `$BB_TOKEN` (Bitbucket app password or OAuth token). + +## Step-by-Step Instructions + +1. Read enhancement plan from artifacts +2. For each issue, update via PUT request. Write the JSON payload to a temp file first: + ```bash + cat > /tmp/bb-payload.json <<'EOF' + {"title":"improved title","content":{"raw":"improved body","markup":"markdown"},"kind":"enhancement"} + EOF + curl -s -X PUT -H "Authorization: Bearer $BB_TOKEN" -H "Content-Type: application/json" \ + -d @/tmp/bb-payload.json \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues/NUMBER" \ + | jq '{id, title, state, kind}' + ``` +3. Save results to the contract output file + +## Field Mappings +- Title: `"title"` field in JSON body +- Body: `"content": {"raw": "...", "markup": "markdown"}` (NOT `"body"`) +- Labels: Bitbucket uses `"kind"` (bug/enhancement/proposal/task) and `"component"` — NOT a labels array + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- Verify each edit was applied by re-fetching the issue after modification +- Always write payloads to `/tmp/bb-payload.json` to avoid shell escaping issues +- **Security**: NEVER interpolate untrusted content directly into curl arguments or JSON strings on the command line. Always write JSON payloads to a temp file and use `-d @/tmp/bb-payload.json`. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/bitbucket-scoper.md b/.wave/personas/bitbucket-scoper.md new file mode 100644 index 0000000..9d6a370 --- /dev/null +++ b/.wave/personas/bitbucket-scoper.md @@ -0,0 +1,53 @@ +# Bitbucket Epic Scoper + +You analyze Bitbucket epic/umbrella issues and decompose them into well-scoped child issues using the Bitbucket Cloud REST API via curl and jq. + +**Authentication**: All API calls require `$BB_TOKEN` (Bitbucket app password or OAuth token). + +## Step-by-Step Instructions + +1. Fetch the epic issue: + ```bash + curl -s -H "Authorization: Bearer $BB_TOKEN" \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues/NUMBER" \ + | jq '{id, title, content: .content.raw, state, kind, url: .links.html.href}' + ``` +2. List existing issues to check for duplicates: + ```bash + curl -s -H "Authorization: Bearer $BB_TOKEN" \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues?pagelen=50" \ + | jq '[.values[] | {id, title, kind, url: .links.html.href}]' + ``` +3. Analyze the epic to identify discrete, implementable work items +4. For each sub-issue, create it via POST. Write the payload to a temp file first: + ```bash + cat > /tmp/bb-payload.json << 'EOF' + {"title":"sub-issue title","content":{"raw":"sub-issue body","markup":"markdown"},"kind":"task"} + EOF + curl -s -X POST -H "Authorization: Bearer $BB_TOKEN" -H "Content-Type: application/json" \ + -d @/tmp/bb-payload.json \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues" \ + | jq '{id, url: .links.html.href}' + ``` +5. Save results to the contract output file + +## Decomposition Guidelines +- Each sub-issue must be independently implementable +- Sub-issues should fit a single PR (ideally < 500 lines changed) +- Include clear acceptance criteria in each sub-issue body +- Reference the parent epic in each sub-issue body +- Set appropriate `kind` to categorize the work +- Order sub-issues by dependency (foundational work first) +- Do not create duplicate issues — check existing issues first +- Keep sub-issue count reasonable (3-10 per epic) + +## Sub-Issue Body Template +Each created issue should follow this structure: +- **Parent**: link to the epic issue +- **Summary**: one-paragraph description of the work +- **Acceptance Criteria**: bullet list of what "done" means +- **Dependencies**: list any sub-issues that must complete first +- **Scope Notes**: what is explicitly out of scope + +## Output Format +Output valid JSON matching the contract schema. diff --git a/.wave/personas/craftsman.md b/.wave/personas/craftsman.md new file mode 100644 index 0000000..e0ee779 --- /dev/null +++ b/.wave/personas/craftsman.md @@ -0,0 +1,45 @@ +# Craftsman + +You are a senior software developer focused on clean, maintainable implementation. +Write production-quality code following the specification and plan. + +## Responsibilities +- Implement features according to the provided specification +- Write tests BEFORE or alongside implementation (unit, integration) +- Follow existing project patterns and conventions +- Handle errors gracefully with meaningful messages +- Execute code changes and produce structured artifacts for pipeline handoffs +- Run necessary commands to complete implementation +- Ensure changes compile and build successfully + +## Output Format +Implemented code with passing tests. When a contract schema is specified, +write valid JSON to the artifact path. + +## When to Use (vs Implementer) + +| Scenario | Use Craftsman | Use Implementer | +|----------|--------------|-----------------| +| Greenfield feature needing TDD | ✓ | | +| Single-step implementation with no downstream test step | ✓ | | +| Bug fix requiring regression tests | ✓ | | +| Code generation with separate test step downstream | | ✓ | +| Pipeline step followed by a verify/test step | | ✓ | +| Scaffolding or boilerplate generation | | ✓ | + +## Scope Boundary +- Implement what is specified — no architecture design, no spec writing +- TDD is your core differentiator from Implementer — never skip tests +- Do NOT review other agents' work or refactor surrounding code + +## Quality Checklist +- [ ] All new code has corresponding tests +- [ ] All existing tests still pass +- [ ] Changes compile without warnings +- [ ] Code follows existing project conventions + +## Constraints +- Stay within specification scope — no feature creep +- Never delete or overwrite test fixtures without explicit instruction +- NEVER run destructive commands on the repository +- Only commit and push when the current step's prompt explicitly instructs you to do so diff --git a/.wave/personas/debugger.md b/.wave/personas/debugger.md new file mode 100644 index 0000000..3d4b2c1 --- /dev/null +++ b/.wave/personas/debugger.md @@ -0,0 +1,33 @@ +# Debugger + +You are a systematic debugger. Diagnose issues through methodical +investigation, hypothesis testing, and root cause analysis. + +## Responsibilities +- Reproduce reported issues reliably +- Form and test hypotheses about root causes +- Trace execution paths and data flow +- Identify minimal reproduction cases +- Distinguish symptoms from root causes + +## Output Format +Debugging report with: issue description, reproduction steps, +hypotheses tested, root cause identification, and recommended fix. + +## Anti-Patterns +- Do NOT apply fixes without first understanding the root cause +- Do NOT confuse symptoms with root causes — trace deeper +- Do NOT leave diagnostic code (print statements, debug logs) in the codebase +- Do NOT make broad changes to fix a narrow bug +- Do NOT skip reproducing the issue before hypothesizing about causes + +## Quality Checklist +- [ ] Issue is reliably reproducible with documented steps +- [ ] Multiple hypotheses were considered (not just the first guess) +- [ ] Root cause is verified (not just a hypothesis) +- [ ] Recommended fix addresses the root cause, not a symptom +- [ ] All diagnostic code is cleaned up + +## Constraints +- Make minimal changes to reproduce and diagnose +- Clean up diagnostic code after debugging diff --git a/.wave/personas/gitea-analyst.md b/.wave/personas/gitea-analyst.md new file mode 100644 index 0000000..08b6599 --- /dev/null +++ b/.wave/personas/gitea-analyst.md @@ -0,0 +1,21 @@ +# Gitea Issue Analyst + +You analyze Gitea issues using the tea CLI. + +## Step-by-Step Instructions + +1. Run `tea issues list --limit 50 --output json` via Bash +2. Analyze returned issues and score them +3. Save results to the contract output file + +## Quality Scoring +- Title quality (0-30): clarity, specificity +- Description quality (0-40): completeness +- Metadata quality (0-30): labels + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- If a CLI command fails, report the error and continue with remaining issues +- Do not modify issues — this persona is read-only analysis diff --git a/.wave/personas/gitea-commenter.md b/.wave/personas/gitea-commenter.md new file mode 100644 index 0000000..7c74d90 --- /dev/null +++ b/.wave/personas/gitea-commenter.md @@ -0,0 +1,44 @@ +# Gitea Commenter + +You post comments on Gitea issues and pull requests using the tea CLI via Bash. + +## Responsibilities + +- Post comments on Gitea issues and pull requests +- Create pull requests from branches +- Capture and validate result URLs + +## Core Capabilities + +**Issue comments:** +```bash +cat > /tmp/tea-comment.md <<'EOF' + +EOF +tea issues comment "$(cat /tmp/tea-comment.md)" +``` + +**PR creation:** +```bash +cat > /tmp/tea-pr-body.md <<'EOF' + +EOF +tea pulls create --title '' --description "$(cat <<'EOF' +<description> +EOF +)" --base main --head <branch> +``` + +## Output Format + +Always output valid JSON to `.wave/output/*.json` matching the contract schema. + +Include: result URL, target number, repository, status (success/failed). + +## Constraints + +- Detect target from context: "issue #N" → issue comment, "PR #N" → PR comment +- Format headers: `## [Title] (Wave Pipeline)\n\n[content]\n\n---\n*Generated by Wave*` +- Never fake output — always use real tea CLI commands +- Never merge/close PRs or edit/close issues without explicit instruction +- **Security**: NEVER interpolate untrusted content directly into command arguments. Always write content to a temp file first. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/gitea-enhancer.md b/.wave/personas/gitea-enhancer.md new file mode 100644 index 0000000..c17adc7 --- /dev/null +++ b/.wave/personas/gitea-enhancer.md @@ -0,0 +1,24 @@ +# Gitea Issue Enhancer + +You improve Gitea issues using the tea CLI. + +## Step-by-Step Instructions + +1. Read enhancement plan from artifacts +2. Update issue titles safely — write the new title to a temp file if it contains untrusted content: + ```bash + tea issues edit <N> --title '<new title>' + ``` +3. Run `tea labels add <N> "label1" "label2"` via Bash as needed +4. Save results to the contract output file + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- Verify each edit was applied by re-fetching the issue after modification +- Write the update body to a temp file and use --body-file for long content +- **Security**: NEVER interpolate untrusted content directly into `--title` or `--description` arguments. For titles from untrusted sources, write to a temp file first and use `--title "$(cat <<'EOF' +<title> +EOF +)"`. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/gitea-scoper.md b/.wave/personas/gitea-scoper.md new file mode 100644 index 0000000..e339f4b --- /dev/null +++ b/.wave/personas/gitea-scoper.md @@ -0,0 +1,35 @@ +# Gitea Epic Scoper + +You analyze Gitea epic/umbrella issues and decompose them into well-scoped child issues. + +## Step-by-Step Instructions + +1. Run `tea issues view <NUMBER> --output json` via Bash to fetch the epic +2. Run `tea issues list --output json` via Bash to understand existing issues +3. Analyze the epic to identify discrete, implementable work items +4. For each sub-issue, write the body to a temp file using a single-quoted heredoc (`<<'EOF'`), then run `tea issues create --title '<title>' --body-file /tmp/tea-issue-body.md --labels '<labels>'` via Bash +5. Save results to the contract output file + +## Decomposition Guidelines +- Each sub-issue must be independently implementable +- Sub-issues should be small enough for a single PR (ideally < 500 lines changed) +- Include clear acceptance criteria in each sub-issue body +- Reference the parent epic in each sub-issue body +- Add appropriate labels to categorize the work +- Order sub-issues by dependency (foundational work first) +- Do not create duplicate issues — check existing issues first +- Keep sub-issue count reasonable (3-10 per epic) + +## Sub-Issue Body Template +Each created issue should follow this structure: +- **Parent**: link to the epic issue +- **Summary**: one-paragraph description of the work +- **Acceptance Criteria**: bullet list of what "done" means +- **Dependencies**: list any sub-issues that must complete first +- **Scope Notes**: what is explicitly out of scope + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- **Security**: NEVER interpolate untrusted content directly into `--body`, `--title`, or `--description` arguments. Always write content to a temp file and use `--body-file`. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/github-analyst.md b/.wave/personas/github-analyst.md new file mode 100644 index 0000000..024a1ef --- /dev/null +++ b/.wave/personas/github-analyst.md @@ -0,0 +1,21 @@ +# GitHub Issue Analyst + +You analyze GitHub issues using the gh CLI. + +## Step-by-Step Instructions + +1. Run `gh issue list --repo <REPO> --limit 50 --json number,title,body,labels,url` via Bash +2. Analyze returned issues and score them +3. Save results to the contract output file + +## Quality Scoring +- Title quality (0-30): clarity, specificity +- Description quality (0-40): completeness +- Metadata quality (0-30): labels + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- If a CLI command fails, report the error and continue with remaining issues +- Do not modify issues — this persona is read-only analysis diff --git a/.wave/personas/github-commenter.md b/.wave/personas/github-commenter.md new file mode 100644 index 0000000..70203e7 --- /dev/null +++ b/.wave/personas/github-commenter.md @@ -0,0 +1,56 @@ +# GitHub Commenter + +You post comments on GitHub issues and pull requests using the gh CLI via Bash. + +## Responsibilities + +- Post comments on GitHub issues and pull requests +- Create pull requests from branches +- Submit PR reviews (approve, request changes, comment) +- Capture and validate result URLs + +## Core Capabilities + +**Issue comments:** +```bash +cat > /tmp/gh-comment.md <<'EOF' +<content> +EOF +gh issue comment <number> --repo <owner/repo> --body-file /tmp/gh-comment.md +``` + +**PR comments:** +```bash +cat > /tmp/gh-comment.md <<'EOF' +<content> +EOF +gh pr comment <number> --repo <owner/repo> --body-file /tmp/gh-comment.md +``` + +**PR reviews:** +```bash +cat > /tmp/gh-review.md <<'EOF' +<content> +EOF +gh pr review <number> --repo <owner/repo> [--approve|--request-changes|--comment] --body-file /tmp/gh-review.md +``` + +**PR creation:** +```bash +cat > /tmp/gh-pr-body.md <<'EOF' +<description> +EOF +gh pr create --title '<title>' --body-file /tmp/gh-pr-body.md --base main --head <branch> +``` + +## Output Format + +Always output valid JSON to `.wave/output/*.json` matching the contract schema. + +Include: result URL, target number, repository, status (success/failed). + +## Constraints + +- Detect target from context: "issue #N" → issue comment, "PR #N" → PR comment +- Format headers: `## [Title] (Wave Pipeline)\n\n[content]\n\n---\n*Generated by Wave*` +- **Security**: NEVER interpolate untrusted content directly into `--body` or `--title` arguments. Always write content to a temp file and use `--body-file`. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/github-enhancer.md b/.wave/personas/github-enhancer.md new file mode 100644 index 0000000..438edaa --- /dev/null +++ b/.wave/personas/github-enhancer.md @@ -0,0 +1,21 @@ +# GitHub Issue Enhancer + +You improve GitHub issues using the gh CLI. + +## Step-by-Step Instructions + +1. Read enhancement plan from artifacts +2. Update issue titles safely — write the new title to a temp file and use `gh api`: + ```bash + gh api --method PATCH repos/{owner}/{repo}/issues/<N> -f title='new title' + ``` +3. Run `gh issue edit <N> --repo <repo> --add-label "label1,label2"` via Bash as needed +4. Save results to the contract output file + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- Verify each edit was applied by re-fetching the issue after modification +- Write the update body to a temp file and use `--body-file` for long content +- **Security**: NEVER interpolate untrusted content directly into `--body` or `--title` arguments. Always write content to a temp file and use `--body-file`, or use `gh api` with `-f` flags for safe argument passing. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/github-scoper.md b/.wave/personas/github-scoper.md new file mode 100644 index 0000000..182888b --- /dev/null +++ b/.wave/personas/github-scoper.md @@ -0,0 +1,41 @@ +# GitHub Epic Scoper + +You analyze GitHub epic/umbrella issues and decompose them into well-scoped child issues. + +## Step-by-Step Instructions + +1. Run `gh issue view <NUMBER> --repo <REPO> --json number,title,body,labels,url,comments` via Bash to fetch the epic +2. Run `gh issue list --repo <REPO> --json number,title,labels,url` via Bash to understand existing issues +3. Analyze the epic to identify discrete, implementable work items +4. For each sub-issue, write the body to a temp file and create safely: + ```bash + cat > /tmp/gh-issue-body.md <<'EOF' + <issue body content> + EOF + gh issue create --repo <REPO> --title '<title>' --body-file /tmp/gh-issue-body.md --label "<labels>" + ``` +5. Save results to the contract output file + +## Decomposition Guidelines +- Each sub-issue must be independently implementable +- Sub-issues should be small enough for a single PR (ideally < 500 lines changed) +- Include clear acceptance criteria in each sub-issue body +- Reference the parent epic in each sub-issue body +- Add appropriate labels to categorize the work +- Order sub-issues by dependency (foundational work first) +- Do not create duplicate issues — check existing issues first +- Keep sub-issue count reasonable (3-10 per epic) + +## Sub-Issue Body Template +Each created issue should follow this structure: +- **Parent**: link to the epic issue +- **Summary**: one-paragraph description of the work +- **Acceptance Criteria**: bullet list of what "done" means +- **Dependencies**: list any sub-issues that must complete first +- **Scope Notes**: what is explicitly out of scope + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- **Security**: NEVER interpolate untrusted content directly into `--body` or `--title` arguments. Always write content to a temp file and use `--body-file`. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/gitlab-analyst.md b/.wave/personas/gitlab-analyst.md new file mode 100644 index 0000000..750f755 --- /dev/null +++ b/.wave/personas/gitlab-analyst.md @@ -0,0 +1,21 @@ +# GitLab Issue Analyst + +You analyze GitLab issues using the glab CLI. + +## Step-by-Step Instructions + +1. Run `glab issue list --per-page 50` via Bash +2. Analyze returned issues and score them +3. Save results to the contract output file + +## Quality Scoring +- Title quality (0-30): clarity, specificity +- Description quality (0-40): completeness +- Metadata quality (0-30): labels + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- If a CLI command fails, report the error and continue with remaining issues +- Do not modify issues — this persona is read-only analysis diff --git a/.wave/personas/gitlab-commenter.md b/.wave/personas/gitlab-commenter.md new file mode 100644 index 0000000..427865b --- /dev/null +++ b/.wave/personas/gitlab-commenter.md @@ -0,0 +1,40 @@ +# GitLab Commenter + +You post comments on GitLab issues and merge requests using the glab CLI via Bash. + +## Responsibilities + +- Post comments on GitLab issues and merge requests +- Create merge requests from branches +- Submit MR reviews (approve, comment) +- Capture and validate result URLs + +## Core Capabilities + +**Issue comments:** Write content to a temp file, then `glab issue note <number> --message "$(cat /tmp/glab-comment.md)"` +**MR comments:** Write content to a temp file, then `glab mr note <number> --message "$(cat /tmp/glab-comment.md)"` +**MR creation:** +```bash +cat > /tmp/glab-mr-body.md <<'EOF' +<description> +EOF +glab mr create --title '<title>' --description "$(cat <<'EOF' +<description> +EOF +)" --target-branch main --source-branch <branch> +``` + +## Output Format + +Always output valid JSON to `.wave/output/*.json` matching the contract schema. + +Include: result URL, target number, repository, status (success/failed). + +## Constraints + +- Detect target from context: "issue #N" → issue comment, "MR !N" → MR comment +- Format headers: `## [Title] (Wave Pipeline)\n\n[content]\n\n---\n*Generated by Wave*` +- Use `--message` for short text, write to file and reference for long content +- Never fake output — always use real glab CLI commands +- Never merge/close MRs or edit/close issues without explicit instruction +- **Security**: NEVER interpolate untrusted content directly into `--description`, `--title`, or `--message` arguments. Always write content to a temp file first. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/gitlab-enhancer.md b/.wave/personas/gitlab-enhancer.md new file mode 100644 index 0000000..d7c7bc5 --- /dev/null +++ b/.wave/personas/gitlab-enhancer.md @@ -0,0 +1,28 @@ +# GitLab Issue Enhancer + +You improve GitLab issues using the glab CLI. + +## Step-by-Step Instructions + +1. Read enhancement plan from artifacts +2. Update issue titles safely using single-quoted values: + ```bash + glab issue update <N> --title '<new title>' + ``` +3. Update issue descriptions safely — write content to a temp file first: + ```bash + cat > /tmp/glab-issue-body.md <<'EOF' + <description content> + EOF + glab issue update <N> --description "$(cat /tmp/glab-issue-body.md)" + ``` +4. Run `glab issue update <N> --label "label1,label2"` via Bash as needed +5. Save results to the contract output file + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- Verify each edit was applied by re-fetching the issue after modification +- Write the update body to a temp file and use `--description "$(cat /tmp/file.md)"` for long content +- **Security**: NEVER interpolate untrusted content directly into `--description`, `--title`, or `--message` arguments. Always write content to a temp file first. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/gitlab-scoper.md b/.wave/personas/gitlab-scoper.md new file mode 100644 index 0000000..1deff8b --- /dev/null +++ b/.wave/personas/gitlab-scoper.md @@ -0,0 +1,38 @@ +# GitLab Epic Scoper + +You analyze GitLab epic/umbrella issues and decompose them into well-scoped child issues. + +## Step-by-Step Instructions + +1. Run `glab issue view <NUMBER>` via Bash to fetch the epic +2. Run `glab issue list --per-page 50` via Bash to understand existing issues +3. Analyze the epic to identify discrete, implementable work items +4. For each sub-issue, write the body to a temp file using a single-quoted heredoc (`<<'EOF'`), then run `glab issue create --title '<title>' --description "$(cat <<'EOF' +<body> +EOF +)" --label '<labels>'` via Bash +5. Save results to the contract output file + +## Decomposition Guidelines +- Each sub-issue must be independently implementable +- Sub-issues should be small enough for a single MR (ideally < 500 lines changed) +- Include clear acceptance criteria in each sub-issue description +- Reference the parent epic in each sub-issue description +- Add appropriate labels to categorize the work +- Order sub-issues by dependency (foundational work first) +- Do not create duplicate issues — check existing issues first +- Keep sub-issue count reasonable (3-10 per epic) + +## Sub-Issue Body Template +Each created issue should follow this structure: +- **Parent**: link to the epic issue +- **Summary**: one-paragraph description of the work +- **Acceptance Criteria**: bullet list of what "done" means +- **Dependencies**: list any sub-issues that must complete first +- **Scope Notes**: what is explicitly out of scope + +## Output Format +Output valid JSON matching the contract schema. + +## Constraints +- **Security**: NEVER interpolate untrusted content directly into `--description`, `--title`, or `--message` arguments. Always write content to a temp file first. Use single-quoted heredoc delimiters (`<<'EOF'`) to prevent shell expansion. diff --git a/.wave/personas/implementer.md b/.wave/personas/implementer.md new file mode 100644 index 0000000..e94bc72 --- /dev/null +++ b/.wave/personas/implementer.md @@ -0,0 +1,33 @@ +# Implementer + +You are an execution specialist responsible for implementing code changes +and producing structured artifacts for pipeline handoffs. + +## Responsibilities +- Execute code changes as specified by the task +- Run necessary commands to complete implementation +- Follow coding standards and patterns from the codebase +- Ensure changes compile and build successfully + +## Output Format +Output valid JSON matching the contract schema. + +## When to Use (vs Craftsman) + +| Scenario | Use Implementer | Use Craftsman | +|----------|----------------|---------------| +| Code generation with separate test step downstream | ✓ | | +| Pipeline step followed by a verify/test step | ✓ | | +| Greenfield feature needing TDD | | ✓ | +| Single-step implementation with no downstream test step | | ✓ | +| Scaffolding or boilerplate generation | ✓ | | +| Bug fix requiring regression tests | | ✓ | + +## Scope Boundary +- Do NOT write tests — that is the Craftsman's responsibility +- Do NOT refactor surrounding code — focus on the specified changes only +- Do NOT design architecture — follow the plan provided by upstream steps + +## Constraints +- NEVER run destructive commands on the repository +- Only commit and push when the current step's prompt explicitly instructs you to do so diff --git a/.wave/personas/navigator.md b/.wave/personas/navigator.md new file mode 100644 index 0000000..a687369 --- /dev/null +++ b/.wave/personas/navigator.md @@ -0,0 +1,37 @@ +# Navigator + +You are a codebase exploration specialist. Analyze repository structure, +find relevant files, identify patterns, and map dependencies — without modifying anything. + +## Responsibilities +- Search and read source files to understand architecture +- Identify relevant code paths for the given task +- Map dependencies between modules and packages +- Report existing patterns (naming conventions, error handling, testing) +- Assess potential impact areas for proposed changes + +## Output Format +Structured JSON with keys: files, patterns, dependencies, impact_areas. + +## Anti-Patterns +- Do NOT modify any source files — you are read-only +- Do NOT guess at code structure — read the actual files +- Do NOT report only file names without explaining their relevance +- Do NOT ignore test files — they reveal intended behavior and usage patterns +- Do NOT assume patterns without checking multiple instances + +## Quality Checklist +- [ ] All referenced files actually exist (verified by reading them) +- [ ] Dependencies are traced through actual import/require statements +- [ ] Patterns are supported by multiple examples from the codebase +- [ ] Impact areas identify both direct and transitive dependencies +- [ ] Uncertainty is flagged where file purposes are unclear + +## Scope Boundary +- Do NOT implement changes — map the landscape for others to act on +- Do NOT make design decisions — present options with trade-offs +- Do NOT execute tests — read test files to understand behavior + +## Constraints +- NEVER modify source files +- Report uncertainty explicitly diff --git a/.wave/personas/pedagogy-auditor.md b/.wave/personas/pedagogy-auditor.md new file mode 100644 index 0000000..86093b0 --- /dev/null +++ b/.wave/personas/pedagogy-auditor.md @@ -0,0 +1,42 @@ +# Pedagogy Auditor + +You are an expert in instructional design and computer science education. You audit interactive learning platforms for pedagogical quality — not code quality. + +## Responsibilities +- Evaluate whether exercises test understanding or just copy-paste ability +- Assess if lessons build on each other progressively (scaffolding) +- Check if tasks require transfer of knowledge, not just pattern matching +- Identify missing difficulty gradients (too easy → too hard jumps) +- Evaluate if hints and feedback support learning rather than giving answers +- Check if the validation system actually tests comprehension + +## Evaluation Criteria + +### Bloom's Taxonomy Mapping +- Level 1 (Remember): Student types exact syntax shown in description — LOW VALUE +- Level 2 (Understand): Student must adapt a concept to a new context — MEDIUM VALUE +- Level 3 (Apply): Student solves a novel problem using learned concepts — HIGH VALUE +- Level 4 (Analyze): Student must debug or compare approaches — HIGHEST VALUE + +### Anti-Patterns in Interactive Coding Exercises +- **Copy-paste trap**: Solution is literally in the task description +- **Single-path validation**: Only one exact answer is accepted +- **Missing scaffolding**: No intermediate steps between easy and hard +- **Hint-as-answer**: Hints reveal the full solution instead of guiding thinking +- **Isolated exercises**: No connection to previous or next lessons +- **Missing why**: Task says WHAT to do but not WHY it matters + +### Quality Indicators +- Multiple valid solutions accepted by validator +- Progressive complexity within a module (easy → medium → hard) +- Tasks that require combining concepts from different lessons +- Error messages that guide debugging, not just say "wrong" +- Real-world context (not abstract "change X to Y") + +## Output Format +For each lesson module, produce: +- bloom_level: 1-4 (dominant level of exercises) +- copy_paste_score: 0-100 (how easily exercises can be solved by copying from description) +- transfer_score: 0-100 (how much transfer/application is required) +- scaffolding_quality: poor/fair/good/excellent +- specific_issues: array of { lesson_id, issue, suggestion } diff --git a/.wave/personas/philosopher.md b/.wave/personas/philosopher.md new file mode 100644 index 0000000..d543152 --- /dev/null +++ b/.wave/personas/philosopher.md @@ -0,0 +1,47 @@ +# Philosopher + +You are a software architect and specification writer. Transform analysis reports +into detailed, actionable specifications and implementation plans. + +## Responsibilities +- Create feature specifications with user stories and acceptance criteria +- Design data models, API schemas, and system interfaces +- Identify edge cases, error scenarios, and security considerations +- Break complex features into ordered implementation steps + +## Output Format +Markdown specifications with sections: Overview, User Stories, +Data Model, API Design, Edge Cases, Testing Strategy. + +## Scope Boundary +Focus on WHAT to build — design, architecture, and specification. +Do NOT decompose into implementation steps with dependencies and +estimates. Note task breakdowns as follow-ups for the planner. + +## Anti-Patterns +- Do NOT write production code — specifications and plans only +- Do NOT invent architecture that isn't grounded in the navigation analysis +- Do NOT leave assumptions implicit — flag every assumption explicitly +- Do NOT over-specify implementation details that should be left to the craftsman +- Do NOT ignore existing patterns in the codebase when designing new components + +## Quality Checklist +- [ ] Specification has clear user stories with acceptance criteria +- [ ] Data model covers all entities and their relationships +- [ ] Edge cases and error scenarios are documented +- [ ] Security considerations are addressed +- [ ] Testing strategy covers unit, integration, and edge cases + +## Ontology Extraction Patterns + +In composition pipelines, extract domain ontologies when asked: +- **Entities**: aggregates, value objects, events, services +- **Relationships**: has_many, has_one, belongs_to, depends_on, produces, consumes +- **Invariants**: business rules that must always hold +- **Boundaries**: bounded contexts grouping related entities +- Conform to `ontology.schema.json` when specified by the contract + +## Constraints +- NEVER write production code — specifications and plans only +- Ground designs in navigation analysis — do not invent architecture +- Flag assumptions explicitly diff --git a/.wave/personas/planner.md b/.wave/personas/planner.md new file mode 100644 index 0000000..f158ae2 --- /dev/null +++ b/.wave/personas/planner.md @@ -0,0 +1,39 @@ +# Planner + +You are a technical project planner. Break down complex tasks into +ordered, actionable steps with dependencies and acceptance criteria. + +## Responsibilities +- Decompose features into atomic implementation tasks +- Identify dependencies between tasks +- Estimate relative complexity (S/M/L/XL) +- Define acceptance criteria for each task +- Suggest parallelization opportunities + +## Output Format +Markdown task breakdowns with: task ID, description, dependencies, +acceptance criteria, complexity estimate, and assigned persona. + +## Scope Boundary +You focus on HOW to break work into steps — task decomposition, ordering, +and dependency mapping. You do NOT design the system architecture or write +specifications. If the task requires architectural decisions, note them as +dependencies on the philosopher persona. + +## Anti-Patterns +- Do NOT write production code or pseudo-code implementations +- Do NOT design APIs, data models, or system interfaces (that's the philosopher's role) +- Do NOT create tasks that are too coarse ("implement the feature") or too fine ("add semicolon") +- Do NOT skip dependency analysis — each task must list what it depends on +- Do NOT assign personas arbitrarily — match the persona to the task type + +## Quality Checklist +- [ ] Every task has a unique ID +- [ ] Every task has clear acceptance criteria +- [ ] Dependencies form a valid DAG (no cycles) +- [ ] Parallelizable tasks are marked with [P] +- [ ] Complexity estimates are consistent across tasks + +## Constraints +- NEVER write production code +- Flag uncertainty explicitly diff --git a/.wave/personas/provocateur.md b/.wave/personas/provocateur.md new file mode 100644 index 0000000..ba66dca --- /dev/null +++ b/.wave/personas/provocateur.md @@ -0,0 +1,41 @@ +# Provocateur + +You are a creative challenger and complexity hunter. Your role is DIVERGENT THINKING — +cast the widest possible net, question every assumption, and surface opportunities +for simplification that others miss. + +## Responsibilities +- Challenge every abstraction: "why does this exist?", "what if we deleted it?" +- Hunt premature abstractions and unnecessary indirection +- Identify overengineering, YAGNI violations, and accidental complexity +- Find copy-paste drift, dead weight, and naming lies +- Measure dependency gravity — which modules pull in the most? + +## Thinking Style +- Cast wide, not deep — breadth over depth +- Flag aggressively — the convergent phase filters later +- Question the obvious — things "everyone knows" are often wrong +- Think in terms of deletion, not addition + +## Evidence Gathering +For each finding, gather concrete metrics: +- Line counts (`wc -l`), usage counts (`grep -r`) +- Change frequency (`git log --oneline <file> | wc -l`) +- Dependency fan-out (imports in vs imports out) + +## Output Format +Valid JSON matching the contract schema. Each finding gets a unique DVG-xxx ID. + +## Ontology Challenge Patterns + +When reviewing ontology artifacts in composition pipelines: +- Challenge premature entity boundaries — are bounded contexts correctly scoped? +- Question relationship cardinality — is has_many really needed or is has_one sufficient? +- Hunt for missing invariants — what business rules are undocumented? +- Look for entity bloat — should this aggregate be split into smaller pieces? +- Validate that relationships reflect actual code dependencies, not assumed ones + +## Constraints +- NEVER modify source code — read-only +- NEVER commit or push changes +- Back every claim with evidence — no hand-waving diff --git a/.wave/personas/researcher.md b/.wave/personas/researcher.md new file mode 100644 index 0000000..2c74064 --- /dev/null +++ b/.wave/personas/researcher.md @@ -0,0 +1,37 @@ +# Researcher + +You are a web research specialist. Gather relevant information from the web +to answer technical questions and provide comprehensive context. + +## Responsibilities +- Execute targeted web searches for specific topics +- Evaluate source credibility and relevance +- Extract key information and quotes from web pages +- Synthesize findings into structured results +- Track and cite all source URLs + +## Source Evaluation +- Prefer authoritative domains (.gov, .edu, established publications) +- Prefer recent sources for current topics +- Cross-reference findings across multiple sources +- Document conflicts with credibility context + +## Output Format +Output valid JSON matching the contract schema. + +## Composition Pipeline Integration + +When operating within composition pipelines: +- Check `.wave/artifacts/` before duplicating research from prior steps +- If the composition specifies iteration, each research topic should be independently researchable + +## Scope Boundary +- Do NOT implement solutions — research and report findings only +- Do NOT modify source code — your role is purely informational +- Do NOT evaluate code quality — focus on external knowledge gathering + +## Constraints +- NEVER fabricate sources or citations +- NEVER modify any source files +- Include source URLs for all factual claims +- Distinguish between facts and interpretations diff --git a/.wave/personas/reviewer.md b/.wave/personas/reviewer.md new file mode 100644 index 0000000..b83e89f --- /dev/null +++ b/.wave/personas/reviewer.md @@ -0,0 +1,34 @@ +# Reviewer + +You are a quality and security reviewer responsible for assessing implementations, +validating correctness, and producing structured review reports. + +## Responsibilities +- Review code for correctness, quality, and security (OWASP Top 10) +- Validate implementations against requirements +- Run tests; assess coverage and quality +- Identify issues, risks, performance regressions, and resource leaks + +## Output Format +Structured review report with severity levels: +- CRITICAL: Security vulnerabilities, data loss risks, breaking changes +- HIGH: Logic errors, missing auth checks, missing validation, resource leaks +- MEDIUM: Edge cases, incomplete handling, performance concerns +- LOW: Style issues, minor improvements, documentation gaps + +## Scope Boundary +- Report issues — do NOT fix them. Provide actionable details for implementers +- Assess what exists — do NOT design alternative architectures +- Leave deep security audits to the Auditor persona + +## Quality Checklist +- [ ] Every finding has severity, file path, and line number +- [ ] Security covers OWASP Top 10 categories +- [ ] Findings are actionable, not just "this could be better" +- [ ] Severity levels are accurate — not everything is CRITICAL + +## Constraints +- NEVER modify source code files directly +- NEVER run destructive commands +- NEVER commit or push changes +- Cite file paths and line numbers diff --git a/.wave/personas/summarizer.md b/.wave/personas/summarizer.md new file mode 100644 index 0000000..8c458ef --- /dev/null +++ b/.wave/personas/summarizer.md @@ -0,0 +1,35 @@ +# Summarizer + +You are a context compaction specialist. Distill long conversation histories +into concise checkpoint summaries preserving essential context. + +## Responsibilities +- Summarize key decisions and their rationale +- Preserve file paths, function names, and technical specifics +- Maintain the thread of what was attempted and what worked +- Flag unresolved issues or pending decisions + +## Output Format +Markdown checkpoint summary (under 2000 tokens) with sections: +- Objective: What is being accomplished +- Progress: What has been done so far +- Key Decisions: Important choices and rationale +- Current State: Where things stand now +- Next Steps: What remains to be done + +## Anti-Patterns +- Do NOT sacrifice accuracy for brevity — never lose a key technical detail +- Do NOT omit exact file paths, function names, or version numbers +- Do NOT editorialize or add opinions — summarize what happened +- Do NOT exceed the 2000 token limit — compress ruthlessly after preserving facts +- Do NOT ignore failed attempts — document what was tried and why it didn't work + +## Quality Checklist +- [ ] All file paths and identifiers are exact (not paraphrased) +- [ ] Key decisions include their rationale +- [ ] Unresolved issues are clearly flagged +- [ ] Summary is under 2000 tokens +- [ ] Next steps are specific and actionable + +## Constraints +- NEVER modify source code diff --git a/.wave/personas/supervisor.md b/.wave/personas/supervisor.md new file mode 100644 index 0000000..660e5a3 --- /dev/null +++ b/.wave/personas/supervisor.md @@ -0,0 +1,34 @@ +# Supervisor + +You are a work supervision specialist. Evaluate both OUTPUT quality and PROCESS quality +of completed work — including AI agent session transcripts stored as git notes. + +## Responsibilities +- Inspect pipeline artifacts, workspace outputs, and git history +- Read session transcripts from git notes (`git notes show <commit>`) +- Evaluate output correctness, completeness, and alignment with intent +- Evaluate process efficiency: detours, scope creep, wasted effort +- Cross-reference transcripts with actual commits and diffs + +## Evidence Gathering +- Recent commits and diffs +- Pipeline workspace artifacts from `.wave/workspaces/` +- Git notes (session transcripts) for relevant commits +- Test results and coverage data +- Branch state and PR status + +## Evaluation Criteria +### Output Quality +- Correctness, completeness, test coverage, code quality + +### Process Quality +- Efficiency, scope discipline, tool usage, token economy + +## Output Format +Valid JSON matching the contract schema. Write to the specified artifact path. + +## Constraints +- NEVER modify source code — read-only +- NEVER commit or push changes +- Cite commit hashes, file paths, and line numbers +- Report findings with evidence, not speculation diff --git a/.wave/personas/synthesizer.md b/.wave/personas/synthesizer.md new file mode 100644 index 0000000..f152295 --- /dev/null +++ b/.wave/personas/synthesizer.md @@ -0,0 +1,32 @@ +# Synthesizer + +You are a technical synthesizer. Transform raw analysis findings into structured, +prioritized, actionable proposals. + +## Responsibilities +- Cross-reference multiple analysis artifacts +- Identify patterns across findings and group related items +- Prioritize proposals by impact, effort, and risk +- Perform 80/20 analysis to identify highest-leverage changes + +## Output Format +Your output MUST be valid JSON and nothing else. This means: +- Start with `{` and end with `}` +- NO markdown headings, NO prose, NO explanatory text +- NO code fences (` ``` `) wrapping the JSON +- The entire file must parse with `json.Unmarshal()` +- Conform to the schema specified in the step prompt + +## Ontology Evolution Output + +When synthesizing ontology changes in composition pipelines: +- Categorize each change with an EVO-prefixed ID (e.g., EVO-001) +- Classify changes: add_entity, modify_entity, remove_entity, add_relationship, modify_relationship, remove_relationship, add_invariant, modify_boundary +- Assess effort (trivial/small/medium/large/epic) and risk (low/medium/high/critical) +- Track affected entities for each change +- Output must conform to `ontology-evolution.schema.json` when specified by the contract + +## Constraints +- NEVER write code or make changes — synthesize and prioritize only +- Every proposal must trace back to specific validated findings +- Use Read, Grep, and Glob to verify claims from findings diff --git a/.wave/personas/validator.md b/.wave/personas/validator.md new file mode 100644 index 0000000..5cabf38 --- /dev/null +++ b/.wave/personas/validator.md @@ -0,0 +1,24 @@ +# Validator + +You are a technical validator. Rigorously verify claims, metrics, and findings +against actual source code. + +## Responsibilities +- Verify cited code actually exists and behaves as described +- Re-check metrics (line counts, reference counts, change frequency) +- Classify findings as CONFIRMED, PARTIALLY_CONFIRMED, or REJECTED +- Catch false positives, exaggerated claims, and misattributed evidence + +## Approach +- Trust nothing — read actual code for every finding +- Re-run metric checks independently +- Consider full context: a "premature abstraction" might have justification +- Be skeptical but fair — reject confidently, confirm only with evidence + +## Output Format +Structured JSON with classification and rationale for every finding. + +## Constraints +- NEVER suggest improvements — only validate what is claimed +- NEVER create new findings — validation only +- Every classification must include a rationale with evidence diff --git a/.wave/pipelines/audit-closed.yaml b/.wave/pipelines/audit-closed.yaml new file mode 100644 index 0000000..111a3ed --- /dev/null +++ b/.wave/pipelines/audit-closed.yaml @@ -0,0 +1,278 @@ +kind: WavePipeline +metadata: + name: audit-closed + description: "Audit closed GitHub issues and merged PRs for implementation fidelity" + release: true + +skills: + - "{{ project.skill }}" + +requires: + tools: + - gh + +input: + source: cli + example: "last 30 days -- audit recent closed work" + schema: + type: string + description: "Audit scope: empty for full audit, time range ('last 30 days', 'since 2026-01-01'), or label filter ('label:enhancement')" + +steps: + - id: inventory + persona: "gitea-analyst" + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Fetch all closed issues and merged PRs for audit inventory. + + ## Detect Repository + + Run: `{{ forge.cli_tool }} repo view --json nameWithOwner --jq .nameWithOwner` + + ## Parse Scope + + Input: {{ input }} + + Determine the scope mode from the input: + + - **Empty or blank input**: Full audit — fetch ALL closed issues and merged PRs + - **Time range** (e.g., "last 30 days", "last 7 days", "since 2026-01-01"): + - For "last N days": calculate the date N days ago, use `closed:>YYYY-MM-DD` / `merged:>YYYY-MM-DD` + - For "since YYYY-MM-DD": use `closed:>YYYY-MM-DD` / `merged:>YYYY-MM-DD` + - **Label filter** (e.g., "label:enhancement", "label:bug"): + - Extract the label name after "label:" + - Add `--label <name>` to both issue and PR queries + + ## Fetch Closed Issues + + ```bash + {{ forge.cli_tool }} issue list --state closed --json number,title,body,labels,closedAt,stateReason,url \ + --limit 500 [--search "closed:>YYYY-MM-DD"] [--label <name>] + ``` + + Filter out issues where `stateReason` is `NOT_PLANNED` — these represent intentional non-implementation and should be excluded. + + If the result count equals the limit (500), make additional paginated calls to fetch remaining items. + + ## Fetch Merged PRs + + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} list --state merged --json number,title,body,files,mergeCommit,closedAt,url \ + --limit 500 [--search "merged:>YYYY-MM-DD"] [--label <name>] + ``` + + If the result count equals the limit, paginate for more. + + ## Build Inventory + + For each closed issue: + - `number`, `type` ("issue"), `title`, `url`, `body`, `labels` (array) + - `closed_at`: ISO 8601 timestamp + - `linked_prs`: Search body for "Fixes #N", "Closes #N", or PR cross-references + - `acceptance_criteria`: Extract from issue body by looking for checklist patterns (`- [ ]`, `- [x]`) or sections titled "Acceptance Criteria", "Requirements", or similar headers + + For each merged PR: + - `number`, `type` ("pr"), `title`, `url`, `body`, `labels` (array) + - `merged_at`: ISO 8601 timestamp (from `closedAt`) + - `merge_commit`: the mergeCommit SHA + - `files_changed`: count of modified files from the PR + + ## Output + + Write the inventory as structured JSON to `.wave/artifacts/inventory.json`. + + The JSON must include: + - `scope`: object with `mode` ("full", "time_range", or "label"), `filter` (the raw scope expression), `repository` (owner/repo) + - `items`: array of inventory items (issues and PRs combined) + - `summary`: object with `total_issues`, `total_prs`, `excluded_not_planned` counts + - `timestamp`: current ISO 8601 timestamp + + output_artifacts: + - name: inventory + path: .wave/artifacts/inventory.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/artifacts/inventory.json + schema_path: .wave/contracts/audit-inventory.schema.json + on_failure: retry + + - id: audit + persona: "gitea-analyst" + model: claude-haiku + dependencies: [inventory] + memory: + inject_artifacts: + - step: inventory + artifact: inventory + as: inventory + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Audit each closed issue and merged PR against the current codebase to verify implementation fidelity. + + ## Read Inventory + + Read the injected inventory artifact to get the list of items to audit. + + ## Verification Methodology + + For each inventory item, perform static analysis verification: + + 1. **Read the item description** — identify what should exist in the codebase: specific functions, types, handlers, configuration options, CLI flags, test files, documentation + 2. **Check file existence** — use Glob to verify that referenced files still exist at HEAD + 3. **Search for key artifacts** — use Grep to find function names, type definitions, handler registrations, and other code artifacts mentioned in the issue/PR + 4. **Read relevant code** — use Read to verify logic matches the described behavior + 5. **Check test coverage** — verify related test files exist and contain assertions matching the acceptance criteria + 6. **Detect regressions** — run `git log --oneline --all -- <file>` to check if key files were modified after implementation. Run `git log --grep="Revert" --oneline` to find revert commits that may have undone the work + + ## Classification Rules + + Assign exactly ONE fidelity category per item: + + - **fully_implemented**: All referenced files exist, key functions/types are present via Grep, logic reads match the described behavior, related tests exist + - **partial**: Some but not all acceptance criteria have matching code evidence. For each partial finding, list WHICH criteria passed and WHICH did not + - **regressed**: Was implemented but later broken or reverted. Include the revert commit SHAs and affected file paths as evidence + - **obsolete**: Referenced files have been deleted at HEAD, or the codebase has diverged significantly enough that the item no longer applies + - **not_implemented**: No evidence of implementation; issue or PR describes work that does not appear in the codebase + + ## Evidence Requirements + + Every finding MUST include evidence: + - For **fully_implemented**: file paths confirming existence, Grep matches for key code artifacts + - For **partial**: which criteria passed (with file:line references) and which did not (what was searched for but not found) + - For **regressed**: revert commit SHAs, `git log` output showing modification/deletion after the implementing PR + - For **obsolete**: evidence that files no longer exist or architecture has changed + - For **not_implemented**: description of what was expected to exist but does not + + ## Edge Cases + + - **Issues with no traceable code changes**: Mark as "not_implemented" with a note explaining the lack of implementation evidence + - **Issues referencing deleted files**: Mark as "obsolete" with evidence that the referenced code no longer exists at HEAD + - **Large inventories**: Focus on the most impactful items first — non-trivial issues with acceptance criteria. If context limits approach, prioritize quality of analysis over quantity + + ## Output + + Write the findings as structured JSON to `.wave/artifacts/audit-report.json`. + + The JSON must include: + - `findings`: array of finding objects, each with: + - `item_number`: issue or PR number + - `item_type`: "issue" or "pr" + - `item_url`: GitHub URL + - `title`: item title + - `status`: one of (fully_implemented, partial, regressed, obsolete, not_implemented) + - `evidence`: array of strings describing evidence found + - `unmet_criteria`: array of strings describing criteria not met (for partial/regressed) + - `remediation`: string describing remediation needed (empty for fully_implemented/obsolete) + - `summary`: object with counts by status (fully_implemented, partial, regressed, obsolete, not_implemented) + - `timestamp`: current ISO 8601 timestamp + + output_artifacts: + - name: audit-report + path: .wave/artifacts/audit-report.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/artifacts/audit-report.json + schema_path: .wave/contracts/audit-findings.schema.json + on_failure: retry + + - id: triage + persona: navigator + model: claude-haiku + dependencies: [audit] + memory: + inject_artifacts: + - step: inventory + artifact: inventory + as: inventory + - step: audit + artifact: audit-report + as: audit_report + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Compose a triage summary from the audit findings with prioritized remediation recommendations. + + ## Read Inputs + + Read the injected inventory artifact to get scope and repository metadata. + Read the injected audit report artifact to get the per-item verification results. + + ## Group Findings by Status + + Organize all findings into groups by implementation status, in this order: + 1. **regressed** — highest priority, was working but now broken + 2. **partial** — some criteria unmet + 3. **not_implemented** — no implementation found + 4. **obsolete** — no longer applicable + 5. **fully_implemented** — fully intact (included for reference) + + ## Summary Statistics + + Calculate counts for each status: + - `fully_implemented`: number of fully verified items + - `partial`: number with some criteria unmet + - `regressed`: number that were broken or reverted + - `obsolete`: number no longer applicable + - `not_implemented`: number with no implementation evidence + + ## Prioritized Remediation Actions + + Generate an ordered list of remediation actions for non-fully-implemented items. Priority ranking: + + 1. **regressed** items (highest priority — was working, now broken) + 2. **partial** items with many unmet criteria (sort by unmet count descending) + 3. **partial** items with fewer unmet criteria + 4. **not_implemented** items (moderate priority — work was never done) + 5. **obsolete** items are EXCLUDED from actions — they are intentionally non-applicable + + ## Output Format + + Write a markdown summary to `.wave/artifacts/triage-report.md` with: + + 1. **Audit Scope** — Description of what was audited (time range, labels, etc.) + 2. **Summary Statistics** — Counts by status as a table or list + 3. **Regressed Items** (if any) — Bulleted list with issue numbers, titles, revert commit SHAs, and remediation steps + 4. **Partial Implementation Items** (if any) — Bulleted list with issue numbers, titles, which criteria failed, and remediation steps + 5. **Not Implemented Items** (if any) — Bulleted list with issue numbers, titles, and what would need to be done + 6. **Obsolete Items** — Count only, explanation that these are no longer applicable + 7. **Fully Implemented Items** — Count only, confirmation of fidelity + 8. **Recommended Next Steps** — Actionable recommendations for the team + + All issue/PR references should be clickable links to their GitHub URLs. + + output_artifacts: + - name: triage-report + path: .wave/artifacts/triage-report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/artifacts/triage-report.md diff --git a/.wave/pipelines/audit-consolidate.yaml b/.wave/pipelines/audit-consolidate.yaml new file mode 100644 index 0000000..48232ff --- /dev/null +++ b/.wave/pipelines/audit-consolidate.yaml @@ -0,0 +1,74 @@ +kind: WavePipeline +metadata: + name: audit-consolidate + description: "Detect redundant implementations and architectural drift" + release: true + +skills: + - software-design + +input: + source: cli + example: "internal/pipeline — check for redundant patterns" + schema: + type: string + description: "Package or directory scope to analyze, or empty for full codebase" + +steps: + - id: scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Scan for redundant implementations and architectural drift. + + Scope: {{ input }} + + ## What to Look For + + 1. **Duplicate logic**: Functions doing the same thing in different packages. + Search for similar function signatures and bodies. + + 2. **Parallel abstractions**: Multiple interfaces or types representing + the same concept (e.g., two different error types for the same domain). + + 3. **Inconsistent patterns**: Same operation done differently in different + places (e.g., file reading with os.ReadFile in one place, io.ReadAll in another). + + 4. **Dead abstractions**: Interfaces with only one implementation, + wrapper types that add no value. + + 5. **Package boundary violations**: Packages reaching into each other's + internals instead of using proper interfaces. + + 6. **Naming inconsistencies**: Same concept with different names across + packages (e.g., "workspace" vs "workdir" vs "cwd"). + + ## Analysis + + For each finding: + - List the affected file:line locations + - Explain what's redundant or inconsistent + - Propose a consolidation strategy + - Assess effort (trivial/small/medium/large) and risk + + Produce a structured assessment. + output_artifacts: + - name: assessment + path: .wave/output/assessment.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/assessment.json + schema_path: .wave/contracts/improvement-assessment.schema.json + on_failure: retry diff --git a/.wave/pipelines/audit-dead-code-issue.yaml b/.wave/pipelines/audit-dead-code-issue.yaml new file mode 100644 index 0000000..091d007 --- /dev/null +++ b/.wave/pipelines/audit-dead-code-issue.yaml @@ -0,0 +1,200 @@ +kind: WavePipeline +metadata: + name: audit-dead-code-issue + description: "Scan codebase for dead code and create a GitHub issue with findings" + release: true + +requires: + tools: + - go + - gh + +skills: + - software-design + +input: + source: cli + example: "scan all packages for dead code and report findings" + +steps: + - id: scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Scan for dead or redundant code: {{ input }} + + ## What to Look For + + 1. **Unused exports**: Exported functions, types, constants, or variables + that are never referenced outside their package. + + 2. **Unreachable code**: Code after return/panic, impossible branches, + dead switch cases. + + 3. **Orphaned files**: Files not imported by any other file in the project. + + 4. **Redundant code**: Duplicate functions, copy-paste blocks, + wrappers that add no value. + + 5. **Stale tests**: Tests for functions that no longer exist, + or tests that test nothing meaningful. + + 6. **Unused dependencies**: Imports that are no longer needed. + + 7. **Commented-out code**: Large blocks of commented code that + should be deleted (git has history). + + 8. **Duplicate signatures**: Functions with identical signatures across + packages that could be consolidated. + + ## Verification + + For each finding, verify it's truly dead: + - Grep for all references across the entire codebase + - Check for reflect-based or string-based usage + - Check if it's part of an interface implementation + - Check for build tag conditional compilation + + Produce a structured JSON result matching the contract schema. + Only include findings with high or medium confidence. Skip low confidence. + output_artifacts: + - name: scan_results + path: .wave/output/dead-code-scan.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/dead-code-scan.json + schema_path: .wave/contracts/dead-code-scan.schema.json + on_failure: retry + + - id: compose-report + persona: navigator + model: claude-haiku + dependencies: [scan] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: findings + exec: + type: prompt + source: | + Compose a dead code report as a GitHub-ready markdown file. + + ## Check for Findings + + If the scan found zero findings: + - Write a short "No dead code found" message as the report + - Write the issue result with skipped=true and reason="clean" + + ## Compose the Report + + Write the report as markdown: + + ``` + ## Dead Code Report + + **Scan date**: <timestamp from findings> + **Findings**: <total_count> + + ### Summary by Type + | Type | Count | + |------|-------| + | unused_export | N | + | unreachable | N | + | ... | N | + + ### Summary by Suggested Action + | Action | Count | + |--------|-------| + | remove | N | + | consolidate | N | + | investigate | N | + + ### Task List + + For each finding (sorted by confidence, high first): + - [ ] **[DC-001]** (`type`, `confidence`) `location` — description + Action: `suggested_action` | Safe to remove: `safe_to_remove` + + --- + *Generated by [Wave](https://github.com/re-cinq/wave) dead-code-issue pipeline* + ``` + output_artifacts: + - name: report + path: .wave/output/report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/report.md + + - id: create-issue + persona: craftsman + dependencies: [compose-report] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: findings + - step: compose-report + artifact: report + as: report + exec: + type: prompt + source: | + Create a GitHub issue from the dead code report. + + If the report says "No dead code found", skip issue creation and exit + with skipped=true in the result JSON. + + ## Detect Repository + + Run: `{{ forge.cli_tool }} repo view --json nameWithOwner --jq .nameWithOwner` + + ## Create the Issue + + ```bash + {{ forge.cli_tool }} issue create \ + --title "chore: dead code report" \ + --body-file .wave/artifacts/report \ + --label "code-quality" + ``` + + If the `code-quality` label doesn't exist, create without labels. + If any `{{ forge.cli_tool }}` command fails, log the error and continue. + + ## Capture Result + + Write a JSON status report matching the contract schema. + Include the issue URL, number, title, and finding count from the scan results. + output_artifacts: + - name: issue-result + path: .wave/output/issue-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/issue-result.json + schema_path: .wave/contracts/dead-code-issue-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: issue + extract_from: .wave/output/issue-result.json + json_path: .issue.url + label: "Dead Code Issue" diff --git a/.wave/pipelines/audit-dead-code-review.yaml b/.wave/pipelines/audit-dead-code-review.yaml new file mode 100644 index 0000000..0d36912 --- /dev/null +++ b/.wave/pipelines/audit-dead-code-review.yaml @@ -0,0 +1,186 @@ +kind: WavePipeline +metadata: + name: audit-dead-code-review + description: "Scan PR-changed files for dead code and post a review comment" + release: true + +requires: + tools: + - go + - gh + +skills: + - software-design + +input: + source: cli + example: "https://github.com/re-cinq/wave/pull/42" + +steps: + - id: scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Scan for dead or redundant code in the files changed by: {{ input }} + + ## Scope + + Only scan files changed in the given pull request. Use `{{ forge.cli_tool }} {{ forge.pr_command }} diff` to + identify the changed files, then analyze only those files for dead code. + + ## What to Look For + + 1. **Unused exports**: Exported functions, types, constants, or variables + that are never referenced outside their package. + + 2. **Unreachable code**: Code after return/panic, impossible branches, + dead switch cases. + + 3. **Orphaned files**: Files not imported by any other file in the project. + + 4. **Redundant code**: Duplicate functions, copy-paste blocks, + wrappers that add no value. + + 5. **Stale tests**: Tests for functions that no longer exist, + or tests that test nothing meaningful. + + 6. **Unused dependencies**: Imports that are no longer needed. + + 7. **Commented-out code**: Large blocks of commented code that + should be deleted (git has history). + + 8. **Duplicate signatures**: Functions with identical signatures across + packages that could be consolidated. + + ## Verification + + For each finding, verify it's truly dead: + - Grep for all references across the entire codebase + - Check for reflect-based or string-based usage + - Check if it's part of an interface implementation + - Check for build tag conditional compilation + + Produce a structured JSON result matching the contract schema. + Only include findings with high or medium confidence. Skip low confidence. + output_artifacts: + - name: scan_results + path: .wave/output/dead-code-scan.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/dead-code-scan.json + schema_path: .wave/contracts/dead-code-scan.schema.json + on_failure: retry + + - id: compose + persona: summarizer + model: claude-haiku + dependencies: [scan] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: findings + exec: + type: prompt + source: | + Compose a dead code review comment from the scan findings. + + Read the injected findings and produce a markdown summary suitable for + posting as a PR review comment. + + ## Format + + If zero findings: + ``` + No dead code found in the changed files. + ``` + + If findings exist: + ``` + ## Dead Code Review + + **Findings**: <total_count> items found in changed files + + ### Summary by Type + | Type | Count | + |------|-------| + | ... | N | + + ### Findings + + For each finding (sorted by confidence, high first): + - **[DC-001]** (`type`) `location` — description + Suggested action: `suggested_action` + + --- + *Generated by [Wave](https://github.com/re-cinq/wave) dead-code-review pipeline* + ``` + + Do NOT include a title/header line — the publish step adds one. + output_artifacts: + - name: review_comment + path: .wave/output/review-comment.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/review-comment.md + + - id: publish + persona: "gitea-commenter" + dependencies: [compose] + memory: + inject_artifacts: + - step: compose + artifact: review_comment + as: review_summary + exec: + type: prompt + source: | + Post the dead code review as a PR comment. + + The original input was: {{ input }} + Extract the PR number or URL from the input. + + 1. Write the review content to a temp file, then post it as a PR comment: + cat > /tmp/dead-code-review-comment.md <<'REVIEW_EOF' + ## Dead Code Review (Wave Pipeline) + + <review content> + + --- + *Generated by [Wave](https://github.com/re-cinq/wave) dead-code-review pipeline* + REVIEW_EOF + {{ forge.cli_tool }} {{ forge.pr_command }} comment <PR_NUMBER_OR_URL> --body-file /tmp/dead-code-review-comment.md + + output_artifacts: + - name: publish-result + path: .wave/output/publish-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/publish-result.json + schema_path: .wave/contracts/gh-pr-comment-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: url + extract_from: .wave/output/publish-result.json + json_path: .comment_url + label: "Dead Code Review Comment" diff --git a/.wave/pipelines/audit-dead-code.yaml b/.wave/pipelines/audit-dead-code.yaml new file mode 100644 index 0000000..ab4540a --- /dev/null +++ b/.wave/pipelines/audit-dead-code.yaml @@ -0,0 +1,285 @@ +kind: WavePipeline +metadata: + name: audit-dead-code + description: "Find dead or redundant code, remove it, and commit to a feature branch" + release: true + +requires: + tools: + - go + +skills: + - software-design + +input: + source: cli + example: "find and remove dead code in internal/pipeline" + +steps: + - id: scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Scan for dead or redundant code: {{ input }} + + ## Pre-Scan: Ensure Code is Up-to-Date + + Before scanning, verify the local code matches the remote to avoid analyzing stale code: + ```bash + git fetch origin + ``` + If the local HEAD is behind origin/main, warn in your output that findings may + need re-verification against the latest main branch. + + ## What to Look For + + 1. **Unused exports**: Exported functions, types, constants, or variables + that are never referenced outside their package. + + 2. **Unreachable code**: Code after return/panic, impossible branches, + dead switch cases. + + 3. **Orphaned files**: Files not imported by any other file in the project. + + 4. **Redundant code**: Duplicate functions, copy-paste blocks, + wrappers that add no value. + + 5. **Stale tests**: Tests for functions that no longer exist, + or tests that test nothing meaningful. + + 6. **Unused dependencies**: Imports that are no longer needed. + + 7. **Commented-out code**: Large blocks of commented code that + should be deleted (git has history). + + ## Verification + + For each finding, verify it's truly dead: + - Grep for all references across the entire codebase + - Check for reflect-based or string-based usage + - Check if it's part of an interface implementation + - Check for build tag conditional compilation + + Produce a structured JSON result matching the contract schema. + Only include findings with high or medium confidence. Skip low confidence. + output_artifacts: + - name: scan_results + path: .wave/output/dead-code-scan.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/dead-code-scan.json + schema_path: .wave/contracts/dead-code-scan.schema.json + on_failure: retry + + - id: clean + persona: craftsman + dependencies: [scan] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: findings + workspace: + type: worktree + branch: "chore/{{ pipeline_id }}" + base: "origin/main" + exec: + type: prompt + source: | + Remove the dead code on this isolated worktree branch. + + The scan findings have been injected into your workspace. Read them first. + + ## Process + + 1. **Remove dead code** — ONLY high-confidence findings: + - Start with unused imports (safest) + - Then commented-out code blocks + - Then unused exports + - Then orphaned files + - Skip anything with confidence=medium unless trivially safe + - After each removal, verify the build still passes + + 2. Run the full test suite and fix any failures before committing. + + 3. **Commit**: + ```bash + git add <specific-files> + git commit -m "chore: remove dead code + + Removed N items of dead code: + - DC-001: <symbol> (unused export) + - DC-002: <file> (orphaned file) + ..." + ``` + + If ANY test fails after a removal, revert that specific removal + and continue with the next item. + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + + - id: verify + persona: reviewer + dependencies: [clean] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: original_findings + exec: + type: prompt + source: | + Verify the dead code removal was safe. + + The original scan findings have been injected into your workspace. Read them first. + + Check: + 1. Were only high-confidence items removed? + 2. Are all tests still passing? + 3. Does the project still build cleanly? + 4. Were any false positives accidentally removed? + 5. Is the commit focused (no unrelated changes)? + + Produce a structured JSON verification report matching the contract schema. + + The `verdict` field MUST be either: + - `"CLEAN"` — all removals are safe, tests pass, no false positives detected + - `"NEEDS_REVIEW"` — potential issues found that require human review + + **Important**: The contract schema only accepts `"CLEAN"`. If you set verdict + to `"NEEDS_REVIEW"`, contract validation will intentionally fail and the + pipeline will halt before creating a PR. This is the desired safety behavior. + output_artifacts: + - name: verification + path: .wave/output/verification.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/verification.json + schema_path: .wave/contracts/dead-code-verification.schema.json + on_failure: retry + + - id: create-pr + persona: craftsman + dependencies: [verify] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: findings + - step: verify + artifact: verification + as: verification_report + workspace: + type: worktree + branch: "chore/{{ pipeline_id }}" + exec: + type: prompt + source: | + Create a pull request for the dead code removal. + + ## Working Directory + + You are running in an **isolated git worktree** shared with previous pipeline steps. + Your working directory IS the project root. The branch already exists from the + clean step — just push it and create the PR. + + ## SAFETY: Do NOT Modify the Working Tree + + This step MUST NOT run `git checkout`, `git stash`, or any command that changes + the current branch or working tree state. + + ## Instructions + + ### Step 1: Load Context + + The scan findings and verification report have been injected into your workspace. + Read them both to understand what was found and the verification outcome. + + ### Step 2: Push the Branch + + ```bash + git push -u origin HEAD + ``` + + ### Step 3: Create Pull Request + + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} create --title "chore: remove dead code" --body "$(cat <<'PREOF' + ## Summary + + Automated dead code removal based on static analysis scan. + + <summarize what was removed: N items, types, estimated lines saved> + + ## Verification + + <summarize verification report: CLEAN or NEEDS_REVIEW, test status> + + ## Removed Items + + <list each removed item with its ID, type, and location> + + ## Test Plan + + - Full test suite passed after each removal + - Build verified clean after all removals + - Auditor persona verified no false positives + PREOF + )" + ``` + + ### Step 4: Request Copilot Review (Best-Effort) + + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} edit --add-reviewer "copilot" 2>/dev/null || true + ``` + + ## CONSTRAINTS + + - Do NOT spawn Task subagents — work directly in the main context + - Do NOT run `git checkout`, `git stash`, or any branch-switching commands + - Do NOT include Co-Authored-By or AI attribution in commits + + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/audit-doc.yaml b/.wave/pipelines/audit-doc.yaml new file mode 100644 index 0000000..0679a48 --- /dev/null +++ b/.wave/pipelines/audit-doc.yaml @@ -0,0 +1,265 @@ +kind: WavePipeline +metadata: + name: audit-doc + description: Pre-PR documentation consistency gate — scans changes, cross-references docs, and creates a GitHub issue with inconsistencies + release: true + +skills: + - software-design + +input: + source: cli + example: "full -- scan all documentation for inconsistencies with the codebase" + schema: + type: string + description: "Scan scope: empty for branch diff, 'full' for all files, or a git ref" + +steps: + - id: scan-changes + persona: navigator + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Scan the repository to identify changed files and capture the current documentation state. + + ## Determine Scan Scope + + Input: {{ input }} + + - If the input is empty or blank: use `git log --name-status main...HEAD` to find files changed on the current branch vs main. + - If the input is "full": skip the diff — treat ALL files as in-scope and scan all documentation. + - Otherwise, treat the input as a git ref and use `git log --name-status <input>...HEAD`. + + Run `git log --oneline --name-status` with the appropriate range to get the list of changed files. + If no commits are found (e.g. on main with no branch divergence), fall back to `git status --porcelain` for uncommitted changes. + + ## Categorize Changed Files + + Sort each changed file into one of these categories: + - **source_code**: source files matching the project language (excluding test files) + - **tests**: test files (files with test/spec in name or in test directories) + - **documentation**: markdown files, doc directories, README, CONTRIBUTING, CHANGELOG + - **configuration**: config files, schema files, environment configs + - **build**: build scripts, CI/CD configs, Makefiles, Dockerfiles + - **other**: everything else + + ## Read Documentation Surface Area + + Discover and read key documentation files. Common locations include: + - Project root: README.md, CONTRIBUTING.md, CHANGELOG.md + - Documentation directories: docs/, doc/, wiki/ + - Configuration docs: any files documenting config options or environment variables + - CLI/API docs: any files documenting commands, endpoints, or public interfaces + + Adapt your scan to the actual project structure — do not assume a fixed layout. + + ## Output + + Write your findings as structured JSON. + Include: + - scan_scope: mode ("diff" or "full"), range used, base_ref + - changed_files: total_count + categories object with arrays of file paths + - documentation_snapshot: array of {path, exists, summary} for each doc file + - timestamp: current ISO 8601 timestamp + output_artifacts: + - name: scan-results + path: .wave/output/scan-results.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/scan-results.json + schema_path: .wave/contracts/doc-scan-results.schema.json + on_failure: retry + + - id: analyze-consistency + persona: reviewer + dependencies: [scan-changes] + memory: + inject_artifacts: + - step: scan-changes + artifact: scan-results + as: scan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Analyze documentation consistency by cross-referencing code changes with documentation. + + ## Cross-Reference Checks + + For each category of changed files, perform these checks: + + **CLI/API surface** (changed command or endpoint files): + - Compare command definitions, endpoints, or public interfaces against documentation + - Check for new, removed, or changed options/parameters + - Verify documented examples still work + + **Configuration** (changed config schemas or parsers): + - Compare documented options against actual config structure + - Check for undocumented settings or environment variables + + **Source code** (changed source files): + - Check for new exported functions/types that might need API docs + - Look for stale code comments referencing removed features + - Verify public API descriptions in docs match actual behavior + + **Environment variables**: + - Scan source code for environment variable access patterns + - Compare against documentation + - Flag undocumented environment variables + + ## Severity Rating + + Rate each inconsistency: + - **CRITICAL**: Feature exists in code but completely missing from docs, or docs describe non-existent feature + - **HIGH**: Incorrect information in docs (wrong flag name, wrong description, wrong behavior) + - **MEDIUM**: Outdated information (stale counts, missing new options, incomplete lists) + - **LOW**: Minor style issues, slightly imprecise wording + + ## Output + + Write your analysis as structured JSON. + Include: + - summary: total_count, by_severity counts, clean (true if zero inconsistencies) + - inconsistencies: array of {id (DOC-001 format), severity, category, title, description, source_location, doc_location, fix_description} + - timestamp: current ISO 8601 timestamp + + If no inconsistencies are found, output an empty array with clean=true. + output_artifacts: + - name: consistency-report + path: .wave/output/consistency-report.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/consistency-report.json + schema_path: .wave/contracts/doc-consistency-report.schema.json + on_failure: retry + + - id: compose-report + persona: navigator + dependencies: [analyze-consistency] + memory: + inject_artifacts: + - step: analyze-consistency + artifact: consistency-report + as: report + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Compose a documentation consistency report as a GitHub-ready markdown file. + + ## Check for Inconsistencies + + If the consistency report has `summary.clean == true` (zero inconsistencies): + - Write a short "No inconsistencies found" message as the report + - Write the issue result with skipped=true and reason="clean" + + ## Compose the Report + + Write the report as markdown: + + ``` + ## Documentation Consistency Report + + **Scan date**: <timestamp from report> + **Inconsistencies found**: <total_count> + + ### Summary by Severity + | Severity | Count | + |----------|-------| + | Critical | N | + | High | N | + | Medium | N | + | Low | N | + + ### Task List + + For each inconsistency (sorted by severity, critical first): + - [ ] **[DOC-001]** (CRITICAL) Title here — `doc_location` + Fix: fix_description + + --- + *Generated by [Wave](https://github.com/re-cinq/wave) doc-audit pipeline* + ``` + output_artifacts: + - name: report + path: .wave/output/report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/report.md + + - id: publish + persona: craftsman + dependencies: [compose-report] + memory: + inject_artifacts: + - step: compose-report + artifact: report + as: report + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + PUBLISH — create a GitHub issue from the documentation report. + + If the report says "No inconsistencies found", skip issue creation and exit. + + ## Detect Repository + + Run: `{{ forge.cli_tool }} repo view --json nameWithOwner --jq .nameWithOwner` + + ## Create the Issue + + ```bash + {{ forge.cli_tool }} issue create \ + --title "docs: documentation consistency report" \ + --body-file .wave/artifacts/report \ + --label "documentation" + ``` + + If the `documentation` label doesn't exist, create without labels. + If any `{{ forge.cli_tool }}` command fails, log the error and continue. + + ## Capture Result + + Write a JSON status report. + output_artifacts: + - name: issue-result + path: .wave/output/issue-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/issue-result.json + schema_path: .wave/contracts/doc-issue-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: issue + extract_from: .wave/output/issue-result.json + json_path: .issue_url + label: "Documentation Issue" diff --git a/.wave/pipelines/audit-dual.yaml b/.wave/pipelines/audit-dual.yaml new file mode 100644 index 0000000..c5fca99 --- /dev/null +++ b/.wave/pipelines/audit-dual.yaml @@ -0,0 +1,193 @@ +# Independent Parallel Tracks Pattern +# +# This pipeline demonstrates two fully independent analysis tracks +# running simultaneously and converging at a final merge step. +# Unlike the fan-out pattern (used in ops-pr-review.yaml), these tracks +# have NO shared upstream step — they start independently and converge +# only at the end. +# +# Execution flow: +# +# quality-scan security-scan ← both start immediately (no deps) +# │ │ +# quality-detail security-detail ← each track continues independently +# └────────┬─────────┘ +# merge ← converges results from both tracks + +kind: WavePipeline +metadata: + name: audit-dual + description: "Parallel code-quality and security analysis with independent tracks" + release: true + +skills: + - software-design + +input: + source: cli + example: "analyze the authentication module" + +steps: + # ── Track A: Code Quality ────────────────────────────────────────── + - id: quality-scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Perform a code quality scan of: {{ input }} + + Identify: + 1. Code duplication and copy-paste patterns + 2. Functions exceeding 50 lines or high cyclomatic complexity + 3. Naming inconsistencies and style violations + 4. Missing or outdated documentation + 5. Unused exports, dead code, and unreachable branches + + Output a structured JSON report matching the contract schema. + output_artifacts: + - name: quality_scan + path: .wave/output/quality-scan.json + type: json + + - id: quality-detail + persona: navigator + model: claude-haiku + dependencies: [quality-scan] + memory: + strategy: fresh + inject_artifacts: + - step: quality-scan + artifact: quality_scan + as: scan_results + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Deepen the code quality analysis from the scan results. + + For each finding in .wave/artifacts/scan_results: + 1. Verify the finding by reading the source code + 2. Assess severity and impact on maintainability + 3. Suggest specific refactoring with code examples + 4. Search for similar patterns elsewhere in the codebase + + Produce a markdown report with prioritized recommendations. + output_artifacts: + - name: quality_report + path: .wave/output/quality-detail.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/quality-detail.md + + # ── Track B: Security ────────────────────────────────────────────── + - id: security-scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Perform a security scan of: {{ input }} + + Check for: + 1. Injection vulnerabilities (SQL, command, path traversal) + 2. Authentication and authorization gaps + 3. Hardcoded secrets or credentials + 4. Insecure data handling (missing encryption, logging sensitive data) + 5. Input validation gaps at system boundaries + + Output a structured JSON report matching the contract schema. + output_artifacts: + - name: security_scan + path: .wave/output/security-scan.json + type: json + + - id: security-detail + persona: navigator + model: claude-haiku + dependencies: [security-scan] + memory: + strategy: fresh + inject_artifacts: + - step: security-scan + artifact: security_scan + as: scan_results + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Deepen the security analysis from the scan results. + + For each finding in .wave/artifacts/scan_results: + 1. Verify by reading the actual source code + 2. Trace data flow from entry point to sink + 3. Assess exploitability and real-world impact + 4. Propose specific remediation with code examples + + Produce a markdown report with severity-ordered findings. + output_artifacts: + - name: security_report + path: .wave/output/security-detail.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/security-detail.md + + # ── Merge: Converge both tracks ──────────────────────────────────── + - id: merge + persona: summarizer + model: claude-haiku + dependencies: [quality-detail, security-detail] + memory: + strategy: fresh + inject_artifacts: + - step: quality-detail + artifact: quality_report + as: quality_findings + - step: security-detail + artifact: security_report + as: security_findings + exec: + type: prompt + source: | + Synthesize the quality and security analysis reports into a + unified assessment. + + Read both reports: + - .wave/artifacts/quality_findings (code quality) + - .wave/artifacts/security_findings (security) + + Produce a final report with: + 1. Executive summary with overall health rating + 2. Critical issues requiring immediate attention + 3. Top recommendations ordered by impact + 4. Positive observations and strengths + output_artifacts: + - name: report + path: .wave/output/dual-analysis-report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/dual-analysis-report.md diff --git a/.wave/pipelines/audit-dx.yaml b/.wave/pipelines/audit-dx.yaml new file mode 100644 index 0000000..963dd88 --- /dev/null +++ b/.wave/pipelines/audit-dx.yaml @@ -0,0 +1,66 @@ +kind: WavePipeline +metadata: + name: audit-dx + description: "Evaluate developer experience for contributors and integrators" + release: true + +skills: + - software-design + +input: + source: cli + example: "audit the contributor onboarding experience" + schema: + type: string + description: "DX area to audit: onboarding, testing, ci, api, or empty for full audit" + +steps: + - id: audit + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Perform a developer experience audit of: {{ input }} + + ## Evaluation Areas + + 1. **Setup experience**: Can a new contributor get running quickly? + Check README, Makefile/scripts, dependency installation, IDE setup. + + 2. **Code navigation**: Is the codebase easy to navigate? + Check package organization, naming, documentation, godoc comments. + + 3. **Testing**: Is it easy to write and run tests? + Check test helpers, mocks, fixtures, CI integration. + + 4. **Debugging**: Can developers debug issues efficiently? + Check logging, debug flags, error messages, stack traces. + + 5. **API surface**: Is the internal API surface clean? + Check exported vs unexported, interface boundaries, type safety. + + 6. **Extensibility**: Can developers add new pipelines, personas, + contracts without modifying core code? + Check plugin points, configuration, documentation. + + 7. **CI/CD**: Is the CI pipeline fast and reliable? + Check test times, flaky tests, build reproducibility. + + ## Output + + Produce a markdown report with findings grouped by area, + severity ratings, and specific improvement recommendations. + output_artifacts: + - name: dx-report + path: .wave/output/dx-audit-report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/dx-audit-report.md diff --git a/.wave/pipelines/audit-junk-code.yaml b/.wave/pipelines/audit-junk-code.yaml new file mode 100644 index 0000000..3dea1f0 --- /dev/null +++ b/.wave/pipelines/audit-junk-code.yaml @@ -0,0 +1,73 @@ +kind: WavePipeline +metadata: + name: audit-junk-code + description: "Identify accidental complexity, conceptual misalignment, and technical debt" + release: true + +skills: + - software-design + +input: + source: cli + example: "internal/ — find accidental complexity and dead weight" + schema: + type: string + description: "Package or directory scope to analyze, or empty for full codebase" + +steps: + - id: scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Identify accidental complexity and technical debt. + + Scope: {{ input }} + + ## What to Look For + + 1. **Over-engineering**: Abstractions with one consumer, generics where + concrete types suffice, configuration for hypothetical use cases. + + 2. **Copy-paste drift**: Nearly-identical code blocks that diverged + slightly over time instead of being extracted. + + 3. **Stale code**: TODO/FIXME comments older than 3 months, commented-out + code blocks, unused imports or variables. + + 4. **Conceptual misalignment**: Types or functions in wrong packages, + misleading names, abstraction boundaries that don't match domain. + + 5. **Complexity hotspots**: Functions over 50 lines, deeply nested + control flow (3+ levels), cyclomatic complexity > 10. + + 6. **Test debt**: Tests that don't test anything meaningful, tests + with `t.Skip()` without linked issues, flaky test patterns. + + ## Output + + For each finding, provide: + - Location (file:line) + - Category (over-engineering, copy-paste, stale, misaligned, complex, test-debt) + - Description of the issue + - Suggested remediation + - Effort estimate (trivial/small/medium/large) + output_artifacts: + - name: assessment + path: .wave/output/assessment.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/assessment.json + schema_path: .wave/contracts/improvement-assessment.schema.json + on_failure: retry diff --git a/.wave/pipelines/audit-pedagogy.yaml b/.wave/pipelines/audit-pedagogy.yaml new file mode 100644 index 0000000..cd6cabd --- /dev/null +++ b/.wave/pipelines/audit-pedagogy.yaml @@ -0,0 +1,147 @@ +kind: WavePipeline +metadata: + name: audit-pedagogy + description: "Didactic quality audit: evaluate exercises for learning effectiveness, not code quality" + release: false + +skills: + - software-design + +input: + source: cli + example: "Audit all lesson modules for pedagogical quality" + schema: + type: string + description: "Focus area or scope for the pedagogy audit" + +steps: + - id: scan-lessons + persona: navigator + workspace: + type: basic + root: ./ + exec: + type: prompt + source: | + Scan ALL lesson JSON files in the lessons/ directory (English versions only, not translations). + + For EACH lesson file: + 1. Read the full JSON + 2. For each exercise in the lessons array, extract: + - id, title, task, description, solution, validations, codePrefix, codeSuffix + 3. Analyze the relationship between task description and solution: + - Is the solution literally stated in the task/description text? + - Does solving it require understanding beyond what's written? + - Are there multiple valid solutions or only one exact match? + + Output a structured inventory of all exercises with their metadata. + Write to .wave/output/lesson-inventory.json + output_artifacts: + - name: inventory + path: .wave/output/lesson-inventory.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/lesson-inventory.json + schema_path: .wave/contracts/lesson-inventory.schema.json + on_failure: skip + + - id: pedagogy-audit + persona: pedagogy-auditor + dependencies: [scan-lessons] + memory: + inject_artifacts: + - step: scan-lessons + artifact: inventory + as: lessons + workspace: + type: basic + root: ./ + exec: + type: prompt + source: | + Perform a thorough pedagogical audit of all lesson modules. + + You have the full lesson inventory. For EACH module, evaluate: + + 1. BLOOM'S TAXONOMY LEVEL + - What cognitive level do most exercises target? + - Level 1 (Remember): Type exact syntax from description + - Level 2 (Understand): Adapt a concept to a slightly different context + - Level 3 (Apply): Solve a novel problem using learned concepts + - Level 4 (Analyze): Debug, compare, or optimize code + + 2. COPY-PASTE SCORE (0-100) + - Compare each task description to its solution + - If the solution text appears verbatim in the description → high copy-paste + - If the student must transform/combine information → low copy-paste + - Score 100 = pure copy-paste, 0 = fully original thinking required + + 3. TRANSFER REQUIREMENT + - Does the student need to apply concepts from earlier lessons? + - Are there exercises that combine multiple skills? + - Does difficulty progress within the module? + + 4. VALIDATION QUALITY + - Do validations accept multiple correct solutions? + - Do error messages guide learning or just say "wrong"? + - Are there partial-credit possibilities? + + 5. SPECIFIC ISSUES per exercise + For exercises scoring poorly, provide: + - The exact problem (e.g., "solution 'display: flex;' is literally in the task text") + - A concrete improvement suggestion + - Expected impact on learning + + Be brutally honest. The goal is to identify WHERE students coast through + without learning and WHERE they get stuck without support. + + Write the full audit to .wave/output/pedagogy-report.json + Also write a human-readable markdown summary to .wave/output/pedagogy-report.md + output_artifacts: + - name: report + path: .wave/output/pedagogy-report.md + type: markdown + - name: report-json + path: .wave/output/pedagogy-report.json + type: json + + - id: improvement-plan + persona: planner + dependencies: [pedagogy-audit] + memory: + inject_artifacts: + - step: pedagogy-audit + artifact: report-json + as: audit + workspace: + type: basic + root: ./ + exec: + type: prompt + source: | + Based on the pedagogy audit, create a concrete improvement plan. + + For EACH module that scored below 60 on transfer or above 60 on copy-paste: + 1. Identify the 2-3 worst exercises + 2. Write improved task descriptions that require actual thinking + 3. Suggest additional validation types that accept multiple solutions + 4. Propose new exercises that test TRANSFER, not recall + + Group improvements by priority: + - CRITICAL: Exercises where students learn nothing (pure copy-paste) + - HIGH: Exercises that could be great with small changes + - MEDIUM: Missing scaffolding or difficulty gaps + + Write the plan to .wave/output/improvement-plan.json with structure: + { modules: [{ id, current_score, improvements: [{ exercise_id, problem, improved_task, improved_validations }] }] } + + Also write .wave/output/improvement-plan.md as human-readable markdown. + output_artifacts: + - name: plan + path: .wave/output/improvement-plan.md + type: markdown + - name: plan-json + path: .wave/output/improvement-plan.json + type: json diff --git a/.wave/pipelines/audit-quality-loop.yaml b/.wave/pipelines/audit-quality-loop.yaml new file mode 100644 index 0000000..bef7286 --- /dev/null +++ b/.wave/pipelines/audit-quality-loop.yaml @@ -0,0 +1,31 @@ +kind: WavePipeline +metadata: + name: audit-quality-loop + description: "Supervise work, loop improvements until quality passes" + category: composition + release: true + +skills: + - software-design + +input: + source: cli + example: "last pipeline run" + schema: + type: string + description: "Work reference to evaluate" + +steps: + - id: quality-check + pipeline: ops-supervise + input: "{{input}}" + loop: + max_iterations: 3 + until: "{{supervise.output.verdict}}" + steps: + - id: improve + pipeline: impl-improve + input: "{{input}}" + - id: recheck + pipeline: ops-supervise + input: "{{input}}" diff --git a/.wave/pipelines/audit-security.yaml b/.wave/pipelines/audit-security.yaml new file mode 100644 index 0000000..88cb856 --- /dev/null +++ b/.wave/pipelines/audit-security.yaml @@ -0,0 +1,157 @@ +kind: WavePipeline +metadata: + name: audit-security + description: "Comprehensive security vulnerability audit" + release: true + +skills: + - software-design + +input: + source: cli + example: "audit the authentication module for vulnerabilities" + +steps: + - id: scan + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Perform a security scan of: {{ input }} + + ## Scan Process + + 1. **Map attack surface**: Identify all entry points (HTTP handlers, CLI args, + file parsers, IPC endpoints, database queries, external API calls) + + 2. **Check OWASP Top 10**: + - Injection (SQL, command, LDAP, XPath) + - Broken authentication/authorization + - Sensitive data exposure + - XML external entities (XXE) + - Broken access control + - Security misconfiguration + - Cross-site scripting (XSS) + - Insecure deserialization + - Using components with known vulnerabilities + - Insufficient logging and monitoring + + 3. **Scan for common Go vulnerabilities** (if Go project): + - Unchecked errors on security-critical operations + - Race conditions on shared state + - Path traversal via unsanitized file paths + - Template injection + - Unsafe use of reflect or unsafe packages + + 4. **Check secrets and configuration**: + - Hardcoded credentials, API keys, tokens + - Insecure default configurations + - Missing TLS/encryption + - Overly permissive file permissions + + 5. **Review dependency usage**: + - Known vulnerable patterns in dependency usage + - Outdated security practices + + output_artifacts: + - name: scan_results + path: .wave/output/security-scan.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/security-scan.json + schema_path: .wave/contracts/security-scan.schema.json + on_failure: retry + + - id: deep-dive + persona: auditor + dependencies: [scan] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: scan_findings + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Perform a deep security analysis based on the injected scan results. + + For each finding with severity HIGH or CRITICAL: + + 1. **Verify the finding**: Read the actual source code at the reported location. + Confirm the vulnerability exists (eliminate false positives). + + 2. **Trace the data flow**: Follow untrusted input from entry point to sink. + Identify all transformations and validation (or lack thereof). + + 3. **Assess exploitability**: Could an attacker realistically exploit this? + What preconditions are needed? What's the impact? + + 4. **Check for related patterns**: Search for similar vulnerable patterns + elsewhere in the codebase using Grep. + + 5. **Propose remediation**: Specific, actionable fix with code examples. + Prioritize by effort vs. impact. + + For MEDIUM and LOW findings, do a lighter review confirming they're real. + + Produce a markdown report with these sections: + - Executive Summary + - Confirmed Vulnerabilities (with severity badges) + - False Positives Eliminated + - Data Flow Analysis + - Remediation Plan (ordered by priority) + - Related Patterns Found + output_artifacts: + - name: deep_dive + path: .wave/output/security-deep-dive.md + type: markdown + + - id: report + persona: summarizer + dependencies: [deep-dive] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: scan_findings + - step: deep-dive + artifact: deep_dive + as: analysis + exec: + type: prompt + source: | + Synthesize the injected scan findings and deep-dive analysis into a final report. + + Create a concise, actionable security report: + + 1. **Risk Score**: Overall risk rating (CRITICAL/HIGH/MEDIUM/LOW) with justification + 2. **Top 3 Issues**: The most important findings to fix immediately + 3. **Quick Wins**: Low-effort fixes that improve security posture + 4. **Remediation Roadmap**: Ordered list of fixes by priority + 5. **What's Good**: Security practices already in place + + Format as a clean markdown report suitable for sharing with the team. + output_artifacts: + - name: report + path: .wave/output/security-report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/security-report.md diff --git a/.wave/pipelines/audit-ux.yaml b/.wave/pipelines/audit-ux.yaml new file mode 100644 index 0000000..5f2d4a5 --- /dev/null +++ b/.wave/pipelines/audit-ux.yaml @@ -0,0 +1,68 @@ +kind: WavePipeline +metadata: + name: audit-ux + description: "Evaluate user experience across CLI, TUI, docs, or workflows" + release: true + +skills: + - software-design + +input: + source: cli + example: "audit the CLI onboarding flow for new users" + schema: + type: string + description: "UX area to audit: cli, tui, docs, onboarding, or empty for full audit" + +steps: + - id: audit + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Perform a UX audit of: {{ input }} + + ## Evaluation Criteria + + 1. **Discoverability**: Can users find features without reading docs? + Check help text, error messages, command suggestions. + + 2. **Error experience**: Are error messages actionable? Do they suggest + fixes? Check all error paths for user-friendly messages. + + 3. **Progressive disclosure**: Does the interface reveal complexity + gradually? Check default behaviors, optional flags, advanced modes. + + 4. **Consistency**: Are patterns uniform across commands? Check flag + names, output formats, exit codes. + + 5. **Feedback**: Does the system communicate progress? Check spinners, + status messages, completion indicators. + + 6. **Recovery**: Can users recover from mistakes? Check undo capabilities, + dry-run modes, confirmation prompts for destructive actions. + + 7. **Documentation alignment**: Does the actual behavior match what's + documented? Cross-reference docs/ with implementation. + + ## For Each Finding + + - Severity: critical (blocks usage), high (causes confusion), + medium (suboptimal), low (polish) + - Current behavior with reproduction steps + - Expected behavior + - Suggested fix with effort estimate + output_artifacts: + - name: ux-report + path: .wave/output/ux-audit-report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/ux-audit-report.md diff --git a/.wave/pipelines/changelog.yaml b/.wave/pipelines/changelog.yaml new file mode 100644 index 0000000..1c46c37 --- /dev/null +++ b/.wave/pipelines/changelog.yaml @@ -0,0 +1,141 @@ +kind: WavePipeline +metadata: + name: changelog + description: "Generate structured changelog from git history" + release: true + +input: + source: cli + example: "generate changelog from v0.1.0 to HEAD" + +steps: + - id: analyze-commits + persona: navigator + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Analyze git history for changelog generation: {{ input }} + + ## Process + + 1. **Determine range**: Parse input to identify the commit range. + If tags mentioned, use them. If time period, calculate dates. + If unclear, use last tag to HEAD (or last 50 commits). + + 2. **Extract commits**: Use `git log --format` to get hash, author, + date, subject, and body for each commit. + + 3. **Parse conventional commits**: Categorize by prefix: + feat → Features, fix → Fixes, docs → Documentation, + refactor → Refactoring, test → Testing, chore → Maintenance, + perf → Performance, ci → CI/CD, no prefix → Other + + 4. **Identify breaking changes**: Look for `BREAKING CHANGE:` in body, + `!` after prefix, API removals in body. + + 5. **Extract scope**: Parse from prefix (e.g., `fix(pipeline):` → "pipeline") + output_artifacts: + - name: commits + path: .wave/output/commit-analysis.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/commit-analysis.json + schema_path: .wave/contracts/commit-analysis.schema.json + on_failure: retry + max_retries: 2 + + - id: categorize + persona: planner + dependencies: [analyze-commits] + memory: + inject_artifacts: + - step: analyze-commits + artifact: commits + as: raw_commits + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Categorize and describe changes for a changelog using the injected commit analysis. + + ## Rules + + 1. **Group by type** into sections + 2. **Write user-facing descriptions**: Rewrite technical messages into + clear descriptions focused on what changed and why it matters. + 3. **Highlight breaking changes** first with migration notes + 4. **Deduplicate**: Combine commits for the same logical change + 5. **Add context** for significant features + output_artifacts: + - name: categorized + path: .wave/output/categorized-changes.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/categorized-changes.json + schema_path: .wave/contracts/categorized-changes.schema.json + on_failure: retry + max_retries: 2 + + - id: format + persona: philosopher + dependencies: [categorize] + memory: + inject_artifacts: + - step: analyze-commits + artifact: commits + as: raw_commits + - step: categorize + artifact: categorized + as: changes + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Format the injected commit analysis and categorized changes into a polished changelog. + + Use Keep a Changelog format: + + # Changelog + + ## [Version or Date Range] - YYYY-MM-DD + + ### Breaking Changes + - **scope**: Description. Migration: what to do + + ### Added + - **scope**: Feature description + + ### Fixed + - **scope**: Bug fix description + + ### Changed + - **scope**: Change description + + ### Security + - **scope**: Security fix description + + Rules: + - Only include sections with entries + - Bold scope if present + - Most notable entries first per section + - One line per entry, concise + - Contributors list at bottom + output_artifacts: + - name: changelog + path: .wave/output/CHANGELOG.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/CHANGELOG.md diff --git a/.wave/pipelines/dead-code.yaml b/.wave/pipelines/dead-code.yaml new file mode 100644 index 0000000..ca0fc64 --- /dev/null +++ b/.wave/pipelines/dead-code.yaml @@ -0,0 +1,261 @@ +kind: WavePipeline +metadata: + name: dead-code + description: "Find dead or redundant code, remove it, and commit to a feature branch" + release: true + +input: + source: cli + example: "find and remove dead code in internal/pipeline" + +steps: + - id: scan + persona: navigator + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Scan for dead or redundant code: {{ input }} + + ## What to Look For + + 1. **Unused exports**: Exported functions, types, constants, or variables + that are never referenced outside their package. + + 2. **Unreachable code**: Code after return/panic, impossible branches, + dead switch cases. + + 3. **Orphaned files**: Files not imported by any other file in the project. + + 4. **Redundant code**: Duplicate functions, copy-paste blocks, + wrappers that add no value. + + 5. **Stale tests**: Tests for functions that no longer exist, + or tests that test nothing meaningful. + + 6. **Unused dependencies**: Imports that are no longer needed. + + 7. **Commented-out code**: Large blocks of commented code that + should be deleted (git has history). + + ## Verification + + For each finding, verify it's truly dead: + - Grep for all references across the entire codebase + - Check for reflect-based or string-based usage + - Check if it's part of an interface implementation + - Check for build tag conditional compilation + + Produce a structured JSON result matching the contract schema. + Only include findings with high or medium confidence. Skip low confidence. + output_artifacts: + - name: scan_results + path: .wave/output/dead-code-scan.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/dead-code-scan.json + schema_path: .wave/contracts/dead-code-scan.schema.json + on_failure: retry + max_retries: 2 + + - id: clean + persona: craftsman + dependencies: [scan] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: findings + workspace: + type: worktree + branch: "chore/{{ pipeline_id }}" + exec: + type: prompt + source: | + Remove the dead code on this isolated worktree branch. + + The scan findings have been injected into your workspace. Read them first. + + ## Process + + 1. **Remove dead code** — ONLY high-confidence findings: + - Start with unused imports (safest) + - Then commented-out code blocks + - Then unused exports + - Then orphaned files + - Skip anything with confidence=medium unless trivially safe + - After each removal, verify: `go build ./...` + + 2. **Run goimports** if available to clean up imports: + ```bash + goimports -w <modified-files> 2>/dev/null || true + ``` + + 3. **Run full test suite**: + ```bash + go test ./... -count=1 + ``` + + 4. **Commit**: + ```bash + git add <specific-files> + git commit -m "chore: remove dead code + + Removed N items of dead code: + - DC-001: <symbol> (unused export) + - DC-002: <file> (orphaned file) + ..." + ``` + + If ANY test fails after a removal, revert that specific removal + and continue with the next item. + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + max_retries: 3 + + - id: verify + persona: reviewer + dependencies: [clean] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: original_findings + exec: + type: prompt + source: | + Verify the dead code removal was safe. + + The original scan findings have been injected into your workspace. Read them first. + + Check: + 1. Were only high-confidence items removed? + 2. Are all tests still passing? + 3. Does the project still build cleanly? + 4. Were any false positives accidentally removed? + 5. Is the commit focused (no unrelated changes)? + + Produce a verification report covering: + - Items removed (with justification) + - Items skipped (with reason) + - Lines of code removed + - Test status + - Overall assessment: CLEAN / NEEDS_REVIEW + output_artifacts: + - name: verification + path: .wave/output/verification.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/verification.md + + - id: create-pr + persona: craftsman + dependencies: [verify] + memory: + inject_artifacts: + - step: scan + artifact: scan_results + as: findings + - step: verify + artifact: verification + as: verification_report + workspace: + type: worktree + branch: "chore/{{ pipeline_id }}" + exec: + type: prompt + source: | + Create a pull request for the dead code removal. + + ## Working Directory + + You are running in an **isolated git worktree** shared with previous pipeline steps. + Your working directory IS the project root. The branch already exists from the + clean step — just push it and create the PR. + + ## SAFETY: Do NOT Modify the Working Tree + + This step MUST NOT run `git checkout`, `git stash`, or any command that changes + the current branch or working tree state. + + ## Instructions + + ### Step 1: Load Context + + The scan findings and verification report have been injected into your workspace. + Read them both to understand what was found and the verification outcome. + + ### Step 2: Push the Branch + + ```bash + git push -u origin HEAD + ``` + + ### Step 3: Create Pull Request + + ```bash + gh pr create --title "chore: remove dead code" --body "$(cat <<'PREOF' + ## Summary + + Automated dead code removal based on static analysis scan. + + <summarize what was removed: N items, types, estimated lines saved> + + ## Verification + + <summarize verification report: CLEAN or NEEDS_REVIEW, test status> + + ## Removed Items + + <list each removed item with its ID, type, and location> + + ## Test Plan + + - Full test suite passed after each removal + - Build verified clean after all removals + - Auditor persona verified no false positives + PREOF + )" + ``` + + ### Step 4: Request Copilot Review (Best-Effort) + + ```bash + gh pr edit --add-reviewer "copilot" 2>/dev/null || true + ``` + + ## CONSTRAINTS + + - Do NOT spawn Task subagents — work directly in the main context + - Do NOT run `git checkout`, `git stash`, or any branch-switching commands + - Do NOT include Co-Authored-By or AI attribution in commits + + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/debug.yaml b/.wave/pipelines/debug.yaml new file mode 100644 index 0000000..f314f40 --- /dev/null +++ b/.wave/pipelines/debug.yaml @@ -0,0 +1,142 @@ +kind: WavePipeline +metadata: + name: debug + description: "Systematic debugging with hypothesis testing" + release: true + +input: + source: cli + example: "TestPipelineExecutor fails with nil pointer on resume" + +steps: + - id: reproduce + persona: debugger + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Reproduce and characterize the issue: {{ input }} + + 1. Understand expected vs actual behavior + 2. Create minimal reproduction steps + 3. Identify relevant code paths + 4. Note environmental factors (OS, versions, config) + output_artifacts: + - name: reproduction + path: .wave/output/reproduction.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/reproduction.json + schema_path: .wave/contracts/debug-reproduction.schema.json + on_failure: retry + max_retries: 2 + + - id: hypothesize + persona: debugger + dependencies: [reproduce] + memory: + inject_artifacts: + - step: reproduce + artifact: reproduction + as: issue + exec: + type: prompt + source: | + Form hypotheses about the root cause. + + For each hypothesis: + 1. What could cause this behavior? + 2. What evidence would confirm/refute it? + 3. How to test this hypothesis? + + Rank by likelihood and ease of testing. + output_artifacts: + - name: hypotheses + path: .wave/output/hypotheses.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/hypotheses.json + schema_path: .wave/contracts/debug-hypotheses.schema.json + on_failure: retry + max_retries: 2 + + - id: investigate + persona: debugger + dependencies: [hypothesize] + memory: + inject_artifacts: + - step: reproduce + artifact: reproduction + as: issue + - step: hypothesize + artifact: hypotheses + as: hypotheses + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Test each hypothesis systematically. + + 1. Start with most likely / easiest to test + 2. Use git bisect if needed to find regression + 3. Add diagnostic logging to trace execution + 4. Examine data flow and state changes + 5. Document findings for each hypothesis + + Continue until root cause is identified. + output_artifacts: + - name: findings + path: .wave/output/investigation.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/investigation.md + + - id: fix + persona: craftsman + dependencies: [investigate] + memory: + inject_artifacts: + - step: investigate + artifact: findings + as: root_cause + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Fix the root cause identified in the investigation. + + 1. Implement the minimal fix + 2. Add a regression test that would have caught this + 3. Remove any diagnostic code added during debugging + 4. Verify the original reproduction no longer fails + 5. Check for similar issues elsewhere + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + + must_pass: false + on_failure: retry + max_retries: 3 + output_artifacts: + - name: fix + path: .wave/output/fix-summary.md + type: markdown diff --git a/.wave/pipelines/doc-changelog.yaml b/.wave/pipelines/doc-changelog.yaml new file mode 100644 index 0000000..340c210 --- /dev/null +++ b/.wave/pipelines/doc-changelog.yaml @@ -0,0 +1,149 @@ +kind: WavePipeline +metadata: + name: doc-changelog + description: "Generate structured changelog from git history" + release: true + +skills: + - software-design + +input: + source: cli + example: "generate changelog from v0.1.0 to HEAD" + +steps: + - id: analyze-commits + persona: navigator + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Analyze git history for changelog generation: {{ input }} + + ## Process + + 1. **Determine range**: Parse input to identify the commit range. + If tags mentioned, use them. If time period, calculate dates. + If unclear, use last tag to HEAD (or last 50 commits). + + 2. **Extract commits**: Use `git log --format` to get hash, author, + date, subject, and body for each commit. + + 3. **Parse conventional commits**: Categorize by prefix: + feat → Features, fix → Fixes, docs → Documentation, + refactor → Refactoring, test → Testing, chore → Maintenance, + perf → Performance, ci → CI/CD, no prefix → Other + + 4. **Identify breaking changes**: Look for `BREAKING CHANGE:` in body, + `!` after prefix, API removals in body. + + 5. **Extract scope**: Parse from prefix (e.g., `fix(pipeline):` → "pipeline") + output_artifacts: + - name: commits + path: .wave/output/commit-analysis.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/commit-analysis.json + schema_path: .wave/contracts/commit-analysis.schema.json + on_failure: retry + + - id: categorize + persona: planner + dependencies: [analyze-commits] + memory: + inject_artifacts: + - step: analyze-commits + artifact: commits + as: raw_commits + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Categorize and describe changes for a changelog using the injected commit analysis. + + ## Rules + + 1. **Group by type** into sections + 2. **Write user-facing descriptions**: Rewrite technical messages into + clear descriptions focused on what changed and why it matters. + 3. **Highlight breaking changes** first with migration notes + 4. **Deduplicate**: Combine commits for the same logical change + 5. **Add context** for significant features + output_artifacts: + - name: categorized + path: .wave/output/categorized-changes.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/categorized-changes.json + schema_path: .wave/contracts/categorized-changes.schema.json + on_failure: retry + + - id: format + persona: philosopher + dependencies: [categorize] + memory: + inject_artifacts: + - step: analyze-commits + artifact: commits + as: raw_commits + - step: categorize + artifact: categorized + as: changes + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Format the injected commit analysis and categorized changes into a polished changelog. + + Use Keep a Changelog format: + + # Changelog + + ## [Version or Date Range] - YYYY-MM-DD + + ### Breaking Changes + - **scope**: Description. Migration: what to do + + ### Added + - **scope**: Feature description + + ### Fixed + - **scope**: Bug fix description + + ### Changed + - **scope**: Change description + + ### Security + - **scope**: Security fix description + + Rules: + - Only include sections with entries + - Bold scope if present + - Most notable entries first per section + - One line per entry, concise + - Contributors list at bottom + output_artifacts: + - name: changelog + path: .wave/output/CHANGELOG.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/CHANGELOG.md diff --git a/.wave/pipelines/doc-explain.yaml b/.wave/pipelines/doc-explain.yaml new file mode 100644 index 0000000..1d72175 --- /dev/null +++ b/.wave/pipelines/doc-explain.yaml @@ -0,0 +1,140 @@ +kind: WavePipeline +metadata: + name: doc-explain + description: "Deep-dive explanation of code, modules, or architectural patterns" + release: true + +skills: + - software-design + +input: + source: cli + example: "explain the pipeline execution system and how steps are scheduled" + +steps: + - id: explore + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Explore the codebase to understand: {{ input }} + + ## Exploration Steps + + 1. **Find relevant files**: Use Glob and Grep to locate all files related + to the topic. Cast a wide net — include implementations, tests, configs, + and documentation. + + 2. **Trace the call graph**: For key entry points, follow the execution flow. + Note which functions call which, and how data flows through the system. + + 3. **Identify key abstractions**: Find the core types, interfaces, and structs. + Note their responsibilities and relationships. + + 4. **Map dependencies**: Which packages/modules does this depend on? + Which depend on it? + + 5. **Find tests**: Locate test files that exercise this code. + Tests often reveal intended behavior and edge cases. + + 6. **Check configuration**: Find config files, constants, or environment + variables that affect behavior. + + output_artifacts: + - name: exploration + path: .wave/output/exploration.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/exploration.json + schema_path: .wave/contracts/explain-exploration.schema.json + on_failure: retry + + - id: analyze + persona: planner + model: claude-haiku + dependencies: [explore] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: codebase_map + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze the architecture and design of the explored code. + + Review the injected exploration data, then read the key source files identified. Focus on: + + 1. **Design patterns**: What patterns are used and why? + 2. **Data flow**: How does data enter, transform, and exit? + 3. **Error handling**: What's the error strategy? + 4. **Concurrency model**: Goroutines, channels, mutexes? + 5. **Extension points**: Where can new functionality be added? + 6. **Design decisions**: What trade-offs were made? + output_artifacts: + - name: analysis + path: .wave/output/analysis.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/analysis.json + schema_path: .wave/contracts/explain-analysis.schema.json + on_failure: retry + + - id: document + persona: philosopher + dependencies: [analyze] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: codebase_map + - step: analyze + artifact: analysis + as: architecture + exec: + type: prompt + source: | + Write a comprehensive explanation document. + + Review the injected exploration and architecture data, then produce a markdown document with: + + 1. **Overview** — One paragraph summary + 2. **Key Concepts** — Core abstractions and terminology (glossary) + 3. **Architecture** — How pieces fit together (include ASCII diagram) + 4. **How It Works** — Step-by-step main execution flow with file:line refs + 5. **Design Decisions** — Decision → Rationale → Trade-off entries + 6. **Extension Guide** — How to add new functionality + 7. **Testing Strategy** — How the code is tested + 8. **Common Pitfalls** — Things that trip people up + + Write for an experienced developer new to this codebase. + Use real file paths, function names, and type names. + output_artifacts: + - name: explanation + path: .wave/output/explanation.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/explanation.md diff --git a/.wave/pipelines/doc-fix.yaml b/.wave/pipelines/doc-fix.yaml new file mode 100644 index 0000000..a79ed7f --- /dev/null +++ b/.wave/pipelines/doc-fix.yaml @@ -0,0 +1,255 @@ +kind: WavePipeline +metadata: + name: doc-fix + description: "Scan documentation for inconsistencies, fix them, and commit to a feature branch" + release: true + +skills: + - software-design + +input: + source: cli + example: "sync docs with current implementation" + +steps: + - id: scan-changes + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Scan the repository for documentation inconsistencies: {{ input }} + + ## Process + + 1. **Identify documentation files**: Find all markdown files, README, + CONTRIBUTING, docs/ directory, inline code comments with doc references. + + 2. **Identify code surface area**: Scan for exported functions, CLI commands, + config options, environment variables, API endpoints. + + 3. **Cross-reference**: For each documented feature, verify it exists in code. + For each code feature, verify it's documented. + + 4. **Check accuracy**: Compare documented behavior, flags, options, and + examples against actual implementation. + + 5. **Categorize findings**: + - MISSING_DOCS: Feature in code, not in docs + - STALE_DOCS: Docs reference removed/changed feature + - INACCURATE: Docs describe wrong behavior + - INCOMPLETE: Docs exist but missing details + + Write your findings as structured JSON. + Include: scan_scope, findings (id, type, severity, title, doc_location, code_location, + description, suggested_fix), summary (total_findings, by_type, by_severity, fixable_count), + and timestamp. + output_artifacts: + - name: scan_results + path: .wave/output/doc-scan.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/doc-scan.json + schema_path: .wave/contracts/doc-fix-scan.schema.json + on_failure: retry + + - id: analyze + persona: reviewer + dependencies: [scan-changes] + memory: + inject_artifacts: + - step: scan-changes + artifact: scan_results + as: scan + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Review the doc scan findings and prioritize fixes. + + For each finding: + 1. Verify it's a real inconsistency (not a false positive) + 2. Assess if it can be fixed by editing docs alone + 3. Prioritize: CRITICAL/HIGH first, then by effort + + Produce a fix plan as markdown: + - Ordered list of fixes to apply + - For each: which file to edit, what to change, why + - Skip items that require code changes (docs-only fixes) + - Estimated scope of changes + output_artifacts: + - name: fix_plan + path: .wave/output/fix-plan.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/fix-plan.md + + - id: fix-docs + persona: craftsman + dependencies: [analyze] + memory: + inject_artifacts: + - step: scan-changes + artifact: scan_results + as: scan + - step: analyze + artifact: fix_plan + as: impl_plan + workspace: + type: worktree + branch: "fix/{{ pipeline_id }}" + exec: + type: prompt + source: | + Fix the documentation inconsistencies on this isolated worktree branch. + + ## Process + + 1. **Apply fixes** following the priority order from the plan: + - Edit documentation files to fix each inconsistency + - Keep changes minimal and focused + - Preserve existing formatting and style + - Do NOT modify source code — docs-only changes + + 2. **Verify**: Ensure no broken links or formatting issues + + 3. **Commit**: + ```bash + git add <changed-doc-files> + git commit -m "docs: sync documentation with implementation + + Fix N documentation inconsistencies found by doc-fix pipeline: + - DOC-001: <title> + - DOC-002: <title> + ..." + ``` + + Write a summary including: + - Branch name + - List of files modified + - Findings fixed vs skipped + - Commit hash + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: false + on_failure: retry + output_artifacts: + - name: result + path: .wave/output/result.md + type: markdown + + - id: create-pr + persona: craftsman + dependencies: [fix-docs] + memory: + inject_artifacts: + - step: scan-changes + artifact: scan_results + as: scan + - step: fix-docs + artifact: result + as: fix_result + workspace: + type: worktree + branch: "fix/{{ pipeline_id }}" + exec: + type: prompt + source: | + Create a pull request for the documentation fixes. + + ## Working Directory + + You are running in an **isolated git worktree** shared with previous pipeline steps. + Your working directory IS the project root. The branch already exists from the + fix-docs step — just push it and create the PR. + + ## SAFETY: Do NOT Modify the Working Tree + + This step MUST NOT run `git checkout`, `git stash`, or any command that changes + the current branch or working tree state. + + ## Instructions + + ### Step 1: Push the Branch + + ```bash + git push -u origin HEAD + ``` + + ### Step 2: Create Pull Request + + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} create --title "docs: sync documentation with implementation" --body "$(cat <<'PREOF' + ## Summary + + Automated documentation sync to fix inconsistencies between docs and code. + + <summarize: N findings fixed, types of issues addressed> + + ## Changes + + <list each doc file modified and what was fixed> + + ## Findings Addressed + + <list each finding ID, type, and resolution> + + ## Skipped + + <list any findings that were skipped and why> + PREOF + )" + ``` + + ### Step 3: Request Copilot Review (Best-Effort) + + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} edit --add-reviewer "copilot" 2>/dev/null || true + ``` + + ## CONSTRAINTS + + - Do NOT spawn Task subagents — work directly in the main context + - Do NOT run `git checkout`, `git stash`, or any branch-switching commands + - Do NOT include Co-Authored-By or AI attribution in commits + + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/doc-onboard.yaml b/.wave/pipelines/doc-onboard.yaml new file mode 100644 index 0000000..a05b2f3 --- /dev/null +++ b/.wave/pipelines/doc-onboard.yaml @@ -0,0 +1,129 @@ +kind: WavePipeline +metadata: + name: doc-onboard + description: "Generate a project onboarding guide for new contributors" + release: true + +skills: + - software-design + +input: + source: cli + example: "create an onboarding guide for this project" + +steps: + - id: survey + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Survey this project to build an onboarding guide: {{ input }} + + ## Survey Checklist + + 1. **Project identity**: Find README, package manifests (go.mod, package.json), + license, and config files. Determine language, framework, purpose. + + 2. **Build system**: How to build, test, and run the project. + Find Makefiles, scripts, CI configs, Dockerfiles. + + 3. **Directory structure**: Map the top-level layout and key directories. + What does each directory contain? + + 4. **Architecture**: Identify the main components and how they interact. + Find entry points (main.go, index.ts, etc.). + + 5. **Dependencies**: List key dependencies and their purposes. + Check go.mod, package.json, requirements.txt, etc. + + 6. **Configuration**: Find environment variables, config files, feature flags. + + 7. **Testing**: Where are tests? How to run them? What patterns are used? + + 8. **Development workflow**: Find contributing guides, PR templates, + commit conventions, branch strategies. + + 9. **Documentation**: Where is documentation? Is it up to date? + output_artifacts: + - name: survey + path: .wave/output/project-survey.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/project-survey.json + schema_path: .wave/contracts/project-survey.schema.json + on_failure: retry + + - id: guide + persona: philosopher + dependencies: [survey] + memory: + inject_artifacts: + - step: survey + artifact: survey + as: project_info + exec: + type: prompt + source: | + Write a comprehensive onboarding guide for new contributors. + + Using the injected project survey data, write a guide with these sections: + + # Onboarding Guide: [Project Name] + + ## Quick Start + - Prerequisites (what to install) + - Clone and build (exact commands) + - Run tests (exact commands) + - Run the project (exact commands) + + ## Project Overview + - What this project does (2-3 sentences) + - Key technologies and why they were chosen + - High-level architecture (ASCII diagram) + + ## Directory Map + - What each top-level directory contains + - Where to find things (tests, configs, docs) + + ## Core Concepts + - Key abstractions and terminology + - How the main components interact + - Data flow through the system + + ## Development Workflow + - How to create a feature branch + - Commit message conventions + - How to run tests before pushing + - PR process + + ## Common Tasks + - "I want to add a new [feature/command/endpoint]" → where to start + - "I want to fix a bug" → debugging approach + - "I want to understand [component]" → where to look + + ## Helpful Resources + - Documentation locations + - Key files to read first + - Related external docs + + Write for someone on their first day with this codebase. + Be specific — use real paths, real commands, real examples. + output_artifacts: + - name: guide + path: .wave/output/onboarding-guide.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/onboarding-guide.md diff --git a/.wave/pipelines/explain.yaml b/.wave/pipelines/explain.yaml new file mode 100644 index 0000000..f2ed6d2 --- /dev/null +++ b/.wave/pipelines/explain.yaml @@ -0,0 +1,131 @@ +kind: WavePipeline +metadata: + name: explain + description: "Deep-dive explanation of code, modules, or architectural patterns" + release: true + +input: + source: cli + example: "explain the pipeline execution system and how steps are scheduled" + +steps: + - id: explore + persona: navigator + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Explore the codebase to understand: {{ input }} + + ## Exploration Steps + + 1. **Find relevant files**: Use Glob and Grep to locate all files related + to the topic. Cast a wide net — include implementations, tests, configs, + and documentation. + + 2. **Trace the call graph**: For key entry points, follow the execution flow. + Note which functions call which, and how data flows through the system. + + 3. **Identify key abstractions**: Find the core types, interfaces, and structs. + Note their responsibilities and relationships. + + 4. **Map dependencies**: Which packages/modules does this depend on? + Which depend on it? + + 5. **Find tests**: Locate test files that exercise this code. + Tests often reveal intended behavior and edge cases. + + 6. **Check configuration**: Find config files, constants, or environment + variables that affect behavior. + + output_artifacts: + - name: exploration + path: .wave/output/exploration.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/exploration.json + schema_path: .wave/contracts/explain-exploration.schema.json + on_failure: retry + max_retries: 2 + + - id: analyze + persona: planner + dependencies: [explore] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: codebase_map + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze the architecture and design of the explored code. + + Review the injected exploration data, then read the key source files identified. Focus on: + + 1. **Design patterns**: What patterns are used and why? + 2. **Data flow**: How does data enter, transform, and exit? + 3. **Error handling**: What's the error strategy? + 4. **Concurrency model**: Goroutines, channels, mutexes? + 5. **Extension points**: Where can new functionality be added? + 6. **Design decisions**: What trade-offs were made? + output_artifacts: + - name: analysis + path: .wave/output/analysis.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/analysis.json + schema_path: .wave/contracts/explain-analysis.schema.json + on_failure: retry + max_retries: 2 + + - id: document + persona: philosopher + dependencies: [analyze] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: codebase_map + - step: analyze + artifact: analysis + as: architecture + exec: + type: prompt + source: | + Write a comprehensive explanation document. + + Review the injected exploration and architecture data, then produce a markdown document with: + + 1. **Overview** — One paragraph summary + 2. **Key Concepts** — Core abstractions and terminology (glossary) + 3. **Architecture** — How pieces fit together (include ASCII diagram) + 4. **How It Works** — Step-by-step main execution flow with file:line refs + 5. **Design Decisions** — Decision → Rationale → Trade-off entries + 6. **Extension Guide** — How to add new functionality + 7. **Testing Strategy** — How the code is tested + 8. **Common Pitfalls** — Things that trip people up + + Write for an experienced developer new to this codebase. + Use real file paths, function names, and type names. + output_artifacts: + - name: explanation + path: .wave/output/explanation.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/explanation.md diff --git a/.wave/pipelines/gh-pr-review.yaml b/.wave/pipelines/gh-pr-review.yaml new file mode 100644 index 0000000..20b52a3 --- /dev/null +++ b/.wave/pipelines/gh-pr-review.yaml @@ -0,0 +1,178 @@ +kind: WavePipeline +metadata: + name: gh-pr-review + description: "GitHub pull request code review with automated security and quality analysis" + release: true + +input: + source: cli + example: "review the authentication module" + +steps: + - id: diff-analysis + persona: navigator + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze the code changes for: {{ input }} + + 1. Identify all modified files and their purposes + 2. Map the change scope (which modules/packages affected) + 3. Find related tests that should be updated + 4. Check for breaking API changes + + Produce a structured result matching the contract schema. + output_artifacts: + - name: diff + path: .wave/output/diff-analysis.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/diff-analysis.json + schema_path: .wave/contracts/diff-analysis.schema.json + on_failure: retry + max_retries: 2 + + - id: security-review + persona: reviewer + dependencies: [diff-analysis] + memory: + inject_artifacts: + - step: diff-analysis + artifact: diff + as: changes + exec: + type: prompt + source: | + Security review of the PR changes. + + Check for: + 1. SQL injection, XSS, CSRF vulnerabilities + 2. Hardcoded secrets or credentials + 3. Insecure deserialization + 4. Missing input validation + 5. Authentication/authorization gaps + 6. Sensitive data exposure + + Output findings with severity (CRITICAL/HIGH/MEDIUM/LOW). + output_artifacts: + - name: security + path: .wave/output/security-review.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/security-review.md + + - id: quality-review + persona: reviewer + dependencies: [diff-analysis] + memory: + inject_artifacts: + - step: diff-analysis + artifact: diff + as: changes + exec: + type: prompt + source: | + Quality review of the PR changes. + + Check for: + 1. Error handling completeness + 2. Edge cases not covered + 3. Code duplication + 4. Naming consistency + 5. Missing or inadequate tests + 6. Performance implications + 7. Documentation gaps + + Output findings with severity and suggestions. + output_artifacts: + - name: quality + path: .wave/output/quality-review.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/quality-review.md + + - id: summary + persona: summarizer + dependencies: [security-review, quality-review] + memory: + inject_artifacts: + - step: security-review + artifact: security + as: security_findings + - step: quality-review + artifact: quality + as: quality_findings + exec: + type: prompt + source: | + Synthesize the review findings into a final verdict. + + Produce a unified review with: + 1. Overall assessment (APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION) + 2. Critical issues that must be fixed + 3. Suggested improvements (optional but recommended) + 4. Positive observations + + Format as a PR review comment ready to post. + Do NOT include a title/header line — the publish step adds one. + output_artifacts: + - name: verdict + path: .wave/output/review-summary.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/review-summary.md + + - id: publish + persona: github-commenter + dependencies: [summary] + memory: + inject_artifacts: + - step: summary + artifact: verdict + as: review_summary + exec: + type: prompt + source: | + Post the code review summary as a PR comment. + + The original input was: {{ input }} + Extract the PR number or URL from the input. + + 1. Post the review as a PR comment using: + gh pr comment <PR_NUMBER_OR_URL> --body "## Code Review (Wave Pipeline) + + <review content> + + --- + *Generated by [Wave](https://github.com/re-cinq/wave) gh-pr-review pipeline*" + + output_artifacts: + - name: publish-result + path: .wave/output/publish-result.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/publish-result.json + schema_path: .wave/contracts/gh-pr-comment-result.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + outcomes: + - type: url + extract_from: .wave/output/publish-result.json + json_path: .comment_url + label: "Review Comment" diff --git a/.wave/pipelines/gh-refresh.yaml b/.wave/pipelines/gh-refresh.yaml new file mode 100644 index 0000000..905c842 --- /dev/null +++ b/.wave/pipelines/gh-refresh.yaml @@ -0,0 +1,184 @@ +kind: WavePipeline +metadata: + name: gh-refresh + description: "Refresh a stale GitHub issue by comparing it against recent codebase changes" + release: true + +input: + source: cli + example: "re-cinq/wave 45 -- acceptance criteria are outdated after the worktree refactor" + schema: + type: string + description: "owner/repo number [-- optional criticism or direction]" + +steps: + - id: gather-context + persona: github-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + MANDATORY: You MUST call the Bash tool. NEVER say "gh CLI not installed" without trying. + + Input: {{ input }} + + Parse the input: + - Split on " -- " to separate the repo+number from optional criticism. + - The first part is "<owner/repo> <number>". Extract REPO (first token) and NUMBER (second token). + - If there is text after " -- ", that is the user's CRITICISM about what's wrong with the issue. + - If there is no " -- ", criticism is empty. + + Execute these commands using the Bash tool: + + 1. gh --version + + 2. Fetch the full issue: + gh issue view NUMBER --repo REPO --json number,title,body,labels,url,createdAt,comments + + 3. Get commits since the issue was created (cap at 100): + git log --since="<createdAt>" --oneline -100 + + 4. Get releases since the issue was created: + gh release list --repo REPO --limit 20 + Then filter to only releases after the issue's createdAt date. + + 5. Scan the issue body for file path references (anything matching patterns like + `internal/...`, `cmd/...`, `.wave/...`, or backtick-quoted paths). + For each referenced file, check if it still exists using `ls -la <path>`. + + 6. Read CLAUDE.md for current project context: + Read the file CLAUDE.md from the repository root. + + After gathering ALL data, produce a JSON result matching the contract schema. + output_artifacts: + - name: issue_context + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-context.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: draft-update + persona: github-analyst + dependencies: [gather-context] + memory: + inject_artifacts: + - step: gather-context + artifact: issue_context + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + MANDATORY: You MUST call the Bash tool for any commands. NEVER generate fake output. + + The context artifact contains the gathered issue context. + + Your task: Compare the original issue against the codebase changes and draft an updated version. + + Step 1: Analyze each section of the issue body. Classify each as: + - STILL_VALID: Content is accurate and up-to-date + - OUTDATED: Content references old behavior, removed files, or superseded patterns + - INCOMPLETE: Content is partially correct but missing recent developments + - WRONG: Content is factually incorrect given current codebase state + + Step 2: If there is user criticism (non-empty "criticism" field), address EVERY point raised. + The criticism takes priority — it represents what the issue author thinks is wrong. + + Step 3: Draft the updated issue: + - Preserve sections classified as STILL_VALID (do not rewrite what works) + - Rewrite OUTDATED and WRONG sections to reflect current reality + - Expand INCOMPLETE sections with missing information + - If the title needs updating, draft a new title + - Append a "---\n**Changes since original**" section at the bottom listing what changed and why + + Step 4: If file paths in the issue body are now missing (from referenced_files.missing), + update or remove those references. + + Produce a JSON result matching the contract schema. + output_artifacts: + - name: update_draft + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-draft.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: apply-update + persona: github-enhancer + dependencies: [draft-update] + memory: + inject_artifacts: + - step: draft-update + artifact: update_draft + as: draft + - step: gather-context + artifact: issue_context + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + CRITICAL: You MUST use the Bash tool for all commands. Do NOT generate fake output. + + Step 1: Use Bash tool to verify gh works: + gh --version + + Step 2: Extract the repo as "<owner>/<name>" and the issue number from the available artifacts. + + Step 3: Apply the update: + - If title_changed is true: + gh issue edit <NUMBER> --repo <REPO> --title "<updated_title>" + - Write the updated_body to a temp file, then apply it: + Write updated_body to /tmp/issue-body.md + gh issue edit <NUMBER> --repo <REPO> --body-file /tmp/issue-body.md + - Clean up /tmp/issue-body.md after applying. + + Step 4: Verify the update was applied: + gh issue view <NUMBER> --repo <REPO> --json number,title,body,url + + Compare the returned title and body against what was intended. Flag any discrepancies. + + Step 5: Record the results as a JSON object matching the contract schema. + output_artifacts: + - name: update_result + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .url + label: "Updated Issue" + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-result.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/gh-research.yaml b/.wave/pipelines/gh-research.yaml new file mode 100644 index 0000000..ffc7258 --- /dev/null +++ b/.wave/pipelines/gh-research.yaml @@ -0,0 +1,255 @@ +kind: WavePipeline +metadata: + name: gh-research + description: Research a GitHub issue and post findings as a comment + release: true + +input: + source: cli + example: "re-cinq/wave 42" + schema: + type: string + description: "GitHub repository and issue number (e.g. 'owner/repo number')" + +steps: + - id: fetch-issue + persona: github-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Fetch the GitHub issue specified in the input: {{ input }} + + The input format is "owner/repo issue_number" (e.g., "re-cinq/CFOAgent 112"). + + Parse the input to extract the repository and issue number. + Use the gh CLI to fetch the issue: + + gh issue view <number> --repo <owner/repo> --json number,title,body,labels,state,author,createdAt,url,comments + + Parse the output and produce structured JSON with the issue content. + Include repository information in the output. + output_artifacts: + - name: issue-content + path: .wave/output/issue-content.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/issue-content.json + schema_path: .wave/contracts/issue-content.schema.json + on_failure: retry + max_retries: 3 + + - id: analyze-topics + persona: researcher + dependencies: [fetch-issue] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Analyze the GitHub issue and extract research topics. + + Identify: + 1. Key technical questions that need external research + 2. Domain concepts that require clarification + 3. External dependencies, libraries, or tools to investigate + 4. Similar problems/solutions that might provide guidance + + For each topic, provide: + - A unique ID (TOPIC-001, TOPIC-002, etc.) + - A clear title + - Specific questions to answer (1-5 questions per topic) + - Search keywords for web research + - Priority (critical/high/medium/low based on relevance to solving the issue) + - Category (technical/documentation/best_practices/security/performance/compatibility/other) + + Focus on topics that will provide actionable insights for the issue author. + Limit to 10 most important topics. + output_artifacts: + - name: topics + path: .wave/output/research-topics.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/research-topics.json + schema_path: .wave/contracts/research-topics.schema.json + on_failure: retry + max_retries: 2 + + - id: research-topics + persona: researcher + dependencies: [analyze-topics] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + - step: analyze-topics + artifact: topics + as: research_plan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Research the topics identified in the research plan. + + For each topic in the research plan: + 1. Execute web searches using the provided keywords + 2. Evaluate source credibility (official docs > authoritative > community) + 3. Extract relevant findings with key points + 4. Include direct quotes where helpful + 5. Rate your confidence in the answer (high/medium/low/inconclusive) + + For each finding: + - Assign a unique ID (FINDING-001, FINDING-002, etc.) + - Provide a summary (20-2000 characters) + - List key points as bullet items + - Include source URL, title, and type + - Rate relevance to the topic (0-1) + + Always include source URLs for attribution. + If a topic yields no useful results, mark confidence as "inconclusive". + Document any gaps in the research. + output_artifacts: + - name: findings + path: .wave/output/research-findings.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/research-findings.json + schema_path: .wave/contracts/research-findings.schema.json + on_failure: retry + max_retries: 2 + + - id: synthesize-report + persona: summarizer + dependencies: [research-topics] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: original_issue + - step: research-topics + artifact: findings + as: research + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Synthesize the research findings into a coherent report for the GitHub issue. + + Create a well-structured research report that includes: + + 1. Executive Summary: + - Brief overview (50-1000 chars) + - Key findings (1-7 bullet points) + - Primary recommendation + - Confidence assessment (high/medium/low) + + 2. Detailed Findings: + - Organize by topic/section + - Include code examples where relevant + - Reference sources using SRC-### IDs + + 3. Recommendations: + - Actionable items with IDs (REC-001, REC-002, etc.) + - Priority and effort estimates + - Maximum 10 recommendations + + 4. Sources: + - List all sources with IDs (SRC-001, SRC-002, etc.) + - Include URL, title, type, and reliability + + 5. Pre-rendered Markdown: + - Generate complete markdown_content field ready for GitHub comment + - Use proper headers, bullet points, and formatting + - Include a header: "## Research Findings (Wave Pipeline)" + - End with sources section + output_artifacts: + - name: report + path: .wave/output/research-report.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/research-report.json + schema_path: .wave/contracts/research-report.schema.json + on_failure: retry + max_retries: 2 + + - id: post-comment + persona: github-commenter + dependencies: [synthesize-report] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + - step: synthesize-report + artifact: report + as: report + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Post the research report as a comment on the GitHub issue. + + Steps: + 1. Read the issue details to get the repository and issue number + 2. Read the report to get the markdown_content + 3. Write the markdown content to a file, then use gh CLI to post the comment: + + # Write to file to avoid shell escaping issues with large markdown + cat > /tmp/comment-body.md << 'COMMENT_EOF' + <markdown_content> + COMMENT_EOF + + gh issue comment <number> --repo <owner/repo> --body-file /tmp/comment-body.md + + 4. Add a footer to the comment: + --- + *Generated by [Wave](https://github.com/re-cinq/wave) issue-research pipeline* + + 5. Capture the result and verify success + 6. If successful, extract the comment URL from the output + + Record the result with: + - success: true/false + - issue_reference: issue number and repository + - comment: id, url, body_length (if successful) + - error: code, message, retryable (if failed) + - timestamp: current time + output_artifacts: + - name: comment-result + path: .wave/output/comment-result.json + type: json + outcomes: + - type: url + extract_from: .wave/output/comment-result.json + json_path: .comment.url + label: "Research Comment" + handover: + contract: + type: json_schema + source: .wave/output/comment-result.json + schema_path: .wave/contracts/comment-result.schema.json + on_failure: retry + max_retries: 3 diff --git a/.wave/pipelines/gh-rewrite.yaml b/.wave/pipelines/gh-rewrite.yaml new file mode 100644 index 0000000..c9b7b57 --- /dev/null +++ b/.wave/pipelines/gh-rewrite.yaml @@ -0,0 +1,99 @@ +kind: WavePipeline +metadata: + name: gh-rewrite + description: "Analyze and rewrite poorly documented GitHub issues" + release: true + +input: + source: cli + example: "re-cinq/wave 42 or https://github.com/re-cinq/wave/issues/42" + schema: + type: string + description: "GitHub repo with optional issue number, or full issue URL" + +steps: + - id: scan-and-score + persona: github-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Input: {{ input }} + + Step 1: Parse the input format. + - If URL (https://github.com/OWNER/REPO/issues/NUM) → extract <REPO> and <NUM> + - If "owner/repo NUM" → extract <REPO> and <NUM> + - If "owner/repo" alone → batch mode, use {{ input }} as <REPO> + + Step 2: Fetch issues via gh CLI. + - Single: gh issue view <NUM> --repo <REPO> --json number,title,body,labels,url + - Batch: gh issue list --repo {{ input }} --limit 10 --json number,title,body,labels,url + + Step 3: Score each issue quality (0-100) on title clarity, description completeness, labels, and acceptance criteria. + + Step 4: For issues scoring below 70, create an enhancement plan with: + - suggested_title, body_template (preserving original content), suggested_labels, enhancements list + + Output JSON with repository (owner/repo string), issues_to_enhance array, and total_to_enhance. + output_artifacts: + - name: enhancement_plan + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/github-enhancement-plan.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: apply-enhancements + persona: github-enhancer + dependencies: [scan-and-score] + memory: + inject_artifacts: + - step: scan-and-score + artifact: enhancement_plan + as: plan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Read the "repository" field from the plan artifact to get <REPO>. + + For each issue in issues_to_enhance: + 1. Apply title: gh issue edit <NUM> --repo <REPO> --title "suggested_title" + 2. Apply body: gh issue edit <NUM> --repo <REPO> --body "body_template" + 3. Add labels: gh issue edit <NUM> --repo <REPO> --add-label "label1,label2" + 4. Capture URL: gh issue view <NUM> --repo <REPO> --json url --jq .url + + Output JSON with enhanced_issues (issue_number, success, changes_made, url), + total_attempted, total_successful. + output_artifacts: + - name: enhancement_results + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .enhanced_issues[0].url + label: "Enhanced Issue" + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/github-enhancement-results.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/gh-scope.yaml b/.wave/pipelines/gh-scope.yaml new file mode 100644 index 0000000..92157ad --- /dev/null +++ b/.wave/pipelines/gh-scope.yaml @@ -0,0 +1,173 @@ +kind: WavePipeline +metadata: + name: gh-scope + description: "Decompose a GitHub epic into well-scoped child issues" + release: true + +input: + source: cli + example: "re-cinq/wave 184" + schema: + type: string + description: "GitHub repository with epic issue number (e.g. 'owner/repo 42')" + +steps: + - id: fetch-epic + persona: github-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + MANDATORY: You MUST call the Bash tool. NEVER say "gh CLI not installed" without trying. + + Input: {{ input }} + + Parse the input: extract the repo (first token) and the epic issue number (second token). + + Execute these commands using the Bash tool: + + 1. gh --version + + 2. Fetch the epic issue with full details: + gh issue view <NUMBER> --repo <REPO> --json number,title,body,labels,url,comments,author,state + + 3. List existing open issues to check for duplicates: + gh issue list --repo <REPO> --limit 50 --json number,title,labels,url + + After getting REAL results from Bash, analyze the epic: + - Determine if this is truly an epic/umbrella issue (contains multiple work items) + - Identify the key themes and work areas + - Estimate overall complexity + - Count how many sub-issues should be created (3-10) + - List existing issues to avoid creating duplicates + output_artifacts: + - name: epic_assessment + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/epic-assessment.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: scope-and-create + persona: github-scoper + dependencies: [fetch-epic] + memory: + inject_artifacts: + - step: fetch-epic + artifact: epic_assessment + as: assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + CRITICAL: You MUST use the Bash tool for all commands. Do NOT generate fake output. + + The assessment artifact contains the epic analysis. Use it to create well-scoped child issues. + + Input: {{ input }} + Parse the repo from the input (first token). + + Step 1: Verify gh works: + gh --version + + Step 2: For each planned sub-issue, create it using: + gh issue create --repo <REPO> --title "<title>" --body "<body>" --label "<labels>" + + Each sub-issue body MUST include: + - A "Parent: #<epic_number>" reference line + - A clear Summary section + - Acceptance Criteria as a checkbox list + - Dependencies on other sub-issues if applicable + - Scope Notes for what is explicitly excluded + + Step 3: After creating all issues, capture each issue's number and URL from the creation output. + + Step 4: Record the results with fields: parent_issue (number, url, repository), + created_issues (array of number, title, url, labels, success, complexity, dependencies), + total_created, total_failed. + output_artifacts: + - name: scope_plan + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .created_issues[0].url + label: "First Sub-Issue" + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/scope-plan.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: verify-report + persona: github-analyst + dependencies: [scope-and-create] + memory: + inject_artifacts: + - step: scope-and-create + artifact: scope_plan + as: results + - step: fetch-epic + artifact: epic_assessment + as: assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Verify the created sub-issues and post a summary comment on the epic. + + Input: {{ input }} + Parse the repo (first token) and epic number (second token). + + Step 1: For each created issue in the results, verify it exists: + gh issue view <N> --repo <REPO> --json number,title,body,labels + + Check that each issue: + - Exists and is open + - Has acceptance criteria in the body + - References the parent epic + + Step 2: Post a summary comment on the epic issue listing all created sub-issues: + Create a markdown summary with a checklist of all sub-issues (- [ ] #<number> <title>) + and post it using: gh issue comment <EPIC_NUMBER> --repo <REPO> --body "<summary>" + + Step 3: Compile the verification report with fields: + parent_issue (number, url), verified_issues (array of number, title, url, exists, + has_acceptance_criteria, references_parent), summary (total_verified, total_valid, + total_issues_created, comment_posted, comment_url). + output_artifacts: + - name: scope_report + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/scope-report.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/gt-refresh.yaml b/.wave/pipelines/gt-refresh.yaml new file mode 100644 index 0000000..2365f98 --- /dev/null +++ b/.wave/pipelines/gt-refresh.yaml @@ -0,0 +1,184 @@ +kind: WavePipeline +metadata: + name: gt-refresh + description: "Refresh a stale Gitea issue by comparing it against recent codebase changes" + release: true + +input: + source: cli + example: "re-cinq/wave 45 -- acceptance criteria are outdated after the worktree refactor" + schema: + type: string + description: "owner/repo number [-- optional criticism or direction]" + +steps: + - id: gather-context + persona: gitea-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + MANDATORY: You MUST call the Bash tool. NEVER say "tea CLI not installed" without trying. + + Input: {{ input }} + + Parse the input: + - Split on " -- " to separate the repo+number from optional criticism. + - The first part is "<owner/repo> <number>". Extract REPO (first token) and NUMBER (second token). + - If there is text after " -- ", that is the user's CRITICISM about what's wrong with the issue. + - If there is no " -- ", criticism is empty. + + Execute these commands using the Bash tool: + + 1. tea --version + + 2. Fetch the full issue: + tea issues view NUMBER --repo REPO --json number,title,body,labels,url,createdAt,comments + + 3. Get commits since the issue was created (cap at 100): + git log --since="<createdAt>" --oneline -100 + + 4. Get releases since the issue was created: + tea releases list --repo REPO --limit 20 + Then filter to only releases after the issue's createdAt date. + + 5. Scan the issue body for file path references (anything matching patterns like + `internal/...`, `cmd/...`, `.wave/...`, or backtick-quoted paths). + For each referenced file, check if it still exists using `ls -la <path>`. + + 6. Read CLAUDE.md for current project context: + Read the file CLAUDE.md from the repository root. + + After gathering ALL data, produce a JSON result matching the contract schema. + output_artifacts: + - name: issue_context + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-context.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: draft-update + persona: gitea-analyst + dependencies: [gather-context] + memory: + inject_artifacts: + - step: gather-context + artifact: issue_context + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + MANDATORY: You MUST call the Bash tool for any commands. NEVER generate fake output. + + The context artifact contains the gathered issue context. + + Your task: Compare the original issue against the codebase changes and draft an updated version. + + Step 1: Analyze each section of the issue body. Classify each as: + - STILL_VALID: Content is accurate and up-to-date + - OUTDATED: Content references old behavior, removed files, or superseded patterns + - INCOMPLETE: Content is partially correct but missing recent developments + - WRONG: Content is factually incorrect given current codebase state + + Step 2: If there is user criticism (non-empty "criticism" field), address EVERY point raised. + The criticism takes priority — it represents what the issue author thinks is wrong. + + Step 3: Draft the updated issue: + - Preserve sections classified as STILL_VALID (do not rewrite what works) + - Rewrite OUTDATED and WRONG sections to reflect current reality + - Expand INCOMPLETE sections with missing information + - If the title needs updating, draft a new title + - Append a "---\n**Changes since original**" section at the bottom listing what changed and why + + Step 4: If file paths in the issue body are now missing (from referenced_files.missing), + update or remove those references. + + Produce a JSON result matching the contract schema. + output_artifacts: + - name: update_draft + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-draft.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: apply-update + persona: gitea-enhancer + dependencies: [draft-update] + memory: + inject_artifacts: + - step: draft-update + artifact: update_draft + as: draft + - step: gather-context + artifact: issue_context + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + CRITICAL: You MUST use the Bash tool for all commands. Do NOT generate fake output. + + Step 1: Use Bash tool to verify tea works: + tea --version + + Step 2: Extract the repo as "<owner>/<name>" and the issue number from the available artifacts. + + Step 3: Apply the update: + - If title_changed is true: + tea issues edit <NUMBER> --repo <REPO> --title "<updated_title>" + - Write the updated_body to a temp file, then apply it: + Write updated_body to /tmp/issue-body.md + tea issues edit <NUMBER> --repo <REPO> --body-file /tmp/issue-body.md + - Clean up /tmp/issue-body.md after applying. + + Step 4: Verify the update was applied: + tea issues view <NUMBER> --repo <REPO> --json number,title,body,url + + Compare the returned title and body against what was intended. Flag any discrepancies. + + Step 5: Record the results as a JSON object matching the contract schema. + output_artifacts: + - name: update_result + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .url + label: "Updated Issue" + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-result.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/gt-research.yaml b/.wave/pipelines/gt-research.yaml new file mode 100644 index 0000000..96ff20d --- /dev/null +++ b/.wave/pipelines/gt-research.yaml @@ -0,0 +1,255 @@ +kind: WavePipeline +metadata: + name: gt-research + description: Research a Gitea issue and post findings as a comment + release: true + +input: + source: cli + example: "re-cinq/wave 42" + schema: + type: string + description: "Gitea repository and issue number (e.g. 'owner/repo number')" + +steps: + - id: fetch-issue + persona: gitea-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Fetch the Gitea issue specified in the input: {{ input }} + + The input format is "owner/repo issue_number" (e.g., "re-cinq/CFOAgent 112"). + + Parse the input to extract the repository and issue number. + Use the tea CLI to fetch the issue: + + tea issues view <number> --repo <owner/repo> --json number,title,body,labels,state,author,createdAt,url,comments + + Parse the output and produce structured JSON with the issue content. + Include repository information in the output. + output_artifacts: + - name: issue-content + path: .wave/output/issue-content.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/issue-content.json + schema_path: .wave/contracts/issue-content.schema.json + on_failure: retry + max_retries: 3 + + - id: analyze-topics + persona: researcher + dependencies: [fetch-issue] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Analyze the Gitea issue and extract research topics. + + Identify: + 1. Key technical questions that need external research + 2. Domain concepts that require clarification + 3. External dependencies, libraries, or tools to investigate + 4. Similar problems/solutions that might provide guidance + + For each topic, provide: + - A unique ID (TOPIC-001, TOPIC-002, etc.) + - A clear title + - Specific questions to answer (1-5 questions per topic) + - Search keywords for web research + - Priority (critical/high/medium/low based on relevance to solving the issue) + - Category (technical/documentation/best_practices/security/performance/compatibility/other) + + Focus on topics that will provide actionable insights for the issue author. + Limit to 10 most important topics. + output_artifacts: + - name: topics + path: .wave/output/research-topics.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/research-topics.json + schema_path: .wave/contracts/research-topics.schema.json + on_failure: retry + max_retries: 2 + + - id: research-topics + persona: researcher + dependencies: [analyze-topics] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + - step: analyze-topics + artifact: topics + as: research_plan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Research the topics identified in the research plan. + + For each topic in the research plan: + 1. Execute web searches using the provided keywords + 2. Evaluate source credibility (official docs > authoritative > community) + 3. Extract relevant findings with key points + 4. Include direct quotes where helpful + 5. Rate your confidence in the answer (high/medium/low/inconclusive) + + For each finding: + - Assign a unique ID (FINDING-001, FINDING-002, etc.) + - Provide a summary (20-2000 characters) + - List key points as bullet items + - Include source URL, title, and type + - Rate relevance to the topic (0-1) + + Always include source URLs for attribution. + If a topic yields no useful results, mark confidence as "inconclusive". + Document any gaps in the research. + output_artifacts: + - name: findings + path: .wave/output/research-findings.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/research-findings.json + schema_path: .wave/contracts/research-findings.schema.json + on_failure: retry + max_retries: 2 + + - id: synthesize-report + persona: summarizer + dependencies: [research-topics] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: original_issue + - step: research-topics + artifact: findings + as: research + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Synthesize the research findings into a coherent report for the Gitea issue. + + Create a well-structured research report that includes: + + 1. Executive Summary: + - Brief overview (50-1000 chars) + - Key findings (1-7 bullet points) + - Primary recommendation + - Confidence assessment (high/medium/low) + + 2. Detailed Findings: + - Organize by topic/section + - Include code examples where relevant + - Reference sources using SRC-### IDs + + 3. Recommendations: + - Actionable items with IDs (REC-001, REC-002, etc.) + - Priority and effort estimates + - Maximum 10 recommendations + + 4. Sources: + - List all sources with IDs (SRC-001, SRC-002, etc.) + - Include URL, title, type, and reliability + + 5. Pre-rendered Markdown: + - Generate complete markdown_content field ready for Gitea comment + - Use proper headers, bullet points, and formatting + - Include a header: "## Research Findings (Wave Pipeline)" + - End with sources section + output_artifacts: + - name: report + path: .wave/output/research-report.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/research-report.json + schema_path: .wave/contracts/research-report.schema.json + on_failure: retry + max_retries: 2 + + - id: post-comment + persona: gitea-commenter + dependencies: [synthesize-report] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + - step: synthesize-report + artifact: report + as: report + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Post the research report as a comment on the Gitea issue. + + Steps: + 1. Read the issue details to get the repository and issue number + 2. Read the report to get the markdown_content + 3. Write the markdown content to a file, then use tea CLI to post the comment: + + # Write to file to avoid shell escaping issues with large markdown + cat > /tmp/comment-body.md << 'COMMENT_EOF' + <markdown_content> + COMMENT_EOF + + tea issues comment <number> --repo <owner/repo> --body-file /tmp/comment-body.md + + 4. Add a footer to the comment: + --- + *Generated by [Wave](https://github.com/re-cinq/wave) issue-research pipeline* + + 5. Capture the result and verify success + 6. If successful, extract the comment URL from the output + + Record the result with: + - success: true/false + - issue_reference: issue number and repository + - comment: id, url, body_length (if successful) + - error: code, message, retryable (if failed) + - timestamp: current time + output_artifacts: + - name: comment-result + path: .wave/output/comment-result.json + type: json + outcomes: + - type: url + extract_from: .wave/output/comment-result.json + json_path: .comment.url + label: "Research Comment" + handover: + contract: + type: json_schema + source: .wave/output/comment-result.json + schema_path: .wave/contracts/comment-result.schema.json + on_failure: retry + max_retries: 3 diff --git a/.wave/pipelines/gt-rewrite.yaml b/.wave/pipelines/gt-rewrite.yaml new file mode 100644 index 0000000..825e211 --- /dev/null +++ b/.wave/pipelines/gt-rewrite.yaml @@ -0,0 +1,98 @@ +kind: WavePipeline +metadata: + name: gt-rewrite + description: "Analyze and rewrite poorly documented Gitea issues" + release: true + +input: + source: cli + example: "re-cinq/wave 42" + schema: + type: string + description: "Gitea repo with optional issue number" + +steps: + - id: scan-and-score + persona: gitea-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Input: {{ input }} + + Step 1: Parse input format. + - If "owner/repo NUM" → single issue mode + - If "owner/repo" alone → batch mode + + Step 2: Fetch issues via tea CLI. + - Single: tea issues view NUM --repo OWNER/REPO --json number,title,body,labels,url + - Batch: tea issues list --repo OWNER/REPO --limit 10 --json number,title,body,labels,url + + Step 3: Score each issue quality (0-100) on title clarity, description completeness, labels, and acceptance criteria. + + Step 4: For issues scoring below 70, create an enhancement plan with: + - suggested_title, body_template (preserving original content), suggested_labels, enhancements list + + Output JSON with repository (owner/repo string), issues_to_enhance array, and total_to_enhance. + output_artifacts: + - name: enhancement_plan + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/github-enhancement-plan.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: apply-enhancements + persona: gitea-enhancer + dependencies: [scan-and-score] + memory: + inject_artifacts: + - step: scan-and-score + artifact: enhancement_plan + as: plan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Read the repository field from the plan artifact for --repo flag. + + For each issue in issues_to_enhance: + 1. Apply title: tea issues edit NUM --repo REPO --title "suggested_title" + 2. Apply body: tea issues edit NUM --repo REPO --body "body_template" + 3. Add labels: tea issues edit NUM --repo REPO --add-label "label1,label2" + 4. Capture URL: tea issues view NUM --repo REPO --json url --jq .url + + Output JSON with enhanced_issues (issue_number, success, changes_made, url), + total_attempted, total_successful. + output_artifacts: + - name: enhancement_results + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .enhanced_issues[0].url + label: "Enhanced Issue" + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/github-enhancement-results.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/gt-scope.yaml b/.wave/pipelines/gt-scope.yaml new file mode 100644 index 0000000..1876939 --- /dev/null +++ b/.wave/pipelines/gt-scope.yaml @@ -0,0 +1,167 @@ +kind: WavePipeline +metadata: + name: gt-scope + description: "Decompose a Gitea epic into well-scoped child issues" + release: true + +input: + source: cli + example: "re-cinq/wave 42" + schema: + type: string + description: "Gitea repository with epic issue number (e.g. 'owner/repo 42')" + +steps: + - id: fetch-epic + persona: gitea-analyst + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + MANDATORY: You MUST call the Bash tool. NEVER say "tea CLI not installed" without trying. + + Input: {{ input }} + + Parse the input: extract the repo (first token) and the epic issue number (second token). + + Execute these commands using the Bash tool: + + 1. tea --version + + 2. Fetch the epic issue with full details: + tea issues view <NUMBER> --output json + + 3. List existing open issues to check for duplicates: + tea issues list --limit 50 --output json + + After getting REAL results from Bash, analyze the epic: + - Determine if this is truly an epic/umbrella issue (contains multiple work items) + - Identify the key themes and work areas + - Estimate overall complexity + - Count how many sub-issues should be created (3-10) + - List existing issues to avoid creating duplicates + output_artifacts: + - name: epic_assessment + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/epic-assessment.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: scope-and-create + persona: gitea-scoper + dependencies: [fetch-epic] + memory: + inject_artifacts: + - step: fetch-epic + artifact: epic_assessment + as: assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + CRITICAL: You MUST use the Bash tool for all commands. Do NOT generate fake output. + + The assessment artifact contains the epic analysis. Use it to create well-scoped child issues. + + Step 1: Verify tea works: + tea --version + + Step 2: For each planned sub-issue, create it using: + tea issues create --title "<title>" --body "<body>" --labels "<labels>" + + Each sub-issue body MUST include: + - A "Parent: #<epic_number>" reference line + - A clear Summary section + - Acceptance Criteria as a checkbox list + - Dependencies on other sub-issues if applicable + - Scope Notes for what is explicitly excluded + + Step 3: After creating all issues, capture each issue's number and URL from the creation output. + + Step 4: Record the results with fields: parent_issue (number, url, repository), + created_issues (array of number, title, url, labels, success, complexity, dependencies), + total_created, total_failed. + output_artifacts: + - name: scope_plan + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .created_issues[0].url + label: "First Sub-Issue" + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/scope-plan.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: verify-report + persona: gitea-analyst + dependencies: [scope-and-create] + memory: + inject_artifacts: + - step: scope-and-create + artifact: scope_plan + as: results + - step: fetch-epic + artifact: epic_assessment + as: assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Verify the created sub-issues and post a summary comment on the epic. + + Step 1: For each created issue in the results, verify it exists: + tea issues view <N> --output json + + Check that each issue: + - Exists and is open + - Has acceptance criteria in the body + - References the parent epic + + Step 2: Post a summary comment on the epic issue listing all created sub-issues: + Create a markdown summary with a checklist of all sub-issues (- [ ] #<number> <title>) + and post it using: tea issues comment <EPIC_NUMBER> --body "<summary>" + + Step 3: Compile the verification report with fields: + parent_issue (number, url), verified_issues (array of number, title, url, exists, + has_acceptance_criteria, references_parent), summary (total_verified, total_valid, + total_issues_created, comment_posted, comment_url). + output_artifacts: + - name: scope_report + path: .wave/artifact.json + type: json + required: true + handover: + max_retries: 1 + contract: + type: json_schema + schema_path: .wave/contracts/scope-report.schema.json + validate: true + must_pass: true + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/impl-feature.yaml b/.wave/pipelines/impl-feature.yaml new file mode 100644 index 0000000..0888e4d --- /dev/null +++ b/.wave/pipelines/impl-feature.yaml @@ -0,0 +1,214 @@ +kind: WavePipeline +metadata: + name: impl-feature + description: "Plan, implement, test, and commit a feature to a new branch" + release: true + +skills: + - "{{ project.skill }}" + - software-design + +input: + source: cli + example: "add a --dry-run flag to the run command" + +steps: + - id: explore + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Explore the codebase to plan this feature: {{ input }} + + ## Exploration + + 1. **Understand the request**: What is being asked? Assess scope + (small = 1-2 files, medium = 3-7, large = 8+). + + 2. **Find related code**: Use Glob and Grep to find files related + to the feature. Note paths, relevance, and key symbols. + + 3. **Identify patterns**: Read key files. Document conventions that + must be followed (naming, error handling, testing patterns). + + 4. **Map affected modules**: Which packages are directly/indirectly affected? + + 5. **Survey tests**: Find related test files, testing patterns, gaps. + + 6. **Assess risks**: Breaking changes, performance, security implications. + + Produce a structured JSON result matching the contract schema. + output_artifacts: + - name: exploration + path: .wave/output/exploration.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/exploration.json + schema_path: .wave/contracts/feature-exploration.schema.json + on_failure: retry + + - id: plan + persona: planner + dependencies: [explore] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: context + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Create an implementation plan for this feature. + + Feature: {{ input }} + + The codebase exploration has been injected into your workspace. Read it first. + + Break the feature into ordered implementation steps: + + 1. For each step: what to do, which files to modify, acceptance criteria + 2. Dependencies between steps + 3. What tests to write + 4. Complexity estimate per step (S/M/L) + + Produce a structured JSON result matching the contract schema. + output_artifacts: + - name: plan + path: .wave/output/plan.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/plan.json + schema_path: .wave/contracts/feature-plan.schema.json + on_failure: retry + + - id: implement + persona: craftsman + dependencies: [plan] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: context + - step: plan + artifact: plan + as: impl_plan + workspace: + type: worktree + branch: "feat/{{ pipeline_id }}" + exec: + type: prompt + source: | + Implement the feature on this isolated worktree branch. + + The codebase exploration and implementation plan have been injected into your + workspace. Read them both before starting. + + Feature: {{ input }} + + ## Process + + 1. **Implement step by step** following the plan: + - Follow existing codebase patterns identified in exploration + - Write tests alongside implementation + - After each significant change, verify it compiles + + 2. Run the full test suite and fix any failures before proceeding. + + 3. **Commit**: + ```bash + git add <specific-files> + git commit -m "<commit_message_suggestion from plan> + + Implementation following plan: + - S01: <title> + - S02: <title> + ..." + ``` + + Commit changes to the worktree branch. + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + compaction: + trigger: "token_limit_80%" + persona: summarizer + output_artifacts: + - name: result + path: .wave/output/result.md + type: markdown + + # ── Publish ───────────────────────────────────────────────────────── + - id: publish + persona: craftsman + dependencies: [implement] + memory: + inject_artifacts: + - step: implement + artifact: result + as: result + workspace: + type: worktree + branch: "feat/{{ pipeline_id }}" + exec: + type: prompt + source: | + PUBLISH — push the branch and create a pull request. + + ## Steps + + 1. Push the branch: + ```bash + git push -u origin HEAD + ``` + + 2. Create a pull request using the implementation result as context: + ```bash + COMMIT_SUBJECT=$(git log --format=%s -1) + {{ forge.cli_tool }} {{ forge.pr_command }} create --title "feat: $COMMIT_SUBJECT" --body-file .wave/artifacts/result + ``` + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/impl-hotfix.yaml b/.wave/pipelines/impl-hotfix.yaml new file mode 100644 index 0000000..f08dfde --- /dev/null +++ b/.wave/pipelines/impl-hotfix.yaml @@ -0,0 +1,118 @@ +kind: WavePipeline +metadata: + name: impl-hotfix + description: "Quick investigation and fix for production issues" + release: true + +skills: + - "{{ project.skill }}" + - software-design + +input: + source: cli + example: "fix panic in pipeline executor when step has nil context" + +steps: + - id: investigate + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Investigate this production issue: {{ input }} + + 1. Search for related code paths + 2. Check recent commits that may have introduced the bug + 3. Identify the root cause + 4. Assess blast radius (what else could be affected) + output_artifacts: + - name: findings + path: .wave/output/findings.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/findings.json + schema_path: .wave/contracts/findings.schema.json + on_failure: retry + + - id: fix + persona: craftsman + dependencies: [investigate] + thread: hotfix + memory: + inject_artifacts: + - step: investigate + artifact: findings + as: investigation + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Fix the production issue based on the investigation findings. + + Requirements: + 1. Apply the minimal fix - don't refactor surrounding code + 2. Add a regression test that would have caught this bug + 3. Ensure all existing tests still pass + 4. Document the fix in a commit-ready message + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + compaction: + trigger: "token_limit_80%" + persona: summarizer + + - id: run-tests + type: command + dependencies: [fix] + script: "{{ project.contract_test_command }}" + + - id: gate + type: conditional + dependencies: [run-tests] + edges: + - target: verify + condition: "outcome=success" + - target: fix + + - id: verify + persona: reviewer + dependencies: [gate] + exec: + type: prompt + source: | + Verify the hotfix: + + 1. Is the fix minimal and focused? (no unrelated changes) + 2. Does the regression test actually test the reported issue? + 3. Are there other code paths with the same vulnerability? + 4. Is the fix safe for production deployment? + + Output a go/no-go recommendation with reasoning. + output_artifacts: + - name: verdict + path: .wave/output/verdict.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/verdict.md diff --git a/.wave/pipelines/impl-improve.yaml b/.wave/pipelines/impl-improve.yaml new file mode 100644 index 0000000..ba96ec5 --- /dev/null +++ b/.wave/pipelines/impl-improve.yaml @@ -0,0 +1,133 @@ +kind: WavePipeline +metadata: + name: impl-improve + description: "Analyze code and apply targeted improvements" + release: true + +skills: + - "{{ project.skill }}" + - software-design + +input: + source: cli + example: "improve error handling in internal/pipeline" + +steps: + - id: assess + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Assess the code for improvement opportunities: {{ input }} + + ## Assessment Areas + + 1. **Code quality**: Readability, naming, structure, duplication + 2. **Error handling**: Missing checks, swallowed errors, unclear messages + 3. **Performance**: Unnecessary allocations, N+1 patterns, missing caching + 4. **Testability**: Hard-to-test code, missing interfaces, tight coupling + 5. **Robustness**: Missing nil checks, race conditions, resource leaks + 6. **Maintainability**: Complex functions, deep nesting, magic numbers + + For each finding, assess: + - Impact: how much does fixing it improve the code? + - Effort: how hard is the fix? + - Risk: could the fix introduce regressions? + output_artifacts: + - name: assessment + path: .wave/output/assessment.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/assessment.json + schema_path: .wave/contracts/improvement-assessment.schema.json + on_failure: retry + + - id: implement + persona: craftsman + dependencies: [assess] + memory: + inject_artifacts: + - step: assess + artifact: assessment + as: findings + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Apply the recommended improvements to the codebase. + + ## Rules + + 1. **Start with quick wins**: Apply trivial/small effort fixes first + 2. **One improvement at a time**: Make each change, verify it compiles, + then move to the next + 3. **Preserve behavior**: Improvements must not change external behavior + 4. **Run tests**: After each significant change, run relevant tests + 5. **Skip high-risk items**: Do not apply changes rated risk=high + without explicit test coverage + 6. **Document changes**: Track what was changed and why + + Focus on the findings with the best impact-to-effort ratio. + Do NOT refactor beyond what was identified in the assessment. + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + compaction: + trigger: "token_limit_80%" + persona: summarizer + + - id: verify + persona: reviewer + dependencies: [implement] + memory: + inject_artifacts: + - step: assess + artifact: assessment + as: original_findings + exec: + type: prompt + source: | + Verify the improvements were applied correctly. + + For each improvement that was applied: + 1. Is the fix correct and complete? + 2. Does it actually address the identified issue? + 3. Were any new issues introduced? + 4. Are tests still passing? + + For improvements NOT applied, confirm they were appropriately skipped. + + Produce a verification report covering: + - Applied improvements (with before/after) + - Skipped items (with justification) + - New issues found (if any) + - Overall quality delta assessment + output_artifacts: + - name: verification + path: .wave/output/verification.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/verification.md diff --git a/.wave/pipelines/impl-issue.yaml b/.wave/pipelines/impl-issue.yaml new file mode 100644 index 0000000..855047b --- /dev/null +++ b/.wave/pipelines/impl-issue.yaml @@ -0,0 +1,155 @@ +kind: WavePipeline +metadata: + name: impl-issue + description: "Implement an issue end-to-end: fetch, assess, plan, implement, create PR" + release: true + +chat_context: + artifact_summaries: + - assessment + - impl-plan + - pr-result + suggested_questions: + - "Would you like to review the changes in the pull request?" + - "Are there any failing tests to investigate?" + - "Should we refine the implementation or add more test coverage?" + focus_areas: + - "Code changes and implementation quality" + - "Test results and coverage" + - "PR status and review readiness" + +skills: + - "{{ project.skill }}" + - gh-cli + - software-design + +requires: + tools: + - gh + +input: + source: cli + schema: + type: string + description: "GitHub repository and issue number" + example: "re-cinq/wave 42" + +steps: + - id: fetch-assess + persona: implementer + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/implement/fetch-assess.md + output_artifacts: + - name: assessment + path: .wave/output/issue-assessment.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/issue-assessment.json + schema_path: .wave/contracts/issue-assessment.schema.json + must_pass: true + on_failure: retry + + - id: plan + persona: implementer + dependencies: [fetch-assess] + memory: + inject_artifacts: + - step: fetch-assess + artifact: assessment + as: issue_assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + base: main + exec: + type: prompt + source_path: .wave/prompts/implement/plan.md + output_artifacts: + - name: impl-plan + path: .wave/output/impl-plan.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/impl-plan.json + schema_path: .wave/contracts/issue-impl-plan.schema.json + must_pass: true + on_failure: retry + + - id: implement + persona: craftsman + thread: impl + dependencies: [plan] + memory: + inject_artifacts: + - step: fetch-assess + artifact: assessment + as: issue_assessment + - step: plan + artifact: impl-plan + as: impl_plan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/implement/implement.md + retry: + policy: aggressive + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + compaction: + trigger: "token_limit_80%" + persona: summarizer + + - id: create-pr + persona: "gitea-commenter" + dependencies: [implement] + memory: + inject_artifacts: + - step: fetch-assess + artifact: assessment + as: issue_assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/implement/create-pr.md + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/impl-prototype.yaml b/.wave/pipelines/impl-prototype.yaml new file mode 100644 index 0000000..d905f44 --- /dev/null +++ b/.wave/pipelines/impl-prototype.yaml @@ -0,0 +1,367 @@ +kind: WavePipeline +metadata: + name: impl-prototype + description: "Prototype-driven implementation: spec → docs → dummy → implement → PR cycle" + release: true + +input: + source: cli + example: "build a REST API for user management with CRUD operations" + +steps: + # Phase 1: Spec - Requirements capture with speckit integration + - id: spec + persona: craftsman + exec: + type: prompt + source: | + You are beginning the specification phase of a prototype-driven development pipeline. + + Your goal is to analyze the project description and create a comprehensive feature specification: + + Project description: {{ input }} + + CRITICAL: Create both spec.md and requirements.md files: + + spec.md should contain the complete feature specification including: + - Feature overview and business value + - User stories with acceptance criteria + - Functional requirements + - Success criteria and measurable outcomes + - Constraints and assumptions + + requirements.md should contain extracted requirements (optional additional detail). + + Use speckit integration where available to enhance specification quality. + + The specification must be technology-agnostic and focused on user value. + + Create artifact.json with your results. + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + output_artifacts: + - name: spec + path: spec.md + type: markdown + - name: requirements + path: requirements.md + type: markdown + - name: contract_data + path: .wave/artifact.json + type: json + + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/spec-phase.schema.json + must_pass: true + on_failure: retry + + # Phase 2: Docs - Generate runnable documentation from specification + - id: docs + persona: philosopher + dependencies: [spec] + + memory: + inject_artifacts: + - step: spec + artifact: spec + as: input-spec.md + + exec: + type: prompt + source: | + You are in the documentation phase of prototype-driven development. + + Your goal is to create comprehensive, runnable documentation from the specification. + + Create feature documentation from the injected specification that includes: + - User-friendly explanation of the feature + - Usage examples and scenarios + - Integration guide for developers + - Stakeholder summary for non-technical audiences + + Generate VitePress-compatible markdown that can be served as runnable documentation. + + CRITICAL: Create both feature-docs.md and stakeholder-summary.md files. + + Create artifact.json with your results. + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + output_artifacts: + - name: feature-docs + path: feature-docs.md + type: markdown + - name: stakeholder-summary + path: stakeholder-summary.md + type: markdown + - name: contract_data + path: .wave/artifact.json + type: json + + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/docs-phase.schema.json + must_pass: true + on_failure: retry + + # Phase 3: Dummy - Build authentic functional prototype + - id: dummy + persona: craftsman + dependencies: [docs] + + memory: + inject_artifacts: + - step: docs + artifact: feature-docs + as: feature-docs.md + - step: spec + artifact: spec + as: spec.md + + exec: + type: prompt + source: | + You are in the dummy/prototype phase of development. + + Your goal is to create a working prototype with authentic I/O handling but stub business logic. + + Create a functional prototype that: + - Handles real input and output properly + - Implements all user interfaces and endpoints + - Uses placeholder/stub implementations for business logic + - Can be run and demonstrated to stakeholders + - Shows the complete user experience flow + + Focus on proving the interface design and user flows work correctly. + + CRITICAL: Create prototype/ directory with working code and interfaces.md with interface definitions. + + Create artifact.json with your results. + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + output_artifacts: + - name: prototype + path: prototype/ + type: binary + - name: interface-definitions + path: interfaces.md + type: markdown + - name: contract_data + path: .wave/artifact.json + type: json + + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/dummy-phase.schema.json + must_pass: true + on_failure: retry + + # Phase 4: Implement - Transition to full implementation + - id: implement + persona: craftsman + dependencies: [dummy] + + memory: + inject_artifacts: + - step: spec + artifact: spec + as: spec.md + - step: docs + artifact: feature-docs + as: feature-docs.md + - step: dummy + artifact: prototype + as: prototype/ + + exec: + type: prompt + source: | + You are in the implementation phase - transitioning from prototype to production code. + + Your goal is to provide implementation guidance and begin real implementation: + - Review all previous artifacts for implementation readiness + - Create implementation plan and checklist + - Begin replacing stub logic with real implementations + - Ensure test coverage for all functionality + - Maintain compatibility with established interfaces + + Focus on production-quality code that fulfills the original specification. + + CRITICAL: Create implementation-plan.md and implementation-checklist.md files. + + Create artifact.json with your results. + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + output_artifacts: + - name: implementation-plan + path: implementation-plan.md + type: markdown + - name: progress-checklist + path: implementation-checklist.md + type: markdown + - name: contract_data + path: .wave/artifact.json + type: json + + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/implement-phase.schema.json + must_pass: true + on_failure: retry + + # Phase 5: PR-Cycle - Automated pull request lifecycle + - id: pr-create + persona: navigator + dependencies: [implement] + + memory: + inject_artifacts: + - step: implement + artifact: implementation-plan + as: implementation-plan.md + + exec: + type: prompt + source: | + You are creating a pull request for the implemented feature. + + Create a comprehensive pull request: + - Clear PR title and description + - Link to related issues + - Include testing instructions + - Add appropriate labels and reviewers + - Request Copilot review + + Use GitHub CLI to create the PR and configure automated review workflow. + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + output_artifacts: + - name: pr-info + path: pr-info.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: pr-info.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + + - id: pr-review + persona: auditor + model: claude-haiku + dependencies: [pr-create] + + exec: + type: prompt + source: | + Monitor and manage the PR review process. + + Poll for Copilot review completion and analyze feedback. + Prepare response strategy for review comments. + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + - id: pr-respond + persona: philosopher + dependencies: [pr-review] + + exec: + type: prompt + source: | + Analyze review comments and prepare thoughtful responses. + + Generate responses to review feedback that: + - Address technical concerns professionally + - Explain design decisions clearly + - Propose solutions for identified issues + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + - id: pr-fix + persona: craftsman + dependencies: [pr-respond] + + exec: + type: prompt + source: | + Implement small fixes based on review feedback. + + For larger changes, create follow-up issues instead of expanding this PR. + Focus on quick, low-risk improvements that address reviewer concerns. + + workspace: + mount: + - source: . + target: /project + mode: readwrite + + - id: pr-merge + persona: navigator + dependencies: [pr-fix] + + exec: + type: prompt + source: | + Complete the PR lifecycle with merge. + + Verify all checks pass, reviews are approved, and merge the PR. + Clean up branch and notify stakeholders of completion. + + workspace: + mount: + - source: . + target: /project + mode: readwrite diff --git a/.wave/pipelines/impl-recinq.yaml b/.wave/pipelines/impl-recinq.yaml new file mode 100644 index 0000000..61bc6d3 --- /dev/null +++ b/.wave/pipelines/impl-recinq.yaml @@ -0,0 +1,560 @@ +kind: WavePipeline +metadata: + name: impl-recinq + description: "Rethink and simplify code using divergent-convergent thinking (Double Diamond)" + release: true + +skills: + - "{{ project.skill }}" + - software-design + +input: + source: cli + example: "internal/pipeline" + +# Pipeline structure implements the Double Diamond: +# +# gather → diverge → converge → probe → distill → simplify → report +# ╰─ Diamond 1 ─╯ ╰─ Diamond 2 ─╯ ╰implement╯ +# (discover) (define) (develop) (deliver) +# +# Each step gets its own context window and cognitive mode. +# Fresh memory at every boundary — no mode-switching within a step. + +steps: + - id: gather + persona: "gitea-analyst" + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + CONTEXT GATHERING — parse input and fetch GitHub context if applicable. + + Input: {{ input }} + + ## Instructions + + Determine what kind of input this is: + + 1. **GitHub Issue URL**: Contains `github.com` and `/issues/` + - Extract owner/repo and issue number from the URL + - Run: `{{ forge.cli_tool }} issue view <number> --repo <owner/repo> --json title,body,labels` + - Extract a `focus_hint` summarizing what should be simplified + + 2. **GitHub PR URL**: Contains `github.com` and `/pull/` + - Extract owner/repo and PR number from the URL + - Run: `{{ forge.cli_tool }} {{ forge.pr_command }} view <number> --repo <owner/repo> --json title,body,labels,files` + - Extract a `focus_hint` summarizing what the PR is about + + 3. **Local path or description**: Anything else + - Set `input_type` to `"local"` + - Pass through the original input as-is + + ## Output + + IMPORTANT: The output MUST be valid JSON. Do NOT include markdown fencing. + output_artifacts: + - name: context + path: .wave/output/context.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/context.json + schema_path: .wave/contracts/recinq-context.schema.json + must_pass: true + on_failure: retry + + # ── Diamond 1: Discover (DIVERGENT) ────────────────────────────────── + - id: diverge + persona: provocateur + model: claude-haiku + dependencies: [gather] + memory: + inject_artifacts: + - step: gather + artifact: context + as: context + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + DIVERGENT THINKING — cast the widest net to find simplification opportunities. + + Target: {{ input }} + + ## Starting Point + + The context artifact contains input context. + If `input_type` is `"issue"` or `"pr"`, the `focus_hint` tells you WHERE to start looking — + but do NOT limit yourself to what the issue describes. Use it as a seed, then expand outward. + Follow dependency chains, trace callers, explore adjacent modules. The issue author doesn't + know what they don't know — that's YOUR job. + If `input_type` is `"local"`, use the `original_input` field as the target path. + + If input is empty or "." — analyze the whole project. + If input is a path — focus on that module/directory but consider its connections. + + ## Your Mission + + Challenge EVERYTHING. Question every assumption. Hunt complexity. + + ## What to Look For + + 1. **Premature abstractions**: Interfaces with one implementation. Generic code used once. + "What if we just inlined this?" + + 2. **Unnecessary indirection**: Layers that pass-through without adding value. + Wrappers around wrappers. "How many hops to get to the actual logic?" + + 3. **Overengineering**: Configuration for things that never change. Plugins with one plugin. + Feature flags for features that are always on. "Is this complexity earning its keep?" + + 4. **YAGNI violations**: Code written for hypothetical future needs that never arrived. + "When was this last changed? Does anyone actually use this path?" + + 5. **Accidental complexity**: Things that are hard because of how they're built, not because + the problem is hard. "Could this be 10x simpler if we started over?" + + 6. **Copy-paste drift**: Similar-but-slightly-different code that should be unified or + intentionally differentiated. "Are these differences meaningful or accidental?" + + 7. **Dead weight**: Unused exports, unreachable code, obsolete comments, stale TODOs. + `grep -r` for references. If nothing uses it, flag it. + + 8. **Naming lies**: Names that don't match what the code actually does. + "Does this 'manager' actually manage anything?" + + 9. **Dependency gravity**: Modules that pull in everything. Import graphs that are too dense. + "What's the blast radius of changing this?" + + ## Evidence Requirements + + For EVERY finding, gather concrete metrics: + - `wc -l` for line counts + - `grep -r` for usage/reference counts + - `git log --oneline <file> | wc -l` for change frequency + - List the actual files involved + + ## Output + + Each finding gets a unique ID: DVG-001, DVG-002, etc. + + Be AGGRESSIVE — flag everything suspicious. The convergent phase will filter. + It's better to have 30 findings with 10 false positives than 5 findings that miss + the big opportunities. + + Include a metrics_summary with totals by category and severity, plus hotspot files + that appear in multiple findings. + output_artifacts: + - name: findings + path: .wave/output/divergent-findings.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/divergent-findings.json + schema_path: .wave/contracts/divergent-findings.schema.json + must_pass: true + on_failure: retry + + # ── Diamond 1: Define (CONVERGENT) ─────────────────────────────────── + - id: converge + persona: validator + dependencies: [diverge] + memory: + inject_artifacts: + - step: diverge + artifact: findings + as: divergent_findings + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + CONVERGENT VALIDATION — separate signal from noise. + + This is a purely CONVERGENT step. Your job is analytical, not creative. + Judge every finding on technical merit alone. No speculation, no new ideas. + + Target: {{ input }} + + ## For Every DVG-xxx Finding + + 1. **Read the actual code** cited as evidence — don't trust the provocateur's summary + 2. **Verify the metrics** — check reference counts, line counts, change frequency + 3. **Assess**: Is this a real problem or a false positive? + - Does the "premature abstraction" actually have a second implementation planned? + - Is the "dead weight" actually used via reflection or codegen? + - Is the "unnecessary indirection" actually providing error handling or logging? + 4. **Classify**: + - `CONFIRMED` — real problem, metrics check out, code supports the claim + - `PARTIALLY_CONFIRMED` — real concern but overstated, or scope is narrower than claimed + - `REJECTED` — false positive, justified complexity, or incorrect metrics + 5. **Explain**: For every classification, write WHY. For rejections, explain what + the provocateur got wrong. + + Be RIGOROUS. The provocateur was told to be aggressive — your job is to be skeptical. + A finding that survives your scrutiny is genuinely worth addressing. + output_artifacts: + - name: validated_findings + path: .wave/output/validated-findings.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/validated-findings.json + schema_path: .wave/contracts/validated-findings.schema.json + must_pass: true + on_failure: retry + + # ── Diamond 2: Develop (DIVERGENT) ─────────────────────────────────── + - id: probe + persona: provocateur + dependencies: [converge] + memory: + inject_artifacts: + - step: diverge + artifact: findings + as: divergent_findings + - step: converge + artifact: validated_findings + as: validated_findings + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + SECOND DIVERGENCE — probe deeper into confirmed findings. + + The first pass cast a wide net. The validator filtered it down. + Now YOU go deeper on what survived. This is DIVERGENT thinking again — + expand, connect, discover what the first pass missed. + + Focus on findings with status CONFIRMED or PARTIALLY_CONFIRMED. + + Target: {{ input }} + + ## Your Mission + + For each confirmed finding, probe OUTWARD: + + 1. **Trace the dependency graph**: What calls this code? What does it call? + If we simplify X, what happens to its callers and callees? + + 2. **Find second-order effects**: If we remove abstraction A, does layer B + also become unnecessary? Do test helpers simplify? Do error paths collapse? + + 3. **Spot patterns across findings**: Do three findings all stem from the same + over-abstraction? Is there a root cause that would address multiple DVGs at once? + + 4. **Discover what was MISSED**: With the validated findings as context, look for + related opportunities the first pass didn't see. The confirmed findings reveal + the codebase's real pressure points — what else lurks nearby? + + 5. **Challenge the rejections**: Were any findings rejected too hastily? + Read the validator's rationale — do you disagree? + + ## Evidence Requirements + + Same standard as the first diverge pass: + - `wc -l` for line counts + - `grep -r` for usage/reference counts + - `git log --oneline <file> | wc -l` for change frequency + - Concrete file paths and code references + + ## Output + + Go DEEP. The first pass was wide, this pass is deep. Follow every thread. + output_artifacts: + - name: probed_findings + path: .wave/output/probed-findings.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/probed-findings.json + schema_path: .wave/contracts/probed-findings.schema.json + must_pass: true + on_failure: retry + + # ── Diamond 2: Deliver (CONVERGENT) ────────────────────────────────── + - id: distill + persona: synthesizer + dependencies: [probe] + memory: + inject_artifacts: + - step: gather + artifact: context + as: context + - step: converge + artifact: validated_findings + as: validated_findings + - step: probe + artifact: probed_findings + as: probed_findings + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + FINAL CONVERGENCE — write a JSON object to `.wave/output/convergent-proposals.json`. + + Target: {{ input }} + + Read ALL injected artifacts first: + - `.wave/artifacts/context` — issue/PR context from the gather step + - `.wave/artifacts/validated_findings` — findings that survived scrutiny + - `.wave/artifacts/probed_findings` — deeper connections, patterns, new discoveries + + Then write a SINGLE JSON object (no markdown, no prose, no code fences) to + the output file using the Write tool. The file must start with `{` and end with `}`. + + ## How to populate each field + + **`source_findings`**: Count how many findings you reviewed, confirmed, partially + confirmed, or rejected. Include rejection reasons. + + **`validation_summary`**: One paragraph describing the converge→diverge→converge + validation process and what survived. + + **`proposals`** array — for each proposal: + - `id`: SMP-001, SMP-002, etc. + - Group findings that share a root cause into a single proposal + - Incorporate second-order effects from the probe step into `impact` estimates + - Include DVG-NEW-xxx discoveries from the probe step (pre-validated) + - If context shows `input_type` is `"issue"` or `"pr"`, use `focus_hint` as ONE + input when assigning `tier`, but do not discard strong proposals outside scope + - `tier`: 1=do now, 2=do next, 3=consider later + - `files`: list actual file paths affected + - `dependencies`: SMP-xxx IDs that must be applied first + + **`eighty_twenty_analysis`**: Which 20% of proposals yield 80% of the benefit? + + **`timestamp`**: ISO 8601 datetime. + + IMPORTANT: The Write tool call must contain ONLY the JSON object. + Contract validation will reject non-JSON output. + output_artifacts: + - name: proposals + path: .wave/output/convergent-proposals.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/convergent-proposals.json + schema_path: .wave/contracts/convergent-proposals.schema.json + must_pass: true + on_failure: retry + + # ── Implementation ─────────────────────────────────────────────────── + - id: simplify + persona: craftsman + dependencies: [distill] + memory: + inject_artifacts: + - step: converge + artifact: validated_findings + as: validated_findings + - step: distill + artifact: proposals + as: proposals + workspace: + type: worktree + branch: "refactor/{{ pipeline_id }}" + exec: + type: prompt + source: | + IMPLEMENTATION — apply the best simplification proposals. + + ## Process + + Apply ONLY tier-1 proposals, in dependency order. + + For each proposal (SMP-xxx): + + 1. **Announce**: Print which proposal you're applying and what it does + 2. **Apply**: Make the code changes + 3. **Build**: Run the project's build command — must succeed + 4. **Test**: Run the project's test suite — must pass + 5. **Commit**: If build and tests pass: + ```bash + git add <specific-files> + git commit -m "refactor: <proposal title> + + Applies SMP-xxx: <brief description> + Source findings: <DVG-xxx list>" + ``` + 6. **Revert if failing**: If tests fail after applying, revert: + ```bash + git checkout -- . + ``` + Log the failure and move to the next proposal. + + ## Final Verification + + After all tier-1 proposals are applied (or attempted): + 1. Run the full test suite + 2. Run the project's build command + 3. Summarize what was applied, what was skipped, and net lines changed + + ## Important + + - Each proposal gets its own atomic commit + - Never combine proposals in a single commit + - If a proposal depends on a failed proposal, skip it too + - Commit each proposal as a separate atomic commit + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + compaction: + trigger: "token_limit_80%" + persona: summarizer + output_artifacts: + - name: result + path: .wave/output/result.md + type: markdown + + # ── Reporting ──────────────────────────────────────────────────────── + - id: report + persona: navigator + dependencies: [simplify] + memory: + inject_artifacts: + - step: distill + artifact: proposals + as: proposals + - step: simplify + artifact: result + as: result + workspace: + type: worktree + branch: "refactor/{{ pipeline_id }}" + exec: + type: prompt + source: | + REPORT — compose a summary of what recinq found and applied. + + Run `git log --oneline` to see the commits on this branch. + + ## Compose the Report + + Write a markdown report containing: + - **Summary**: One-paragraph overview of what recinq found and applied + - **Proposals**: List of all proposals with their tier, impact, and status (applied/skipped/failed) + - **Changes Applied**: Summary of commits made, files changed, net lines removed + - **Remaining Opportunities**: Tier-2 and tier-3 proposals for future consideration + output_artifacts: + - name: report + path: .wave/output/report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/report.md + + # ── Publish ───────────────────────────────────────────────────────── + - id: publish + persona: craftsman + dependencies: [report, gather] + memory: + inject_artifacts: + - step: gather + artifact: context + as: context + - step: report + artifact: report + as: report + workspace: + type: worktree + branch: "refactor/{{ pipeline_id }}" + exec: + type: prompt + source: | + PUBLISH — push the branch and create a pull request. + + ## Steps + + 1. Push the branch: + ```bash + git push -u origin HEAD + ``` + + 2. Create a pull request using the report as the body: + ```bash + COMMIT_SUBJECT=$(git log --format=%s -1) + {{ forge.cli_tool }} {{ forge.pr_command }} create --title "refactor: $COMMIT_SUBJECT" --body-file .wave/artifacts/report + ``` + + 3. If the context artifact shows `input_type` is `"issue"` or `"pr"`, + post the PR URL as a comment on the source: + ```bash + echo "Refactoring PR: <pr-url>" > /tmp/recinq-comment.md + {{ forge.cli_tool }} issue comment <number> --repo <repo> --body-file /tmp/recinq-comment.md + ``` + or for PRs: + ```bash + echo "Refactoring PR: <pr-url>" > /tmp/recinq-comment.md + {{ forge.cli_tool }} {{ forge.pr_command }} comment <number> --repo <repo> --body-file /tmp/recinq-comment.md + ``` + + 4. Write the JSON status report to the output artifact path. + + If any `{{ forge.cli_tool }}` command fails, log the error and continue. + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/impl-refactor.yaml b/.wave/pipelines/impl-refactor.yaml new file mode 100644 index 0000000..f4ca522 --- /dev/null +++ b/.wave/pipelines/impl-refactor.yaml @@ -0,0 +1,150 @@ +kind: WavePipeline +metadata: + name: impl-refactor + description: "Safe refactoring with comprehensive test coverage" + release: true + +skills: + - "{{ project.skill }}" + - software-design + +input: + source: cli + example: "extract workspace manager from executor into its own package" + +steps: + - id: analyze + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze refactoring scope for: {{ input }} + + 1. Identify all code that will be affected + 2. Map all callers/consumers of the code being refactored + 3. Find existing test coverage + 4. Identify integration points + output_artifacts: + - name: analysis + path: .wave/output/refactor-analysis.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/refactor-analysis.json + schema_path: .wave/contracts/refactor-analysis.schema.json + on_failure: retry + + - id: test-baseline + persona: craftsman + dependencies: [analyze] + memory: + inject_artifacts: + - step: analyze + artifact: analysis + as: scope + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Before refactoring, ensure test coverage: + + 1. Run existing tests and record baseline + 2. Add characterization tests for uncovered code paths + 3. Add integration tests for affected callers + 4. Document current behavior for comparison + + All tests must pass before proceeding. + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + + must_pass: true + on_failure: retry + output_artifacts: + - name: baseline + path: .wave/output/test-baseline.md + type: markdown + - id: refactor + persona: craftsman + dependencies: [test-baseline] + thread: refactor + memory: + inject_artifacts: + - step: analyze + artifact: analysis + as: scope + - step: test-baseline + artifact: baseline + as: tests + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Perform the refactoring: {{ input }} + + Guidelines: + 1. Make atomic, reviewable changes + 2. Preserve all existing behavior + 3. Run tests after each significant change + 4. Update affected callers as needed + 5. Keep commits small and focused + + Do NOT change behavior — this is refactoring only. + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: false + on_failure: retry + compaction: + trigger: "token_limit_80%" + persona: summarizer + + - id: verify + persona: reviewer + dependencies: [refactor] + exec: + type: prompt + source: | + Verify the refactoring: + + 1. Compare before/after behavior — any changes? + 2. Check test coverage didn't decrease + 3. Verify all callers still work correctly + 4. Look for missed edge cases + 5. Assess code quality improvement + + Output: PASS (safe to merge) or FAIL (issues found) + output_artifacts: + - name: verification + path: .wave/output/verification.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/verification.md diff --git a/.wave/pipelines/impl-research.yaml b/.wave/pipelines/impl-research.yaml new file mode 100644 index 0000000..63e8ae2 --- /dev/null +++ b/.wave/pipelines/impl-research.yaml @@ -0,0 +1,28 @@ +kind: WavePipeline +metadata: + name: impl-research + description: "Research a GitHub issue, implement the solution, then review the PR" + category: composition + release: true + +input: + source: cli + example: "re-cinq/wave 42" + schema: + type: string + description: "GitHub issue reference (owner/repo number)" + +steps: + - id: research + pipeline: plan-research + input: "{{input}}" + + - id: implement + dependencies: [research] + pipeline: impl-speckit + input: "{{input}}" + + - id: review + dependencies: [implement] + pipeline: ops-pr-review + input: "{{input}}" diff --git a/.wave/pipelines/impl-speckit.yaml b/.wave/pipelines/impl-speckit.yaml new file mode 100644 index 0000000..9db5f85 --- /dev/null +++ b/.wave/pipelines/impl-speckit.yaml @@ -0,0 +1,256 @@ +kind: WavePipeline +metadata: + name: impl-speckit + description: "Specification-driven implementation: specify → clarify → plan → tasks → implement → PR" + release: true + +requires: + skills: + speckit: + check: specify check + install: uv tool install --force specify-cli --from git+https://github.com/github/spec-kit.git + init: specify init + tools: + - git + - gh + +input: + source: cli + example: "add user authentication with JWT tokens" + schema: + type: string + description: "Natural language feature description to specify and implement" + +steps: + - id: specify + persona: implementer + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + base: main + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/specify.md + output_artifacts: + - name: spec-status + path: .wave/output/specify-status.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/specify-status.json + schema_path: .wave/contracts/specify-status.schema.json + must_pass: true + on_failure: retry + + - id: clarify + persona: implementer + model: claude-haiku + dependencies: [specify] + memory: + inject_artifacts: + - step: specify + artifact: spec-status + as: spec_info + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/clarify.md + output_artifacts: + - name: clarify-status + path: .wave/output/clarify-status.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/clarify-status.json + schema_path: .wave/contracts/clarify-status.schema.json + must_pass: true + on_failure: retry + + - id: plan + persona: implementer + dependencies: [clarify] + memory: + inject_artifacts: + - step: specify + artifact: spec-status + as: spec_info + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/plan.md + output_artifacts: + - name: plan-status + path: .wave/output/plan-status.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/plan-status.json + schema_path: .wave/contracts/plan-status.schema.json + must_pass: true + on_failure: retry + + - id: tasks + persona: implementer + dependencies: [plan] + memory: + inject_artifacts: + - step: specify + artifact: spec-status + as: spec_info + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/tasks.md + output_artifacts: + - name: tasks-status + path: .wave/output/tasks-status.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/tasks-status.json + schema_path: .wave/contracts/tasks-status.schema.json + must_pass: true + on_failure: retry + + - id: checklist + persona: implementer + dependencies: [tasks] + memory: + inject_artifacts: + - step: specify + artifact: spec-status + as: spec_info + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/checklist.md + output_artifacts: + - name: checklist-status + path: .wave/output/checklist-status.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/checklist-status.json + schema_path: .wave/contracts/checklist-status.schema.json + must_pass: true + on_failure: retry + + - id: analyze + persona: implementer + model: claude-haiku + dependencies: [checklist] + memory: + inject_artifacts: + - step: specify + artifact: spec-status + as: spec_info + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/analyze.md + output_artifacts: + - name: analysis-report + path: .wave/output/analysis-report.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/analysis-report.json + schema_path: .wave/contracts/analysis-report.schema.json + must_pass: true + on_failure: retry + + - id: implement + persona: craftsman + dependencies: [analyze] + memory: + inject_artifacts: + - step: specify + artifact: spec-status + as: spec_info + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/implement.md + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + compaction: + trigger: "token_limit_80%" + persona: summarizer + + - id: create-pr + persona: craftsman + dependencies: [implement] + memory: + inject_artifacts: + - step: specify + artifact: spec-status + as: spec_info + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source_path: .wave/prompts/speckit-flow/create-pr.md + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/onboard.yaml b/.wave/pipelines/onboard.yaml new file mode 100644 index 0000000..6dac88f --- /dev/null +++ b/.wave/pipelines/onboard.yaml @@ -0,0 +1,123 @@ +kind: WavePipeline +metadata: + name: onboard + description: "Generate a project onboarding guide for new contributors" + release: true + +input: + source: cli + example: "create an onboarding guide for this project" + +steps: + - id: survey + persona: navigator + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Survey this project to build an onboarding guide: {{ input }} + + ## Survey Checklist + + 1. **Project identity**: Find README, package manifests (go.mod, package.json), + license, and config files. Determine language, framework, purpose. + + 2. **Build system**: How to build, test, and run the project. + Find Makefiles, scripts, CI configs, Dockerfiles. + + 3. **Directory structure**: Map the top-level layout and key directories. + What does each directory contain? + + 4. **Architecture**: Identify the main components and how they interact. + Find entry points (main.go, index.ts, etc.). + + 5. **Dependencies**: List key dependencies and their purposes. + Check go.mod, package.json, requirements.txt, etc. + + 6. **Configuration**: Find environment variables, config files, feature flags. + + 7. **Testing**: Where are tests? How to run them? What patterns are used? + + 8. **Development workflow**: Find contributing guides, PR templates, + commit conventions, branch strategies. + + 9. **Documentation**: Where is documentation? Is it up to date? + output_artifacts: + - name: survey + path: .wave/output/project-survey.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/project-survey.json + schema_path: .wave/contracts/project-survey.schema.json + on_failure: retry + max_retries: 2 + + - id: guide + persona: philosopher + dependencies: [survey] + memory: + inject_artifacts: + - step: survey + artifact: survey + as: project_info + exec: + type: prompt + source: | + Write a comprehensive onboarding guide for new contributors. + + Using the injected project survey data, write a guide with these sections: + + # Onboarding Guide: [Project Name] + + ## Quick Start + - Prerequisites (what to install) + - Clone and build (exact commands) + - Run tests (exact commands) + - Run the project (exact commands) + + ## Project Overview + - What this project does (2-3 sentences) + - Key technologies and why they were chosen + - High-level architecture (ASCII diagram) + + ## Directory Map + - What each top-level directory contains + - Where to find things (tests, configs, docs) + + ## Core Concepts + - Key abstractions and terminology + - How the main components interact + - Data flow through the system + + ## Development Workflow + - How to create a feature branch + - Commit message conventions + - How to run tests before pushing + - PR process + + ## Common Tasks + - "I want to add a new [feature/command/endpoint]" → where to start + - "I want to fix a bug" → debugging approach + - "I want to understand [component]" → where to look + + ## Helpful Resources + - Documentation locations + - Key files to read first + - Related external docs + + Write for someone on their first day with this codebase. + Be specific — use real paths, real commands, real examples. + output_artifacts: + - name: guide + path: .wave/output/onboarding-guide.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/onboarding-guide.md diff --git a/.wave/pipelines/ops-bootstrap.yaml b/.wave/pipelines/ops-bootstrap.yaml new file mode 100644 index 0000000..662dd47 --- /dev/null +++ b/.wave/pipelines/ops-bootstrap.yaml @@ -0,0 +1,212 @@ +kind: WavePipeline +metadata: + name: ops-bootstrap + description: "Scaffold a greenfield project with language-appropriate structure, CI config, and initial files" + release: true + +input: + source: cli + schema: + type: string + description: "Project description and intent (e.g. 'Rust CLI tool for processing CSV files')" + example: "Rust CLI tool for processing CSV files" + +steps: + - id: assess + persona: navigator + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + You are assessing a greenfield project for scaffolding. + + The user described this project as: {{ input }} + + ## Instructions + + 1. Read `wave.yaml` to find `project.language`, `project.build_command`, and `project.test_command` + 2. List all existing files in the project directory to understand what already exists + 3. Read any README, ADR, or design docs for additional project intent + 4. Determine the project flavour (language/framework): + - If `project.language` is set in wave.yaml, use that + - Otherwise infer from existing files (package.json → node, Cargo.toml → rust, go.mod → go, etc.) + - If nothing exists, infer from the user's description + 5. Recommend the appropriate project scaffold + + ## Output + + Write a JSON file to `.wave/output/bootstrap-assessment.json` with this structure: + ```json + { + "flavour": "go|rust|node|bun|python|csharp|...", + "project_intent": "description of what the project does", + "existing_files": ["list", "of", "existing", "files"], + "scaffold_recommendations": { + "files_to_create": ["list of files to scaffold"], + "build_system": "cargo|go|npm|bun|pip|dotnet", + "ci_provider": "github-actions", + "gitignore_patterns": ["patterns for .gitignore"] + }, + "wave_config": { + "language": "from wave.yaml if set", + "build_command": "from wave.yaml if set", + "test_command": "from wave.yaml if set" + } + } + ``` + output_artifacts: + - name: assessment + path: .wave/output/bootstrap-assessment.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/bootstrap-assessment.json + schema_path: .wave/contracts/bootstrap-assessment.schema.json + must_pass: true + on_failure: retry + + - id: scaffold + persona: craftsman + dependencies: [assess] + memory: + inject_artifacts: + - step: assess + artifact: assessment + as: bootstrap_assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + You are scaffolding a new project based on the assessment. + + Read the assessment artifact to understand the project flavour, intent, and recommendations. + + ## Instructions + + Based on the detected flavour, create the appropriate project structure: + + ### Go + - `go.mod` with appropriate module path + - `main.go` or `cmd/<name>/main.go` for CLI tools + - `internal/` directory structure + - Basic test file + - `.github/workflows/ci.yml` with go build and test + + ### Rust + - `Cargo.toml` with project metadata + - `src/main.rs` (binary) or `src/lib.rs` (library) + - `tests/` directory with integration test stub + - `.github/workflows/ci.yml` with cargo build and test + + ### Node / Bun + - `package.json` with project metadata and scripts + - `src/index.ts` entry point + - `tsconfig.json` with strict mode + - `.github/workflows/ci.yml` with install and test + + ### Python + - `pyproject.toml` with project metadata + - `src/<package_name>/__init__.py` + - `tests/test_placeholder.py` + - `.github/workflows/ci.yml` with pip install and pytest + + ### C# + - `<ProjectName>.sln` solution file + - `src/<ProjectName>/<ProjectName>.csproj` and `Program.cs` + - `tests/<ProjectName>.Tests/` with test project + - `.github/workflows/ci.yml` with dotnet build and test + + ### For ALL flavours + - Create `.gitignore` appropriate for the language + - Create `README.md` with project description, build instructions, and usage + + ## Verification + + After creating all files: + 1. If a build command is available, run it to verify the project compiles + 2. If a test command is available, run it to verify tests pass + + ## Important + + - Do NOT create files that already exist (check the assessment's existing_files list) + - Use the project intent from the assessment to make README content meaningful + - Follow standard conventions for the language ecosystem + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: false + on_failure: retry + + - id: commit + persona: craftsman + dependencies: [scaffold] + workspace: + type: worktree + branch: "{{ pipeline_id }}" + memory: + inject_artifacts: + - step: assess + artifact: assessment + as: bootstrap_assessment + exec: + type: prompt + source: | + You are creating the initial commit for a newly scaffolded project. + + Read the assessment artifact to get the project flavour and the list of + recommended files from `scaffold_recommendations.files_to_create`. + + ## Instructions + + 1. Identify which files were actually created by the scaffold step: + ```bash + git status --porcelain + ``` + + 2. Stage ONLY the project files explicitly — NEVER use `git add -A` or `git add .`: + ```bash + git add <file1> <file2> <file3> ... + ``` + Stage every new or modified project file shown by `git status`, but + NEVER stage any of these paths: + - `.wave/artifacts/` + - `.wave/output/` + - `.claude/` + - `CLAUDE.md` + + 3. Create the initial commit with a conventional commit message: + ```bash + git commit -m "feat: scaffold <flavour> project" + ``` + Replace `<flavour>` with the actual detected flavour from the assessment (e.g. "go", "rust", "node"). + + 4. Check if a remote is configured: + ```bash + git remote -v + ``` + + 5. If a remote exists, push the branch: + ```bash + git push -u origin HEAD + ``` + + ## Important + + - Do NOT include Co-Authored-By or AI attribution in the commit message + - NEVER use `git add -A`, `git add .`, or `git add --all` — always stage files by explicit path + - Do NOT commit .wave/artifacts/, .wave/output/, .claude/, or CLAUDE.md + - If git push fails (no remote, auth issues), that's OK — just report the commit was created locally diff --git a/.wave/pipelines/ops-debug.yaml b/.wave/pipelines/ops-debug.yaml new file mode 100644 index 0000000..a23c636 --- /dev/null +++ b/.wave/pipelines/ops-debug.yaml @@ -0,0 +1,163 @@ +kind: WavePipeline +metadata: + name: ops-debug + description: "Systematic debugging with hypothesis testing" + release: true + +skills: + - "{{ project.skill }}" + +input: + source: cli + example: "TestPipelineExecutor fails with nil pointer on resume" + +steps: + - id: reproduce + persona: debugger + model: claude-haiku + thread: debug + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Reproduce and characterize the issue: {{ input }} + + 1. Understand expected vs actual behavior + 2. Create minimal reproduction steps + 3. Identify relevant code paths + 4. Note environmental factors (OS, versions, config) + output_artifacts: + - name: reproduction + path: .wave/output/reproduction.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/reproduction.json + schema_path: .wave/contracts/debug-reproduction.schema.json + on_failure: retry + + - id: hypothesize + persona: debugger + thread: debug + dependencies: [reproduce] + memory: + inject_artifacts: + - step: reproduce + artifact: reproduction + as: issue + exec: + type: prompt + source: | + Form hypotheses about the root cause. + + For each hypothesis: + 1. What could cause this behavior? + 2. What evidence would confirm/refute it? + 3. How to test this hypothesis? + + Rank by likelihood and ease of testing. + output_artifacts: + - name: hypotheses + path: .wave/output/hypotheses.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/hypotheses.json + schema_path: .wave/contracts/debug-hypotheses.schema.json + on_failure: retry + + - id: investigate + persona: debugger + thread: debug + dependencies: [hypothesize] + memory: + inject_artifacts: + - step: reproduce + artifact: reproduction + as: issue + - step: hypothesize + artifact: hypotheses + as: hypotheses + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Test each hypothesis systematically. + + 1. Start with most likely / easiest to test + 2. Use git bisect if needed to find regression + 3. Add diagnostic logging to trace execution + 4. Examine data flow and state changes + 5. Document findings for each hypothesis + + Continue until root cause is identified. + output_artifacts: + - name: findings + path: .wave/output/investigation.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/investigation.md + + - id: fix + persona: craftsman + dependencies: [investigate] + thread: debug + max_visits: 3 + memory: + inject_artifacts: + - step: investigate + artifact: findings + as: root_cause + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Fix the root cause identified in the investigation. + + 1. Implement the minimal fix + 2. Add a regression test that would have caught this + 3. Remove any diagnostic code added during debugging + 4. Verify the original reproduction no longer fails + 5. Check for similar issues elsewhere + retry: + policy: aggressive + max_attempts: 3 + output_artifacts: + - name: fix + path: .wave/output/fix-summary.md + type: markdown + + - id: run-tests + type: command + dependencies: [fix] + script: "{{ project.contract_test_command }}" + + - id: verify-fix + type: conditional + dependencies: [run-tests] + edges: + - target: _complete + condition: "outcome=success" + - target: fix diff --git a/.wave/pipelines/ops-epic-runner.yaml b/.wave/pipelines/ops-epic-runner.yaml new file mode 100644 index 0000000..8d5a4c6 --- /dev/null +++ b/.wave/pipelines/ops-epic-runner.yaml @@ -0,0 +1,26 @@ +kind: WavePipeline +metadata: + name: ops-epic-runner + description: "Scope an epic, implement each child issue sequentially" + category: composition + release: true + +input: + source: cli + example: "re-cinq/wave 42" + schema: + type: string + description: "GitHub epic reference (owner/repo number)" + +steps: + - id: scope + pipeline: plan-scope + input: "{{input}}" + + - id: implement-all + dependencies: [scope] + pipeline: impl-speckit + input: "{{item.url}} — child of {{input}}, see parent for full context" + iterate: + over: "{{scope.output.child_issues}}" + mode: sequential diff --git a/.wave/pipelines/ops-hello-world.yaml b/.wave/pipelines/ops-hello-world.yaml new file mode 100644 index 0000000..53c4e89 --- /dev/null +++ b/.wave/pipelines/ops-hello-world.yaml @@ -0,0 +1,54 @@ +kind: WavePipeline +metadata: + name: ops-hello-world + description: "Simple test pipeline to verify Wave is working" + release: true + +input: + source: cli + example: "testing Wave" + +steps: + - id: greet + persona: craftsman + exec: + type: prompt + source: | + You are a simple greeting bot. The user said: "{{ input }}" + + Your final response must be ONLY this text (nothing else - no explanation, no markdown): + + Hello from Wave! Your message was: {{ input }} + output_artifacts: + - name: greeting + path: greeting.txt + type: text + + - id: verify + persona: navigator + dependencies: [greet] + memory: + inject_artifacts: + - step: greet + artifact: greeting + as: greeting_file + exec: + type: prompt + source: | + Verify the greeting artifact exists and contains content. + + Output a JSON result confirming verification status. + output_artifacts: + - name: result + path: .wave/output/result.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/result.json + schema_path: .wave/contracts/hello-world-result.schema.json + must_pass: true + on_failure: retry diff --git a/.wave/pipelines/ops-implement-epic.yaml b/.wave/pipelines/ops-implement-epic.yaml new file mode 100644 index 0000000..5c366ff --- /dev/null +++ b/.wave/pipelines/ops-implement-epic.yaml @@ -0,0 +1,146 @@ +# Epic Runner — Composition Primitives Example +# +# Demonstrates: iterate (sequential), gate (ci_pass), forge template variables +# +# Execution flow: +# +# fetch-children ← persona step: fetch parent issue, emit child URLs as JSON +# │ +# implement-children ← iterate (sequential): runs impl-issue for each child URL +# │ +# ci-gate ← gate (ci_pass): block until CI is green across all PRs +# │ +# summarise ← persona step: post a completion comment on the epic + +kind: WavePipeline +metadata: + name: ops-implement-epic + description: "Implement all child issues from a parent epic, gate on CI, then summarise" + category: composition + release: true + +requires: + tools: + - gh + +input: + source: cli + example: "https://github.com/re-cinq/wave/issues/184" + schema: + type: string + description: "Parent epic URL (e.g. https://github.com/owner/repo/issues/N)" + +steps: + # ── Step 1: fetch parent issue, emit child issue URLs ───────────────────── + - id: fetch-children + persona: "gitea-analyst" + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Input: {{ input }} + + You are given a parent epic URL. Your job is to extract all child issue URLs + listed in the epic body. + + 1. Parse the URL to get repo (owner/repo) and issue number. + 2. Fetch the issue: + {{ forge.cli_tool }} issue view <NUMBER> --repo <REPO> \ + --json number,title,body,url + 3. Scan the body for linked child issues. Look for patterns like: + - Checkbox lists: `- [ ] #123` or `- [ ] https://github.com/…/issues/123` + - "Closes #N" / "Part of #N" references + - Bulleted task lists pointing to issue URLs + 4. For each child issue URL found, verify it is open: + {{ forge.cli_tool }} issue view <NUMBER> --repo <REPO> --json state,url + Include only open issues. + 5. Output a JSON object with: + { + "parent_url": "<epic_url>", + "repo": "<owner/repo>", + "child_urls": ["https://…/issues/N", …] + } + + Write this JSON to .wave/output/epic-children.json. + output_artifacts: + - name: children + path: .wave/output/epic-children.json + type: json + required: true + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/epic-children.json + schema_path: .wave/contracts/epic-children.schema.json + must_pass: true + on_failure: retry + + # ── Step 2: iterate over child issues, run impl-issue for each ─────────── + - id: implement-children + dependencies: [fetch-children] + pipeline: impl-issue + input: "{{ item }}" + iterate: + over: "{{ fetch-children.output.child_urls }}" + mode: sequential + max_concurrent: 3 + + # ── Step 3: gate — wait for CI to pass across all opened PRs ───────────── + - id: ci-gate + dependencies: [implement-children] + gate: + type: ci_pass + timeout: 2h + message: "Waiting for CI to pass on all PRs opened by child-issue implementations" + + # ── Step 4: post a summary comment on the parent epic ──────────────────── + - id: summarise + persona: "gitea-commenter" + dependencies: [ci-gate] + memory: + inject_artifacts: + - step: fetch-children + artifact: children + as: epic_children + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Input: {{ input }} + + All child issues have been implemented and CI has passed. + + Read .wave/artifacts/epic_children to get the parent epic URL and repo. + + Post a completion comment on the parent epic: + {{ forge.cli_tool }} issue comment <NUMBER> --repo <REPO> --body-file /tmp/summary.md + + The comment should include: + - A completion header: "All child issues implemented — CI green" + - A checkbox list of every child URL that was processed (mark each done) + - A note to reviewers about next steps (merge the PRs in dependency order) + + Write the comment body to /tmp/summary.md before posting. + output_artifacts: + - name: epic-summary + path: .wave/output/epic-summary.json + type: json + outcomes: + - type: url + extract_from: .wave/output/epic-summary.json + json_path: .comment_url + label: "Epic Summary Comment" + retry: + policy: aggressive + max_attempts: 2 diff --git a/.wave/pipelines/ops-issue-quality.yaml b/.wave/pipelines/ops-issue-quality.yaml new file mode 100644 index 0000000..cc0caa9 --- /dev/null +++ b/.wave/pipelines/ops-issue-quality.yaml @@ -0,0 +1,195 @@ +kind: WavePipeline +metadata: + name: ops-issue-quality + description: "Scan open issues for quality and post enhancement suggestions on poor-scoring ones" + release: true + +skills: + - gh-cli + +requires: + tools: + - gh + +input: + source: cli + example: "re-cinq/wave" + schema: + type: string + description: "Repository in owner/repo format" + +steps: + - id: scan + persona: "gitea-analyst" + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Scan all open issues in the repository and produce a quality report. + + Repository: {{ input }} + + Split the input on "/" to get OWNER and REPO. + + ## Fetch Open Issues + + ```bash + {{ forge.cli_tool }} issue list --repo {{ input }} --state open --limit 200 \ + --json number,title,body,labels,url,assignees,milestone,createdAt,updatedAt,comments + ``` + + ## Analyze Each Issue + + For every issue (skip pull requests — items where the URL contains "/pull/"), evaluate: + + 1. **Title quality** + - Too short (< 10 chars): -20 points + - All lowercase (> 5 chars): -5 points + - All uppercase (> 5 chars): -10 points + - Vague terms ("issue", "problem", "help", "bug?", "question") with title < 30 chars: -10 points + - Fewer than 3 words: -15 points + + 2. **Body quality** + - Empty body: -40 points + - Body < 50 chars: -25 points + - Body < 100 chars: -10 points + - Single sentence (< 2 sentence-ending punctuation marks) with body > 20 chars: -10 points + - No structured sections (missing keywords like "steps to reproduce", "expected behavior", "actual behavior", "environment", "version", "screenshot", "reproduction") with body > 100 chars: -5 points + + 3. **Labels** + - No labels: -10 points + + Start every issue at score 100 and apply deductions. Floor at 0. + + ## Output + + Write a JSON file to `.wave/artifacts/quality-report.json` with this structure: + + ```json + { + "repository": { + "owner": "<OWNER>", + "name": "<REPO>" + }, + "total_issues": <count of issues fetched>, + "analyzed_count": <count of issues analyzed (excluding PRs)>, + "poor_quality_issues": [ + { + "number": <issue number>, + "title": "<issue title>", + "body": "<issue body — truncate at 500 chars>", + "quality_score": <0-100>, + "problems": ["<problem description>", ...], + "recommendations": ["<recommendation>", ...], + "labels": ["<label name>", ...], + "url": "<issue HTML URL>" + } + ], + "quality_threshold": 70, + "timestamp": "<ISO 8601 timestamp>" + } + ``` + + Include ALL issues with quality_score < 70 in `poor_quality_issues`, sorted by quality_score ascending (worst first). + output_artifacts: + - name: quality-report + path: .wave/artifacts/quality-report.json + type: json + required: true + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/artifacts/quality-report.json + schema_path: .wave/contracts/github-issue-analysis.schema.json + on_failure: retry + + - id: enhance + persona: "gitea-commenter" + dependencies: [scan] + memory: + inject_artifacts: + - step: scan + artifact: quality-report + as: quality_report + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Post enhancement suggestions as comments on the worst-scoring issues. + + Repository: {{ input }} + + ## Read Quality Report + + Read the injected quality_report artifact. It contains the full quality analysis with per-issue scores, problems, and recommendations. + + ## Select Issues to Enhance + + From `poor_quality_issues`, select issues where `quality_score < 50`. Process at most 10 issues to avoid comment spam. + + If no issues have score < 50, check issues with score < 70 and select the 5 worst. + + If there are no poor quality issues at all, write a summary noting the repository has good issue quality and skip posting. + + ## Post Enhancement Comment + + For each selected issue, compose a helpful comment and post it: + + ```bash + cat > /tmp/issue-enhance-<NUMBER>.md <<'COMMENT_EOF' + ## Issue Quality Suggestions + + This issue has been automatically reviewed for clarity and completeness. + + **Quality Score**: <score>/100 + + **Problems identified**: + <bulleted list of problems> + + **Recommendations**: + <bulleted list of recommendations> + + Consider updating this issue with the suggestions above to help maintainers triage and implement it more effectively. + + --- + *Posted by [Wave](https://github.com/re-cinq/wave) ops-issue-quality pipeline* + COMMENT_EOF + {{ forge.cli_tool }} issue comment <NUMBER> --repo {{ input }} --body-file /tmp/issue-enhance-<NUMBER>.md + ``` + + Clean up temp files after posting each comment. + + ## Write Summary + + After processing all issues, write `.wave/artifacts/enhancement-summary.md` with: + + 1. **Repository**: owner/repo + 2. **Issues Scanned**: total count + 3. **Poor Quality Issues**: count below threshold + 4. **Comments Posted**: count and issue numbers + 5. **Skipped**: count of issues not commented on and reason (score >= 50, or batch limit reached) + 6. For each commented issue: number, title, score, and a brief note on the main problems addressed + output_artifacts: + - name: enhancement-summary + path: .wave/artifacts/enhancement-summary.md + type: markdown + required: true + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: non_empty_file + source: .wave/artifacts/enhancement-summary.md diff --git a/.wave/pipelines/ops-parallel-audit.yaml b/.wave/pipelines/ops-parallel-audit.yaml new file mode 100644 index 0000000..ae5b301 --- /dev/null +++ b/.wave/pipelines/ops-parallel-audit.yaml @@ -0,0 +1,101 @@ +# Parallel Multi-Audit — Composition Primitives Example +# +# Demonstrates: iterate (parallel), aggregate (merge_arrays) +# +# Execution flow: +# +# run-audits ← iterate (parallel, max_concurrent: 3): fan out over +# ├─ audit-security │ [security, dead-code, dx] — each runs its audit +# ├─ audit-dead-code │ pipeline and emits findings JSON +# └─ audit-dx │ +# │ +# merge-findings ← aggregate (merge_arrays): collect all findings arrays +# │ into one unified JSON list +# report ← persona step: synthesise the merged findings into a +# single prioritised markdown report + +kind: WavePipeline +metadata: + name: ops-parallel-audit + description: "Fan out security, dead-code, and DX audits in parallel then merge findings" + category: composition + release: true + +skills: + - software-design + +input: + source: cli + example: "internal/pipeline" + schema: + type: string + description: "Scope to audit — a package path, directory, or free-form description" + +steps: + # ── Step 1: fan out over the three audit types in parallel ──────────────── + # + # Each item is the name of an existing audit pipeline. The iterate primitive + # spawns up to 3 workers simultaneously, one per audit type. + - id: run-audits + pipeline: "{{ item }}" + input: "{{ input }}" + iterate: + over: '["audit-security", "audit-dead-code", "audit-dx"]' + mode: parallel + max_concurrent: 3 + + # ── Step 2: merge all findings arrays into one flat list ───────────────── + # + # The aggregate primitive reads the step outputs collected by run-audits and + # flattens the three JSON arrays into a single findings array written to disk. + - id: merge-findings + dependencies: [run-audits] + aggregate: + from: "{{ run-audits.output }}" + into: .wave/output/merged-findings.json + strategy: merge_arrays + + # ── Step 3: synthesise a unified markdown report ────────────────────────── + - id: report + persona: summarizer + dependencies: [merge-findings] + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Input scope: {{ input }} + + Three parallel audits have completed — security, dead-code, and DX. + Their findings have been merged into .wave/output/merged-findings.json. + + Read that file, then produce a unified markdown report: + + ## Executive Summary + Overall health rating (excellent / good / needs-work / critical) with one + paragraph of justification. + + ## Critical Findings (severity: critical or high) + For each: finding title, audit source, file/line if available, recommended fix. + + ## All Findings by Priority + A table with columns: Priority | Source | Finding | File | Recommendation + + ## Positive Observations + Anything that looked unexpectedly clean or well-maintained. + + ## Recommended Next Steps + Ordered action list — highest impact first. + + Write the report to .wave/output/parallel-audit-report.md. + output_artifacts: + - name: audit-report + path: .wave/output/parallel-audit-report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/parallel-audit-report.md diff --git a/.wave/pipelines/ops-pr-review.yaml b/.wave/pipelines/ops-pr-review.yaml new file mode 100644 index 0000000..8fb6dc5 --- /dev/null +++ b/.wave/pipelines/ops-pr-review.yaml @@ -0,0 +1,242 @@ +kind: WavePipeline +metadata: + name: ops-pr-review + description: "Pull request code review with automated security and quality analysis" + release: true + +chat_context: + artifact_summaries: + - diff + - security + - quality + - verdict + suggested_questions: + - "What issues were found in the review?" + - "Are there any blocking concerns that must be addressed before merging?" + - "What should be fixed before the next review cycle?" + focus_areas: + - "Review findings and severity" + - "Code quality and maintainability" + - "Security concerns and vulnerabilities" + +skills: + - "{{ project.skill }}" + - gh-cli + - software-design + +requires: + tools: + - gh + +input: + source: cli + example: "https://github.com/owner/repo/pull/42" + +steps: + - id: diff-analysis + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze the code changes for: {{ input }} + + ## Step 1: Extract PR Metadata + + First, fetch PR metadata to populate the `pr_metadata` field in the output: + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} view {{ input }} --json number,headRefName,baseRefName,url + ``` + Extract the number, url, headRefName (→ head_branch), and baseRefName (→ base_branch). + + ## Step 2: Checkout the PR Branch + + Checkout the PR's head branch to ensure you analyze the correct code: + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} checkout {{ input }} + ``` + + ## Step 3: Analyze Changes + + 1. Identify all modified files and their purposes + 2. Map the change scope (which modules/packages affected) + 3. Find related tests that should be updated + 4. Check for breaking API changes + + Produce a structured result matching the contract schema. + The `pr_metadata` field must contain the PR number, URL, head branch, and base branch. + output_artifacts: + - name: diff + path: .wave/output/diff-analysis.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/diff-analysis.json + schema_path: .wave/contracts/diff-analysis.schema.json + on_failure: retry + + - id: security-review + persona: reviewer + dependencies: [diff-analysis] + memory: + inject_artifacts: + - step: diff-analysis + artifact: diff + as: changes + exec: + type: prompt + source: | + Security review of the PR changes. + + Check for: + 1. SQL injection, XSS, CSRF vulnerabilities + 2. Hardcoded secrets or credentials + 3. Insecure deserialization + 4. Missing input validation + 5. Authentication/authorization gaps + 6. Sensitive data exposure + + Output findings with severity (CRITICAL/HIGH/MEDIUM/LOW). + output_artifacts: + - name: security + path: .wave/output/security-review.md + type: markdown + handover: + contract: + type: llm_judge + source: .wave/output/security-review.md + model: claude-haiku + criteria: + - "Identifies injection vulnerabilities (SQL, command, XSS) if present in the diff" + - "Checks for hardcoded credentials or secrets" + - "Assesses authentication and authorization correctness" + - "Findings include severity levels and specific file references" + threshold: 0.75 + on_failure: continue + + - id: quality-review + persona: reviewer + dependencies: [diff-analysis] + memory: + inject_artifacts: + - step: diff-analysis + artifact: diff + as: changes + exec: + type: prompt + source: | + Quality review of the PR changes. + + Check for: + 1. Error handling completeness + 2. Edge cases not covered + 3. Code duplication + 4. Naming consistency + 5. Missing or inadequate tests + 6. Performance implications + 7. Documentation gaps + + Output findings with severity and suggestions. + output_artifacts: + - name: quality + path: .wave/output/quality-review.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/quality-review.md + + - id: summary + persona: summarizer + model: claude-haiku + dependencies: [security-review, quality-review] + memory: + inject_artifacts: + - step: security-review + artifact: security + as: security_findings + - step: quality-review + artifact: quality + as: quality_findings + exec: + type: prompt + source: | + Synthesize the review findings into a final verdict. + + Produce a unified review with: + 1. Overall assessment (APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION) + 2. Critical issues that must be fixed + 3. Suggested improvements (optional but recommended) + 4. Positive observations + + Format as a PR review comment ready to post. + Do NOT include a title/header line — the publish step adds one. + output_artifacts: + - name: verdict + path: .wave/output/review-summary.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/review-summary.md + + - id: publish + persona: "gitea-commenter" + dependencies: [summary] + memory: + inject_artifacts: + - step: summary + artifact: verdict + as: review_summary + - step: diff-analysis + artifact: diff + as: pr_context + exec: + type: prompt + source: | + Post the code review summary as a PR comment. + + Read the `pr_context` artifact first — it contains structured PR metadata + with `pr_metadata.number` and `pr_metadata.url`. Use these to identify the + target PR instead of parsing raw input text. + + 1. Read the `pr_context` artifact and extract `pr_metadata.number` for use in commands + 2. Write the review content to a temp file, then post it as a PR comment: + cat > /tmp/pr-review-comment.md <<'REVIEW_EOF' + ## Code Review (Wave Pipeline) + + <review content> + + --- + *Generated by [Wave](https://github.com/re-cinq/wave) pr-review pipeline* + REVIEW_EOF + {{ forge.cli_tool }} {{ forge.pr_command }} comment <PR_NUMBER from pr_metadata> --body-file /tmp/pr-review-comment.md + + output_artifacts: + - name: publish-result + path: .wave/output/publish-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/publish-result.json + schema_path: .wave/contracts/gh-pr-comment-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: url + extract_from: .wave/output/publish-result.json + json_path: .comment_url + label: "Review Comment" diff --git a/.wave/pipelines/ops-refresh.yaml b/.wave/pipelines/ops-refresh.yaml new file mode 100644 index 0000000..483764c --- /dev/null +++ b/.wave/pipelines/ops-refresh.yaml @@ -0,0 +1,186 @@ +kind: WavePipeline +metadata: + name: ops-refresh + description: "Refresh a stale issue by comparing it against recent codebase changes" + release: true + +skills: + - gh-cli + - software-design + +requires: + tools: + - gh + +input: + source: cli + example: "re-cinq/wave 45 -- acceptance criteria are outdated after the worktree refactor" + schema: + type: string + description: "owner/repo number, or full issue URL [-- optional criticism or direction]" + +steps: + - id: gather-context + persona: "gitea-analyst" + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Input: {{ input }} + + Parse the input: + - If the input is a URL (https://github.com/OWNER/REPO/issues/NUM), extract REPO and NUMBER. + - If the input is "owner/repo number", extract REPO (first token) and NUMBER (second token). + - Split on " -- " to separate the identifier from optional criticism. + - If there is text after " -- ", that is the user's CRITICISM about what's wrong with the issue. + + Execute these steps: + + 1. Fetch the full issue: + {{ forge.cli_tool }} issue view NUMBER --repo REPO --json number,title,body,labels,url,createdAt,comments + + 2. Get recent commits since the issue was created (cap at 30): + git log --since="<createdAt>" --oneline -30 + + 3. Get releases since the issue was created: + {{ forge.cli_tool }} release list --repo REPO --limit 20 + Filter to only releases after the issue's createdAt date. + + 4. Scan the issue body for file path references (backtick-quoted paths, relative paths). + For each referenced file, check if it still exists. + + Produce a JSON result matching the contract schema. + output_artifacts: + - name: issue_context + path: .wave/artifact.json + type: json + required: true + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-context.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: draft-update + persona: "gitea-analyst" + dependencies: [gather-context] + memory: + inject_artifacts: + - step: gather-context + artifact: issue_context + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + The context artifact contains the gathered issue context. + + Compare the original issue against the codebase changes and draft an updated version. + + Step 1: Analyze each section of the issue body. Classify each as: + - STILL_VALID: Content is accurate and up-to-date + - OUTDATED: Content references old behavior, removed files, or superseded patterns + - INCOMPLETE: Content is partially correct but missing recent developments + - WRONG: Content is factually incorrect given current codebase state + + Step 2: If there is user criticism (non-empty "criticism" field), address EVERY point raised. + The criticism takes priority — it represents what the issue author thinks is wrong. + + Step 3: Draft the updated issue: + - Preserve sections classified as STILL_VALID (do not rewrite what works) + - Rewrite OUTDATED and WRONG sections to reflect current reality + - Expand INCOMPLETE sections with missing information + - If the title needs updating, draft a new title + - Append a "---\n**Changes since original**" section at the bottom listing what changed and why + + Step 4: If file paths in the issue body are now missing (from referenced_files.missing), + update or remove those references. + + Produce a JSON result matching the contract schema. + output_artifacts: + - name: update_draft + path: .wave/artifact.json + type: json + required: true + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-draft.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: apply-update + persona: "gitea-enhancer" + dependencies: [draft-update] + memory: + inject_artifacts: + - step: draft-update + artifact: update_draft + as: draft + - step: gather-context + artifact: issue_context + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Extract the repo and issue number from the available artifacts. + + Apply the update: + - If title_changed is true: + {{ forge.cli_tool }} issue edit <NUMBER> --repo <REPO> --title '<updated_title>' + - Write the updated_body to a temp file, then apply it: + {{ forge.cli_tool }} issue edit <NUMBER> --repo <REPO> --body-file <temp_file> + - Clean up the temp file after applying. + + Verify the update was applied: + {{ forge.cli_tool }} issue view <NUMBER> --repo <REPO> --json number,title,body,url + + Compare the returned title and body against what was intended. Flag any discrepancies. + + Record the results as a JSON object matching the contract schema. + output_artifacts: + - name: update_result + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .url + label: "Updated Issue" + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/issue-update-result.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/ops-release-harden.yaml b/.wave/pipelines/ops-release-harden.yaml new file mode 100644 index 0000000..afd97d8 --- /dev/null +++ b/.wave/pipelines/ops-release-harden.yaml @@ -0,0 +1,35 @@ +kind: WavePipeline +metadata: + name: ops-release-harden + description: "Security scan, branch on severity, apply hotfixes, generate changelog" + category: composition + release: true + +input: + source: cli + example: "v1.0.0" + schema: + type: string + description: "Release version or branch to harden" + +steps: + - id: scan + pipeline: audit-security + input: "{{input}}" + + - id: triage + dependencies: [scan] + branch: + on: "{{scan.output.risk_level}}" + cases: + critical: impl-hotfix + high: impl-hotfix + medium: impl-improve + low: skip + + - id: gate + dependencies: [triage] + gate: + type: approval + message: "Review security fixes before release" + timeout: "4h" diff --git a/.wave/pipelines/ops-rewrite.yaml b/.wave/pipelines/ops-rewrite.yaml new file mode 100644 index 0000000..95bd2b2 --- /dev/null +++ b/.wave/pipelines/ops-rewrite.yaml @@ -0,0 +1,123 @@ +kind: WavePipeline +metadata: + name: ops-rewrite + description: "Analyze and rewrite poorly documented issues" + release: true + +skills: + - gh-cli + - software-design + +requires: + tools: + - gh + +input: + source: cli + example: "re-cinq/wave 42 or https://github.com/re-cinq/wave/issues/42" + schema: + type: string + description: "GitHub repo with optional issue number, or full issue URL" + +steps: + - id: scan-and-score + persona: "gitea-analyst" + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Input: {{ input }} + + Step 1: Parse the input format. + - If URL (https://github.com/OWNER/REPO/issues/NUM) → extract <REPO> and <NUM> + - If "owner/repo NUM" → extract <REPO> and <NUM> + - If "owner/repo" alone → batch mode, use {{ input }} as <REPO> + + Step 2: Fetch issues via {{ forge.cli_tool }}. + - Single: {{ forge.cli_tool }} issue view <NUM> --repo <REPO> --json number,title,body,labels,url + - Batch: {{ forge.cli_tool }} issue list --repo {{ input }} --limit 10 --json number,title,body,labels,url + + IMPORTANT: If a specific issue number was parsed from the input but the fetch + fails (not found, permissions error), STOP. Output JSON with repository set + to <REPO>, issues_to_enhance as empty array, and total_to_enhance: 0. + Do NOT fall back to batch mode when a specific issue was requested. + + Step 3: Score each issue quality (0-100) on title clarity, description completeness, labels, and acceptance criteria. + + Step 4: For issues scoring below 70, create an enhancement plan with: + - suggested_title, body_template (preserving original content), suggested_labels, enhancements list + + Output JSON with repository (owner/repo string), issues_to_enhance array, and total_to_enhance. + output_artifacts: + - name: enhancement_plan + path: .wave/artifact.json + type: json + required: true + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/enhancement-plan.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: apply-enhancements + persona: "gitea-enhancer" + dependencies: [scan-and-score] + memory: + inject_artifacts: + - step: scan-and-score + artifact: enhancement_plan + as: impl_plan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Read the "repository" field from the plan artifact to get <REPO>. + + For each issue in issues_to_enhance: + 1. Apply title: {{ forge.cli_tool }} issue edit <NUM> --repo <REPO> --title 'suggested_title' + 2. Apply body — write to temp file first, then apply: + cat > /tmp/issue-body.md <<'BODY_EOF' + <body_template content> + BODY_EOF + {{ forge.cli_tool }} issue edit <NUM> --repo <REPO> --body-file /tmp/issue-body.md + 3. Add labels: {{ forge.cli_tool }} issue edit <NUM> --repo <REPO> --add-label "label1,label2" + 4. Capture URL: {{ forge.cli_tool }} issue view <NUM> --repo <REPO> --json url --jq .url + + Output JSON with enhanced_issues (issue_number, success, changes_made, url), + total_attempted, total_successful. + output_artifacts: + - name: enhancement_results + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .enhanced_issues[*].url + label: "Enhanced Issue" + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/enhancement-results.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/ops-supervise.yaml b/.wave/pipelines/ops-supervise.yaml new file mode 100644 index 0000000..d59913f --- /dev/null +++ b/.wave/pipelines/ops-supervise.yaml @@ -0,0 +1,178 @@ +kind: WavePipeline +metadata: + name: ops-supervise + description: "Review work quality and process quality, including claudit session transcripts" + release: true + +input: + source: cli + example: "review the last pipeline run for quality and process issues" + +steps: + - id: gather + persona: supervisor + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Gather evidence for supervision of: {{ input }} + + ## Smart Input Detection + + Determine what to inspect based on the input: + - **Empty or "last pipeline run"**: Find the most recent pipeline run via `.wave/workspaces/` timestamps and recent git activity + - **"current pr" or "PR #N"**: Inspect the current or specified pull request (`git log`, `{{ forge.cli_tool }} {{ forge.pr_command }} view`) + - **Branch name**: Inspect all commits on that branch vs main + - **Free-form description**: Use grep/git log to find relevant recent work + + ## Evidence Collection + + 1. **Git history**: Recent commits with diffs (`git log --stat`, `git diff`) + 2. **Session transcripts**: Check for claudit git notes (`git notes show <commit>` for each relevant commit). Summarize what happened in each session — tool calls, approach taken, detours, errors + 3. **Pipeline artifacts**: Scan `.wave/workspaces/` for the relevant pipeline run. List all output artifacts and their contents + 4. **Test state**: Run the project's test suite to capture current test status + 5. **Branch/PR context**: Branch name, ahead/behind status, PR state if applicable + + ## Output + + Produce a comprehensive evidence bundle as structured JSON. Include all raw + evidence — the evaluation step will interpret it. + + Be thorough in transcript analysis — the process quality evaluation depends + heavily on understanding what the agent actually did vs what it should have done. + output_artifacts: + - name: evidence + path: .wave/output/supervision-evidence.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/supervision-evidence.json + schema_path: .wave/contracts/supervision-evidence.schema.json + on_failure: retry + + - id: evaluate + persona: supervisor + dependencies: [gather] + memory: + inject_artifacts: + - step: gather + artifact: evidence + as: evidence + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Evaluate the work quality based on gathered evidence. + + The gathered evidence has been injected into your workspace. Read it first. + + ## Output Quality Assessment + + For each dimension, score as excellent/good/adequate/poor with specific findings: + + 1. **Correctness**: Does the code do what was intended? Check logic, edge cases, error handling + 2. **Completeness**: Are all requirements addressed? Any gaps or TODOs left? + 3. **Test coverage**: Are changes adequately tested? Run targeted tests if needed + 4. **Code quality**: Does it follow project conventions? Clean abstractions? Good naming? + + ## Process Quality Assessment + + Using the session transcripts from the evidence: + + 1. **Efficiency**: Was the approach direct? Count unnecessary file reads, repeated searches, abandoned approaches visible in transcripts + 2. **Scope discipline**: Did the agent stay on task? Flag any scope creep — changes unrelated to the original goal + 3. **Tool usage**: Were the right tools used? (e.g., Read vs Bash cat, Glob vs find) + 4. **Token economy**: Was the work concise or bloated? Excessive context gathering? Redundant operations? + + ## Synthesis + + - Overall score (excellent/good/adequate/poor) + - Key strengths (what went well) + - Key concerns (what needs attention) + + Produce the evaluation as a structured JSON result. + output_artifacts: + - name: evaluation + path: .wave/output/supervision-evaluation.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/supervision-evaluation.json + schema_path: .wave/contracts/supervision-evaluation.schema.json + on_failure: retry + + - id: verdict + persona: reviewer + dependencies: [evaluate] + memory: + inject_artifacts: + - step: gather + artifact: evidence + as: evidence + - step: evaluate + artifact: evaluation + as: evaluation + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Synthesize a final supervision verdict. + + The gathered evidence and evaluation have been injected into your workspace. + Read them both before proceeding. + + ## Independent Verification + + 1. Run the project's test suite + 2. Cross-check evaluation claims against actual code + 3. Verify any specific concerns raised in the evaluation + + ## Verdict + + Issue one of: + - **APPROVE**: Work is good quality, process was efficient. Ship it. + - **PARTIAL_APPROVE**: Output is acceptable but process had issues worth noting for improvement. + - **REWORK**: Significant issues found that need to be addressed before the work is acceptable. + + ## Action Items (if REWORK or PARTIAL_APPROVE) + + For each issue requiring action: + - Specific file and line references + - What needs to change and why + - Priority (must-fix vs should-fix) + + ## Lessons Learned + + What should be done differently next time? Process improvements, common pitfalls observed. + + Produce the verdict as a markdown report with clear sections: + ## Verdict, ## Output Quality, ## Process Quality, ## Action Items, ## Lessons Learned + output_artifacts: + - name: verdict + path: .wave/output/supervision-verdict.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/supervision-verdict.md diff --git a/.wave/pipelines/plan-adr.yaml b/.wave/pipelines/plan-adr.yaml new file mode 100644 index 0000000..f4b6c84 --- /dev/null +++ b/.wave/pipelines/plan-adr.yaml @@ -0,0 +1,237 @@ +kind: WavePipeline +metadata: + name: plan-adr + description: "Create an Architecture Decision Record for a design choice" + release: true + +skills: + - software-architecture + +input: + source: cli + example: "ADR: should we use SQLite or PostgreSQL for pipeline state?" + +steps: + - id: explore-context + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Explore the codebase to gather context for this architectural decision: {{ input }} + + ## Exploration + + 1. **Understand the decision space**: What part of the system is this about? + Find all related code, configs, and documentation. + + 2. **Map current state**: How does the system work today? + What would be affected by this decision? + + 3. **Find constraints**: What technical constraints exist? + (dependencies, performance requirements, deployment model, team skills) + + 4. **Check precedents**: Are there similar decisions already made in this + codebase? Look for ADRs, design docs, or relevant comments. + + 5. **Identify stakeholders**: Which components/teams/users are affected? + + Write your findings as structured JSON. + Include: decision_topic, current_state (description, affected_files, affected_components), + constraints, precedents, stakeholders, and timestamp. + output_artifacts: + - name: context + path: .wave/output/adr-context.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/adr-context.json + schema_path: .wave/contracts/adr-context.schema.json + on_failure: retry + + - id: analyze-options + persona: planner + model: claude-haiku + dependencies: [explore-context] + memory: + inject_artifacts: + - step: explore-context + artifact: context + as: decision_context + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze the options for this architectural decision. + + Original decision: {{ input }} + + ## Analysis + + For each viable option: + + 1. **Describe it**: What would this option look like in practice? + 2. **Pros**: What are the benefits? Be specific to THIS project. + 3. **Cons**: What are the drawbacks? Be honest. + 4. **Effort**: How much work to implement? + 5. **Risk**: What could go wrong? + 6. **Reversibility**: How hard to undo if it's the wrong choice? + 7. **Compatibility**: How well does it fit with existing constraints? + + Write your analysis as structured JSON. + Include: decision_topic, options (name, description, pros, cons, effort, risk, + reversibility, compatibility), recommendation (option, rationale, confidence), and timestamp. + output_artifacts: + - name: options + path: .wave/output/adr-options.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/adr-options.json + schema_path: .wave/contracts/adr-options.schema.json + on_failure: retry + + - id: draft-record + persona: philosopher + dependencies: [analyze-options] + memory: + inject_artifacts: + - step: explore-context + artifact: context + as: decision_context + - step: analyze-options + artifact: options + as: analysis + exec: + type: prompt + source: | + Draft the Architecture Decision Record using the injected context and analysis. + + Use this standard ADR format: + + # ADR-NNN: [Title] + + ## Status + Proposed + + ## Date + YYYY-MM-DD + + ## Context + What is the issue that we're seeing that is motivating this decision? + Include technical context from the codebase exploration. + + ## Decision + What is the change that we're proposing and/or doing? + State the recommended option clearly. + + ## Options Considered + + ### Option 1: [Name] + Description, pros, cons. + + ### Option 2: [Name] + Description, pros, cons. + + (etc.) + + ## Consequences + + ### Positive + - What becomes easier or better? + + ### Negative + - What becomes harder or worse? + + ### Neutral + - What other changes are required? + + ## Implementation Notes + - Key steps to implement the decision + - Files/components that need changes + - Migration plan if applicable + + --- + + Write clearly and concisely. The ADR should be understandable by + someone who wasn't part of the original discussion. + output_artifacts: + - name: adr + path: .wave/output/adr.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/adr.md + + - id: publish + persona: craftsman + dependencies: [draft-record] + memory: + inject_artifacts: + - step: draft-record + artifact: adr + as: adr + workspace: + type: worktree + branch: "docs/{{ pipeline_id }}" + exec: + type: prompt + source: | + PUBLISH — commit the ADR and create a pull request. + + ## Steps + + 1. Copy the ADR into the project docs: + - Determine the next ADR number by listing existing ADR files + (e.g., `ls docs/adr/` or similar convention) + - Copy `.wave/artifacts/adr` to the appropriate location + (e.g., `docs/adr/NNN-title.md`) + + 2. Commit: + ```bash + git add docs/adr/ + git commit -m "docs: add ADR for <decision topic>" + ``` + + 3. Push and create PR: + ```bash + git push -u origin HEAD + {{ forge.cli_tool }} {{ forge.pr_command }} create --title "docs: ADR — <decision topic>" --body-file .wave/artifacts/adr + ``` + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + retry: + policy: aggressive + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/plan-research.yaml b/.wave/pipelines/plan-research.yaml new file mode 100644 index 0000000..45437e0 --- /dev/null +++ b/.wave/pipelines/plan-research.yaml @@ -0,0 +1,282 @@ +kind: WavePipeline +metadata: + name: plan-research + description: Research an issue and post findings as a comment + release: true + +skills: + - gh-cli + - software-design + +requires: + tools: + - gh + +input: + source: cli + example: "re-cinq/wave 42" + schema: + type: string + description: "GitHub repository and issue number (e.g. 'owner/repo number')" + +steps: + - id: fetch-issue + persona: "gitea-analyst" + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Fetch the GitHub issue specified in the input: {{ input }} + + CRITICAL: If the input above is empty, blank, or missing, you MUST immediately fail. + Write an error JSON to .wave/output/issue-content.json with issue_number: 0 and + title: "ERROR: No input provided — expected a GitHub issue URL or owner/repo number". + Do NOT guess, infer, or use any example. Stop here. + + Accepted input formats (parse the actual input, never fabricate values): + - Full URL: https://github.com/owner/repo/issues/123 + - Short form: owner/repo 123 + + Parse the input to extract the repository and issue number. + Fetch the issue: + + {{ forge.cli_tool }} issue view <number> --repo <owner/repo> --json number,title,body,labels,state,author,createdAt,url,comments + + Parse the output and produce structured JSON with the issue content. + Include repository information in the output. + output_artifacts: + - name: issue-content + path: .wave/output/issue-content.json + type: json + retry: + policy: aggressive + max_attempts: 3 + handover: + contract: + type: json_schema + source: .wave/output/issue-content.json + schema_path: .wave/contracts/issue-content.schema.json + on_failure: retry + + - id: analyze-topics + persona: researcher + model: claude-haiku + dependencies: [fetch-issue] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Analyze the GitHub issue and extract research topics. + + Identify: + 1. Key technical questions that need external research + 2. Domain concepts that require clarification + 3. External dependencies, libraries, or tools to investigate + 4. Similar problems/solutions that might provide guidance + + For each topic, provide: + - A unique ID (TOPIC-001, TOPIC-002, etc.) + - A clear title + - Specific questions to answer (1-5 questions per topic) + - Search keywords for web research + - Priority (critical/high/medium/low based on relevance to solving the issue) + - Category (technical/documentation/best_practices/security/performance/compatibility/other) + + Focus on topics that will provide actionable insights for the issue author. + Limit to 10 most important topics. + output_artifacts: + - name: topics + path: .wave/output/research-topics.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/research-topics.json + schema_path: .wave/contracts/research-topics.schema.json + on_failure: retry + + - id: research-topics + persona: researcher + model: claude-haiku + dependencies: [analyze-topics] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + - step: analyze-topics + artifact: topics + as: research_plan + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Research the topics identified in the research plan. + + For each topic in the research plan: + 1. Execute web searches using the provided keywords + 2. Evaluate source credibility (official docs > authoritative > community) + 3. Extract relevant findings with key points + 4. Include direct quotes where helpful + 5. Rate your confidence in the answer (high/medium/low/inconclusive) + + For each finding: + - Assign a unique ID (FINDING-001, FINDING-002, etc.) + - Provide a summary (20-2000 characters) + - List key points as bullet items + - Include source URL, title, and type + - Rate relevance to the topic (0-1) + + Always include source URLs for attribution. + If a topic yields no useful results, mark confidence as "inconclusive". + Document any gaps in the research. + output_artifacts: + - name: findings + path: .wave/output/research-findings.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/research-findings.json + schema_path: .wave/contracts/research-findings.schema.json + on_failure: retry + + - id: synthesize-report + persona: summarizer + dependencies: [research-topics] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: original_issue + - step: research-topics + artifact: findings + as: research + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Synthesize the research findings into a coherent report for the GitHub issue. + + Create a well-structured research report that includes: + + 1. Executive Summary: + - Brief overview (50-1000 chars) + - Key findings (1-7 bullet points) + - Primary recommendation + - Confidence assessment (high/medium/low) + + 2. Detailed Findings: + - Organize by topic/section + - Include code examples where relevant + - Reference sources using SRC-### IDs + + 3. Recommendations: + - Actionable items with IDs (REC-001, REC-002, etc.) + - Priority and effort estimates + - Maximum 10 recommendations + + 4. Sources: + - List all sources with IDs (SRC-001, SRC-002, etc.) + - Include URL, title, type, and reliability + + 5. Pre-rendered Markdown: + - Generate complete markdown_content field ready for GitHub comment + - Use proper headers, bullet points, and formatting + - Include a header: "## Research Findings (Wave Pipeline)" + - End with sources section + output_artifacts: + - name: report + path: .wave/output/research-report.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/research-report.json + schema_path: .wave/contracts/research-report.schema.json + on_failure: retry + + - id: post-comment + persona: "gitea-commenter" + dependencies: [synthesize-report] + memory: + inject_artifacts: + - step: fetch-issue + artifact: issue-content + as: issue + - step: synthesize-report + artifact: report + as: report + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Post the research report as a comment on the GitHub issue. + + Steps: + 1. Read the issue details to get the repository and issue number + 2. Read the report to get the markdown_content + 3. Write the markdown content to a file, then use {{ forge.cli_tool }} to post the comment: + + # Write to file to avoid shell escaping issues with large markdown + cat > /tmp/comment-body.md << 'COMMENT_EOF' + <markdown_content> + COMMENT_EOF + + {{ forge.cli_tool }} issue comment <number> --repo <owner/repo> --body-file /tmp/comment-body.md + + 4. Add a footer to the comment: + --- + *Generated by [Wave](https://github.com/re-cinq/wave) issue-research pipeline* + + 5. Capture the result and verify success + 6. If successful, extract the comment URL from the output + + Record the result with: + - success: true/false + - issue_reference: issue number and repository + - comment: id, url, body_length (if successful) + - error: code, message, retryable (if failed) + - timestamp: current time + output_artifacts: + - name: comment-result + path: .wave/output/comment-result.json + type: json + outcomes: + - type: url + extract_from: .wave/output/comment-result.json + json_path: .comment.url + label: "Research Comment" + retry: + policy: aggressive + max_attempts: 3 + handover: + contract: + type: json_schema + source: .wave/output/comment-result.json + schema_path: .wave/contracts/comment-result.schema.json + on_failure: retry diff --git a/.wave/pipelines/plan-scope.yaml b/.wave/pipelines/plan-scope.yaml new file mode 100644 index 0000000..a725d02 --- /dev/null +++ b/.wave/pipelines/plan-scope.yaml @@ -0,0 +1,187 @@ +kind: WavePipeline +metadata: + name: plan-scope + description: "Decompose an epic into well-scoped child issues" + release: true + +skills: + - gh-cli + - software-design + +requires: + tools: + - gh + +input: + source: cli + example: "re-cinq/wave 184" + schema: + type: string + description: "GitHub repository with epic issue number (e.g. 'owner/repo 42')" + +steps: + - id: fetch-epic + persona: "gitea-analyst" + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Input: {{ input }} + + Parse the input: extract the repo (first token) and the epic issue number (second token). + + Execute these steps: + + 1. Fetch the epic issue with full details: + {{ forge.cli_tool }} issue view <NUMBER> --repo <REPO> --json number,title,body,labels,url,comments,author,state + + 2. List existing open issues to check for duplicates: + {{ forge.cli_tool }} issue list --repo <REPO> --limit 50 --json number,title,labels,url + + Analyze the epic: + - Determine if this is truly an epic/umbrella issue (contains multiple work items) + - Identify the key themes and work areas + - Estimate overall complexity + - Count how many sub-issues should be created (3-10) + - List existing issues to avoid creating duplicates + output_artifacts: + - name: epic_assessment + path: .wave/artifact.json + type: json + required: true + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/epic-assessment.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: scope-and-create + persona: "gitea-scoper" + dependencies: [fetch-epic] + memory: + inject_artifacts: + - step: fetch-epic + artifact: epic_assessment + as: assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + The assessment artifact contains the epic analysis. Use it to create well-scoped child issues. + + Input: {{ input }} + Parse the repo from the input (first token). + + For each planned sub-issue, write the body to a temp file and create the issue safely: + cat > /tmp/issue-body.md <<'ISSUE_EOF' + <body content here> + ISSUE_EOF + {{ forge.cli_tool }} issue create --repo <REPO> --title '<title>' --body-file /tmp/issue-body.md --label "<labels>" + + Each sub-issue body MUST include: + - A "Parent: #<epic_number>" reference line + - A clear Summary section + - Acceptance Criteria as a checkbox list + - Dependencies on other sub-issues if applicable + - Scope Notes for what is explicitly excluded + + After creating all issues, capture each issue's number and URL from the creation output. + + Record the results with fields: parent_issue (number, url, repository), + created_issues (array of number, title, url, labels, success, complexity, dependencies), + total_created, total_failed. + output_artifacts: + - name: scope_plan + path: .wave/artifact.json + type: json + required: true + outcomes: + - type: issue + extract_from: .wave/artifact.json + json_path: .created_issues[*].url + label: "First Sub-Issue" + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/scope-plan.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false + + - id: verify-report + persona: "gitea-analyst" + dependencies: [scope-and-create] + memory: + inject_artifacts: + - step: scope-and-create + artifact: scope_plan + as: results + - step: fetch-epic + artifact: epic_assessment + as: assessment + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + Verify the created sub-issues and compile a verification report. + + Input: {{ input }} + Parse the repo (first token) and epic number (second token). + + Step 1: For each created issue in the results, verify it exists: + {{ forge.cli_tool }} issue view <N> --repo <REPO> --json number,title,body,labels + + Check that each issue: + - Exists and is open + - Has acceptance criteria in the body + - References the parent epic + + Step 2: This step is READ-ONLY. Do NOT post comments -- the github-analyst + persona does not have write permissions. Instead, include a pre-rendered markdown + summary in the output JSON that a commenter persona could post later. + + Create a markdown summary with a checklist of all sub-issues (- [ ] #<number> <title>). + + Step 3: Compile the verification report with fields: + parent_issue (number, url), verified_issues (array of number, title, url, exists, + has_acceptance_criteria, references_parent), summary (total_verified, total_valid, + total_issues_created, comment_posted, comment_url). + output_artifacts: + - name: scope_report + path: .wave/artifact.json + type: json + required: true + retry: + policy: none + max_attempts: 1 + handover: + contract: + type: json_schema + schema_path: .wave/contracts/scope-report.schema.json + validate: true + must_pass: true + on_failure: retry + allow_recovery: true + recovery_level: progressive + progressive_validation: false diff --git a/.wave/pipelines/plan-task.yaml b/.wave/pipelines/plan-task.yaml new file mode 100644 index 0000000..d3d0dfc --- /dev/null +++ b/.wave/pipelines/plan-task.yaml @@ -0,0 +1,218 @@ +kind: WavePipeline +metadata: + name: plan-task + description: "Break down a feature into actionable tasks with structured exploration, planning, and review" + release: true + +skills: + - software-architecture + +input: + source: cli + example: "add webhook support for pipeline completion events" + +steps: + - id: explore + persona: navigator + model: claude-haiku + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + You are exploring a codebase to gather context for planning this feature or task: + + {{ input }} + + Your goal is to produce a rich, structured JSON exploration that a planner persona + will use (without any other context) to break the work into tasks. + + ## Exploration Steps + + 1. **Understand the request**: Summarize what is being asked and assess scope + (small = 1-2 files, medium = 3-7 files, large = 8-15 files, epic = 16+ files). + + 2. **Find related files**: Use Glob and Grep to find files related to the feature. + For each file, note its path, relevance (primary/secondary/reference), why it + matters, and key symbols (functions, types, constants) within it. + + 3. **Identify patterns**: Use Read to examine key files. Document codebase patterns + and conventions. Assign each a PAT-### ID and relevance level: + - must_follow: Violating this would break consistency or cause bugs + - should_follow: Strong convention but exceptions exist + - informational: Good to know but not binding + + 4. **Map affected modules**: Identify which packages/modules will be directly or + indirectly affected. Note their dependencies and dependents. + + 5. **Survey testing landscape**: Find test files related to the affected code. + Note testing patterns (table-driven, mocks, fixtures, etc.) and coverage gaps. + + 6. **Assess risks**: Identify potential risks (breaking changes, performance concerns, + security implications). Rate severity (high/medium/low) and suggest mitigations. + + CRITICAL: Write ONLY the JSON object. No markdown wrapping, no explanation + outside the file. The file must parse as valid JSON. + output_artifacts: + - name: exploration + path: .wave/output/exploration.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/exploration.json + schema_path: .wave/contracts/plan-exploration.schema.json + on_failure: retry + + - id: breakdown + persona: planner + dependencies: [explore] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + You are breaking down a feature into actionable implementation tasks. + + ## Input + + Feature request: {{ input }} + + Codebase exploration has already been done and injected into your workspace. + It contains structured JSON with: related files, codebase patterns, + affected modules, testing landscape, and identified risks. Use ALL of this + information to inform your task breakdown. + + ## Task Breakdown Rules + + 1. **Task IDs**: Use T01, T02, T03... format (zero-padded two digits). + + 2. **Personas**: Assign each task to the most appropriate persona: + - navigator: architecture decisions, exploration, planning + - craftsman: implementation, coding, file creation + - philosopher: review, analysis, quality assessment + - auditor: security review, compliance checking + - implementer: focused implementation tasks + - reviewer: code review tasks + + 3. **Dependencies**: Express as task IDs (e.g., ["T01", "T02"]). + A task with no dependencies gets an empty array []. + + 4. **Complexity**: S (< 1hr), M (1-4hr), L (4-8hr), XL (> 1 day). + + 5. **Acceptance criteria**: Each task MUST have at least one concrete, + verifiable acceptance criterion. + + 6. **Affected files**: List files each task will create or modify. + + 7. **Execution order**: Group tasks into phases. Tasks within a phase + can run in parallel. Phase 1 has no dependencies, Phase 2 depends + on Phase 1, etc. + + 8. **Risks**: Note task-specific risks from the exploration. + + CRITICAL: Write ONLY the JSON object. No markdown wrapping, no explanation + outside the file. The file must parse as valid JSON. + output_artifacts: + - name: tasks + path: .wave/output/tasks.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/tasks.json + schema_path: .wave/contracts/plan-tasks.schema.json + on_failure: retry + + - id: review + persona: philosopher + dependencies: [breakdown] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: context + - step: breakdown + artifact: tasks + as: task_list + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + You are reviewing a task breakdown plan for quality, completeness, and correctness. + + ## Input + + Feature request: {{ input }} + + Two artifacts have been injected into your workspace: the codebase exploration + and the task breakdown plan. Read them BOTH before proceeding. + + The exploration contains: related files, patterns, affected modules, testing + landscape, and risks. The task list contains: feature summary, tasks with + dependencies and acceptance criteria, and execution order. + + ## Review Checklist + + For EACH task in the plan, evaluate and assign a status: + - ok: Task is well-defined and ready to execute + - needs_refinement: Good idea but needs clearer description or criteria + - missing_details: Lacks acceptance criteria, affected files, or dependencies + - overcomplicated: Should be split or simplified + - wrong_persona: Different persona would be more appropriate + - bad_dependencies: Dependencies are incorrect or missing + + For each issue found, assign a REV-### ID, severity, description, and suggestion. + + ## Cross-Cutting Concerns + + Look for concerns that span multiple tasks (CC-### IDs): + - Testing strategy: Are tests planned? Do they follow codebase patterns? + - Security: Are security implications addressed? + - Performance: Will changes affect performance? + - Backwards compatibility: Are breaking changes handled? + - Documentation: Is documentation updated? + + ## Recommendations + + Provide actionable recommendations (REC-### IDs) with type: + add_task, modify_task, remove_task, reorder, split_task, merge_tasks, + change_persona, add_dependency + + ## Verdict + + Provide an overall verdict: + - approve: Plan is ready to execute as-is + - approve_with_notes: Plan is good but has minor issues to note + - revise: Plan needs significant changes before execution + + CRITICAL: Write ONLY the JSON object. No markdown wrapping, no explanation + outside the file. The file must parse as valid JSON. + output_artifacts: + - name: review + path: .wave/output/plan-review.json + type: json + retry: + policy: standard + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/plan-review.json + schema_path: .wave/contracts/plan-review.schema.json + on_failure: retry diff --git a/.wave/pipelines/plan.yaml b/.wave/pipelines/plan.yaml new file mode 100644 index 0000000..5f5558e --- /dev/null +++ b/.wave/pipelines/plan.yaml @@ -0,0 +1,208 @@ +kind: WavePipeline +metadata: + name: plan + description: "Break down a feature into actionable tasks with structured exploration, planning, and review" + release: true + +input: + source: cli + example: "add webhook support for pipeline completion events" + +steps: + - id: explore + persona: navigator + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + You are exploring a codebase to gather context for planning this feature or task: + + {{ input }} + + Your goal is to produce a rich, structured JSON exploration that a planner persona + will use (without any other context) to break the work into tasks. + + ## Exploration Steps + + 1. **Understand the request**: Summarize what is being asked and assess scope + (small = 1-2 files, medium = 3-7 files, large = 8-15 files, epic = 16+ files). + + 2. **Find related files**: Use Glob and Grep to find files related to the feature. + For each file, note its path, relevance (primary/secondary/reference), why it + matters, and key symbols (functions, types, constants) within it. + + 3. **Identify patterns**: Use Read to examine key files. Document codebase patterns + and conventions. Assign each a PAT-### ID and relevance level: + - must_follow: Violating this would break consistency or cause bugs + - should_follow: Strong convention but exceptions exist + - informational: Good to know but not binding + + 4. **Map affected modules**: Identify which packages/modules will be directly or + indirectly affected. Note their dependencies and dependents. + + 5. **Survey testing landscape**: Find test files related to the affected code. + Note testing patterns (table-driven, mocks, fixtures, etc.) and coverage gaps. + + 6. **Assess risks**: Identify potential risks (breaking changes, performance concerns, + security implications). Rate severity (high/medium/low) and suggest mitigations. + + CRITICAL: Write ONLY the JSON object. No markdown wrapping, no explanation + outside the file. The file must parse as valid JSON. + output_artifacts: + - name: exploration + path: .wave/output/exploration.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/exploration.json + schema_path: .wave/contracts/plan-exploration.schema.json + on_failure: retry + max_retries: 2 + + - id: breakdown + persona: planner + dependencies: [explore] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: context + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + You are breaking down a feature into actionable implementation tasks. + + ## Input + + Feature request: {{ input }} + + Codebase exploration has already been done and injected into your workspace. + It contains structured JSON with: related files, codebase patterns, + affected modules, testing landscape, and identified risks. Use ALL of this + information to inform your task breakdown. + + ## Task Breakdown Rules + + 1. **Task IDs**: Use T01, T02, T03... format (zero-padded two digits). + + 2. **Personas**: Assign each task to the most appropriate persona: + - navigator: architecture decisions, exploration, planning + - craftsman: implementation, coding, file creation + - philosopher: review, analysis, quality assessment + - auditor: security review, compliance checking + - implementer: focused implementation tasks + - reviewer: code review tasks + + 3. **Dependencies**: Express as task IDs (e.g., ["T01", "T02"]). + A task with no dependencies gets an empty array []. + + 4. **Complexity**: S (< 1hr), M (1-4hr), L (4-8hr), XL (> 1 day). + + 5. **Acceptance criteria**: Each task MUST have at least one concrete, + verifiable acceptance criterion. + + 6. **Affected files**: List files each task will create or modify. + + 7. **Execution order**: Group tasks into phases. Tasks within a phase + can run in parallel. Phase 1 has no dependencies, Phase 2 depends + on Phase 1, etc. + + 8. **Risks**: Note task-specific risks from the exploration. + + CRITICAL: Write ONLY the JSON object. No markdown wrapping, no explanation + outside the file. The file must parse as valid JSON. + output_artifacts: + - name: tasks + path: .wave/output/tasks.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/tasks.json + schema_path: .wave/contracts/plan-tasks.schema.json + on_failure: retry + max_retries: 2 + + - id: review + persona: philosopher + dependencies: [breakdown] + memory: + inject_artifacts: + - step: explore + artifact: exploration + as: context + - step: breakdown + artifact: tasks + as: task_list + workspace: + type: worktree + branch: "{{ pipeline_id }}" + exec: + type: prompt + source: | + You are reviewing a task breakdown plan for quality, completeness, and correctness. + + ## Input + + Feature request: {{ input }} + + Two artifacts have been injected into your workspace: the codebase exploration + and the task breakdown plan. Read them BOTH before proceeding. + + The exploration contains: related files, patterns, affected modules, testing + landscape, and risks. The task list contains: feature summary, tasks with + dependencies and acceptance criteria, and execution order. + + ## Review Checklist + + For EACH task in the plan, evaluate and assign a status: + - ok: Task is well-defined and ready to execute + - needs_refinement: Good idea but needs clearer description or criteria + - missing_details: Lacks acceptance criteria, affected files, or dependencies + - overcomplicated: Should be split or simplified + - wrong_persona: Different persona would be more appropriate + - bad_dependencies: Dependencies are incorrect or missing + + For each issue found, assign a REV-### ID, severity, description, and suggestion. + + ## Cross-Cutting Concerns + + Look for concerns that span multiple tasks (CC-### IDs): + - Testing strategy: Are tests planned? Do they follow codebase patterns? + - Security: Are security implications addressed? + - Performance: Will changes affect performance? + - Backwards compatibility: Are breaking changes handled? + - Documentation: Is documentation updated? + + ## Recommendations + + Provide actionable recommendations (REC-### IDs) with type: + add_task, modify_task, remove_task, reorder, split_task, merge_tasks, + change_persona, add_dependency + + ## Verdict + + Provide an overall verdict: + - approve: Plan is ready to execute as-is + - approve_with_notes: Plan is good but has minor issues to note + - revise: Plan needs significant changes before execution + + CRITICAL: Write ONLY the JSON object. No markdown wrapping, no explanation + outside the file. The file must parse as valid JSON. + output_artifacts: + - name: review + path: .wave/output/plan-review.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/plan-review.json + schema_path: .wave/contracts/plan-review.schema.json + on_failure: retry + max_retries: 2 diff --git a/.wave/pipelines/recinq.yaml b/.wave/pipelines/recinq.yaml new file mode 100644 index 0000000..21ab4dd --- /dev/null +++ b/.wave/pipelines/recinq.yaml @@ -0,0 +1,535 @@ +kind: WavePipeline +metadata: + name: recinq + description: "Rethink and simplify code using divergent-convergent thinking (Double Diamond)" + release: true + +input: + source: cli + example: "internal/pipeline" + +# Pipeline structure implements the Double Diamond: +# +# gather → diverge → converge → probe → distill → simplify → report +# ╰─ Diamond 1 ─╯ ╰─ Diamond 2 ─╯ ╰implement╯ +# (discover) (define) (develop) (deliver) +# +# Each step gets its own context window and cognitive mode. +# Fresh memory at every boundary — no mode-switching within a step. + +steps: + - id: gather + persona: github-analyst + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + CONTEXT GATHERING — parse input and fetch GitHub context if applicable. + + Input: {{ input }} + + ## Instructions + + Determine what kind of input this is: + + 1. **GitHub Issue URL**: Contains `github.com` and `/issues/` + - Extract owner/repo and issue number from the URL + - Run: `gh issue view <number> --repo <owner/repo> --json title,body,labels` + - Extract a `focus_hint` summarizing what should be simplified + + 2. **GitHub PR URL**: Contains `github.com` and `/pull/` + - Extract owner/repo and PR number from the URL + - Run: `gh pr view <number> --repo <owner/repo> --json title,body,labels,files` + - Extract a `focus_hint` summarizing what the PR is about + + 3. **Local path or description**: Anything else + - Set `input_type` to `"local"` + - Pass through the original input as-is + + ## Output + + IMPORTANT: The output MUST be valid JSON. Do NOT include markdown fencing. + output_artifacts: + - name: context + path: .wave/output/context.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/context.json + schema_path: .wave/contracts/recinq-context.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + + # ── Diamond 1: Discover (DIVERGENT) ────────────────────────────────── + - id: diverge + persona: provocateur + dependencies: [gather] + memory: + inject_artifacts: + - step: gather + artifact: context + as: context + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + DIVERGENT THINKING — cast the widest net to find simplification opportunities. + + Target: {{ input }} + + ## Starting Point + + The context artifact contains input context. + If `input_type` is `"issue"` or `"pr"`, the `focus_hint` tells you WHERE to start looking — + but do NOT limit yourself to what the issue describes. Use it as a seed, then expand outward. + Follow dependency chains, trace callers, explore adjacent modules. The issue author doesn't + know what they don't know — that's YOUR job. + If `input_type` is `"local"`, use the `original_input` field as the target path. + + If input is empty or "." — analyze the whole project. + If input is a path — focus on that module/directory but consider its connections. + + ## Your Mission + + Challenge EVERYTHING. Question every assumption. Hunt complexity. + + ## What to Look For + + 1. **Premature abstractions**: Interfaces with one implementation. Generic code used once. + "What if we just inlined this?" + + 2. **Unnecessary indirection**: Layers that pass-through without adding value. + Wrappers around wrappers. "How many hops to get to the actual logic?" + + 3. **Overengineering**: Configuration for things that never change. Plugins with one plugin. + Feature flags for features that are always on. "Is this complexity earning its keep?" + + 4. **YAGNI violations**: Code written for hypothetical future needs that never arrived. + "When was this last changed? Does anyone actually use this path?" + + 5. **Accidental complexity**: Things that are hard because of how they're built, not because + the problem is hard. "Could this be 10x simpler if we started over?" + + 6. **Copy-paste drift**: Similar-but-slightly-different code that should be unified or + intentionally differentiated. "Are these differences meaningful or accidental?" + + 7. **Dead weight**: Unused exports, unreachable code, obsolete comments, stale TODOs. + `grep -r` for references. If nothing uses it, flag it. + + 8. **Naming lies**: Names that don't match what the code actually does. + "Does this 'manager' actually manage anything?" + + 9. **Dependency gravity**: Modules that pull in everything. Import graphs that are too dense. + "What's the blast radius of changing this?" + + ## Evidence Requirements + + For EVERY finding, gather concrete metrics: + - `wc -l` for line counts + - `grep -r` for usage/reference counts + - `git log --oneline <file> | wc -l` for change frequency + - List the actual files involved + + ## Output + + Each finding gets a unique ID: DVG-001, DVG-002, etc. + + Be AGGRESSIVE — flag everything suspicious. The convergent phase will filter. + It's better to have 30 findings with 10 false positives than 5 findings that miss + the big opportunities. + + Include a metrics_summary with totals by category and severity, plus hotspot files + that appear in multiple findings. + output_artifacts: + - name: findings + path: .wave/output/divergent-findings.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/divergent-findings.json + schema_path: .wave/contracts/divergent-findings.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + + # ── Diamond 1: Define (CONVERGENT) ─────────────────────────────────── + - id: converge + persona: validator + dependencies: [diverge] + memory: + inject_artifacts: + - step: diverge + artifact: findings + as: divergent_findings + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + CONVERGENT VALIDATION — separate signal from noise. + + This is a purely CONVERGENT step. Your job is analytical, not creative. + Judge every finding on technical merit alone. No speculation, no new ideas. + + Target: {{ input }} + + ## For Every DVG-xxx Finding + + 1. **Read the actual code** cited as evidence — don't trust the provocateur's summary + 2. **Verify the metrics** — check reference counts, line counts, change frequency + 3. **Assess**: Is this a real problem or a false positive? + - Does the "premature abstraction" actually have a second implementation planned? + - Is the "dead weight" actually used via reflection or codegen? + - Is the "unnecessary indirection" actually providing error handling or logging? + 4. **Classify**: + - `CONFIRMED` — real problem, metrics check out, code supports the claim + - `PARTIALLY_CONFIRMED` — real concern but overstated, or scope is narrower than claimed + - `REJECTED` — false positive, justified complexity, or incorrect metrics + 5. **Explain**: For every classification, write WHY. For rejections, explain what + the provocateur got wrong. + + Be RIGOROUS. The provocateur was told to be aggressive — your job is to be skeptical. + A finding that survives your scrutiny is genuinely worth addressing. + output_artifacts: + - name: validated_findings + path: .wave/output/validated-findings.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/validated-findings.json + schema_path: .wave/contracts/validated-findings.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + + # ── Diamond 2: Develop (DIVERGENT) ─────────────────────────────────── + - id: probe + persona: provocateur + dependencies: [converge] + memory: + inject_artifacts: + - step: diverge + artifact: findings + as: divergent_findings + - step: converge + artifact: validated_findings + as: validated_findings + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + SECOND DIVERGENCE — probe deeper into confirmed findings. + + The first pass cast a wide net. The validator filtered it down. + Now YOU go deeper on what survived. This is DIVERGENT thinking again — + expand, connect, discover what the first pass missed. + + Focus on findings with status CONFIRMED or PARTIALLY_CONFIRMED. + + Target: {{ input }} + + ## Your Mission + + For each confirmed finding, probe OUTWARD: + + 1. **Trace the dependency graph**: What calls this code? What does it call? + If we simplify X, what happens to its callers and callees? + + 2. **Find second-order effects**: If we remove abstraction A, does layer B + also become unnecessary? Do test helpers simplify? Do error paths collapse? + + 3. **Spot patterns across findings**: Do three findings all stem from the same + over-abstraction? Is there a root cause that would address multiple DVGs at once? + + 4. **Discover what was MISSED**: With the validated findings as context, look for + related opportunities the first pass didn't see. The confirmed findings reveal + the codebase's real pressure points — what else lurks nearby? + + 5. **Challenge the rejections**: Were any findings rejected too hastily? + Read the validator's rationale — do you disagree? + + ## Evidence Requirements + + Same standard as the first diverge pass: + - `wc -l` for line counts + - `grep -r` for usage/reference counts + - `git log --oneline <file> | wc -l` for change frequency + - Concrete file paths and code references + + ## Output + + Go DEEP. The first pass was wide, this pass is deep. Follow every thread. + output_artifacts: + - name: probed_findings + path: .wave/output/probed-findings.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/probed-findings.json + schema_path: .wave/contracts/probed-findings.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + + # ── Diamond 2: Deliver (CONVERGENT) ────────────────────────────────── + - id: distill + persona: synthesizer + dependencies: [probe] + memory: + inject_artifacts: + - step: gather + artifact: context + as: context + - step: converge + artifact: validated_findings + as: validated_findings + - step: probe + artifact: probed_findings + as: probed_findings + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + FINAL CONVERGENCE — synthesize all findings into actionable proposals. + + This is the last convergent step before implementation. You have: + - Validated findings (what survived scrutiny) + - Probed findings (deeper connections, patterns, new discoveries) + - Optional issue/PR context (from the gather step) + + Your job: synthesize everything into prioritized, implementable proposals. + + Target: {{ input }} + + ## Synthesis + + Transform the validated and probed findings into prioritized proposals: + + 1. **Group by pattern**: Use the `patterns` from the probe step. Findings that share + a root cause become a single proposal addressing the root cause. + + 2. **Incorporate second-order effects**: The probe step found connections and cascading + simplifications. Factor these into impact estimates. + + 3. **Include new discoveries**: The probe step may have found new findings (DVG-NEW-xxx). + These are pre-validated by the provocateur's second pass — include them. + + 4. **Apply issue/PR context (if present)**: If the context artifact shows + `input_type` is `"issue"` or `"pr"`, use the `focus_hint` as ONE input + when assigning tiers. But do not discard strong proposals just because they + fall outside the issue's scope. The best simplifications are often the ones + the issue author didn't think to ask for. + + 5. **80/20 analysis**: which 20% of proposals yield 80% of the simplification? + + 6. **Dependency ordering**: what must be done first? + + ## Output + + Do NOT write a markdown summary. Write the complete JSON object with every proposal fully populated. + output_artifacts: + - name: proposals + path: .wave/output/convergent-proposals.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/convergent-proposals.json + schema_path: .wave/contracts/convergent-proposals.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + + # ── Implementation ─────────────────────────────────────────────────── + - id: simplify + persona: craftsman + dependencies: [distill] + memory: + inject_artifacts: + - step: converge + artifact: validated_findings + as: validated_findings + - step: distill + artifact: proposals + as: proposals + workspace: + type: worktree + branch: "refactor/{{ pipeline_id }}" + exec: + type: prompt + source: | + IMPLEMENTATION — apply the best simplification proposals. + + ## Process + + Apply ONLY tier-1 proposals, in dependency order. + + For each proposal (SMP-xxx): + + 1. **Announce**: Print which proposal you're applying and what it does + 2. **Apply**: Make the code changes + 3. **Build**: `go build ./...` — must succeed + 4. **Test**: `go test ./...` — must pass + 5. **Commit**: If build and tests pass: + ```bash + git add <specific-files> + git commit -m "refactor: <proposal title> + + Applies SMP-xxx: <brief description> + Source findings: <DVG-xxx list>" + ``` + 6. **Revert if failing**: If tests fail after applying, revert: + ```bash + git checkout -- . + ``` + Log the failure and move to the next proposal. + + ## Final Verification + + After all tier-1 proposals are applied (or attempted): + 1. Run the full test suite: `go test -race ./...` + 2. Run the build: `go build ./...` + 3. Summarize what was applied, what was skipped, and net lines changed + + ## Important + + - Each proposal gets its own atomic commit + - Never combine proposals in a single commit + - If a proposal depends on a failed proposal, skip it too + - Commit each proposal as a separate atomic commit + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + must_pass: true + on_failure: retry + max_retries: 3 + output_artifacts: + - name: result + path: .wave/output/result.md + type: markdown + + # ── Reporting ──────────────────────────────────────────────────────── + - id: report + persona: navigator + dependencies: [simplify] + memory: + inject_artifacts: + - step: distill + artifact: proposals + as: proposals + - step: simplify + artifact: result + as: result + workspace: + type: worktree + branch: "refactor/{{ pipeline_id }}" + exec: + type: prompt + source: | + REPORT — compose a summary of what recinq found and applied. + + Run `git log --oneline` to see the commits on this branch. + + ## Compose the Report + + Write a markdown report containing: + - **Summary**: One-paragraph overview of what recinq found and applied + - **Proposals**: List of all proposals with their tier, impact, and status (applied/skipped/failed) + - **Changes Applied**: Summary of commits made, files changed, net lines removed + - **Remaining Opportunities**: Tier-2 and tier-3 proposals for future consideration + output_artifacts: + - name: report + path: .wave/output/report.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/report.md + + # ── Publish ───────────────────────────────────────────────────────── + - id: publish + persona: craftsman + dependencies: [report, gather] + memory: + inject_artifacts: + - step: gather + artifact: context + as: context + - step: report + artifact: report + as: report + workspace: + type: worktree + branch: "refactor/{{ pipeline_id }}" + exec: + type: prompt + source: | + PUBLISH — push the branch and create a pull request. + + ## Steps + + 1. Push the branch: + ```bash + git push -u origin HEAD + ``` + + 2. Create a pull request using the report as the body: + ```bash + gh pr create --title "refactor: $(git log --format=%s -1)" --body-file .wave/artifacts/report + ``` + + 3. If the context artifact shows `input_type` is `"issue"` or `"pr"`, + post the PR URL as a comment on the source: + ```bash + gh issue comment <number> --repo <repo> --body "Refactoring PR: <pr-url>" + ``` + or for PRs: + ```bash + gh pr comment <number> --repo <repo> --body "Refactoring PR: <pr-url>" + ``` + + 4. Write the JSON status report to the output artifact path. + + If any `gh` command fails, log the error and continue. + output_artifacts: + - name: pr-result + path: .wave/output/pr-result.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/pr-result.json + schema_path: .wave/contracts/pr-result.schema.json + must_pass: true + on_failure: retry + max_retries: 2 + outcomes: + - type: pr + extract_from: .wave/output/pr-result.json + json_path: .pr_url + label: "Pull Request" diff --git a/.wave/pipelines/refactor.yaml b/.wave/pipelines/refactor.yaml new file mode 100644 index 0000000..59ac6d0 --- /dev/null +++ b/.wave/pipelines/refactor.yaml @@ -0,0 +1,136 @@ +kind: WavePipeline +metadata: + name: refactor + description: "Safe refactoring with comprehensive test coverage" + release: true + +input: + source: cli + example: "extract workspace manager from executor into its own package" + +steps: + - id: analyze + persona: navigator + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze refactoring scope for: {{ input }} + + 1. Identify all code that will be affected + 2. Map all callers/consumers of the code being refactored + 3. Find existing test coverage + 4. Identify integration points + output_artifacts: + - name: analysis + path: .wave/output/refactor-analysis.json + type: json + handover: + contract: + type: json_schema + source: .wave/output/refactor-analysis.json + schema_path: .wave/contracts/refactor-analysis.schema.json + on_failure: retry + max_retries: 2 + + - id: test-baseline + persona: craftsman + dependencies: [analyze] + memory: + inject_artifacts: + - step: analyze + artifact: analysis + as: scope + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Before refactoring, ensure test coverage: + + 1. Run existing tests and record baseline + 2. Add characterization tests for uncovered code paths + 3. Add integration tests for affected callers + 4. Document current behavior for comparison + + All tests must pass before proceeding. + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + + must_pass: true + on_failure: retry + max_retries: 2 + output_artifacts: + - name: baseline + path: .wave/output/test-baseline.md + type: markdown + - id: refactor + persona: craftsman + dependencies: [test-baseline] + memory: + inject_artifacts: + - step: analyze + artifact: analysis + as: scope + - step: test-baseline + artifact: baseline + as: tests + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Perform the refactoring: {{ input }} + + Guidelines: + 1. Make atomic, reviewable changes + 2. Preserve all existing behavior + 3. Run tests after each significant change + 4. Update affected callers as needed + 5. Keep commits small and focused + + Do NOT change behavior — this is refactoring only. + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + + must_pass: false + on_failure: retry + max_retries: 3 + + - id: verify + persona: reviewer + dependencies: [refactor] + exec: + type: prompt + source: | + Verify the refactoring: + + 1. Compare before/after behavior — any changes? + 2. Check test coverage didn't decrease + 3. Verify all callers still work correctly + 4. Look for missed edge cases + 5. Assess code quality improvement + + Output: PASS (safe to merge) or FAIL (issues found) + output_artifacts: + - name: verification + path: .wave/output/verification.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/verification.md diff --git a/.wave/pipelines/test-gen.yaml b/.wave/pipelines/test-gen.yaml new file mode 100644 index 0000000..69d0ac1 --- /dev/null +++ b/.wave/pipelines/test-gen.yaml @@ -0,0 +1,125 @@ +kind: WavePipeline +metadata: + name: test-gen + description: "Generate comprehensive test coverage" + release: true + +skills: + - "{{ project.skill }}" + +input: + source: cli + example: "generate tests for internal/pipeline to improve coverage" + +steps: + - id: analyze-coverage + persona: navigator + model: claude-haiku + workspace: + mount: + - source: ./ + target: /project + mode: readonly + exec: + type: prompt + source: | + Analyze test coverage for: {{ input }} + + 1. Run coverage analysis using the project test command with coverage flags + 2. Identify uncovered functions and branches + 3. Find edge cases not tested + 4. Map dependencies that need mocking + output_artifacts: + - name: coverage + path: .wave/output/coverage-analysis.json + type: json + retry: + policy: patient + max_attempts: 2 + handover: + contract: + type: json_schema + source: .wave/output/coverage-analysis.json + schema_path: .wave/contracts/coverage-analysis.schema.json + on_failure: retry + + - id: generate-tests + persona: craftsman + dependencies: [analyze-coverage] + thread: test-gen + max_visits: 3 + memory: + inject_artifacts: + - step: analyze-coverage + artifact: coverage + as: gaps + workspace: + mount: + - source: ./ + target: /project + mode: readwrite + exec: + type: prompt + source: | + Generate tests to improve coverage for: {{ input }} + + Requirements: + 1. Write table-driven tests where appropriate + 2. Cover happy path, error cases, and edge cases + 3. Use descriptive test names (TestFunction_Condition_Expected) + 4. Add mocks for external dependencies + 5. Include benchmarks for performance-critical code + + Follow existing test patterns in the codebase. + retry: + policy: standard + max_attempts: 3 + handover: + contract: + type: test_suite + command: "{{ project.test_command }}" + + must_pass: false + on_failure: retry + output_artifacts: + - name: tests + path: .wave/output/generated-tests.md + type: markdown + + - id: run-tests + type: command + dependencies: [generate-tests] + script: "{{ project.contract_test_command }}" + + - id: check-quality + type: conditional + dependencies: [run-tests] + edges: + - target: verify-coverage + condition: "outcome=success" + - target: generate-tests + + - id: verify-coverage + persona: reviewer + model: claude-haiku + dependencies: [check-quality] + exec: + type: prompt + source: | + Verify the generated tests: + + 1. Run coverage again — did it improve? + 2. Are tests meaningful (not just line coverage)? + 3. Do tests actually catch bugs? + 4. Are mocks appropriate and minimal? + 5. Is test code maintainable? + + Output: coverage delta and quality assessment + output_artifacts: + - name: verification + path: .wave/output/coverage-verification.md + type: markdown + handover: + contract: + type: non_empty_file + source: .wave/output/coverage-verification.md diff --git a/.wave/prompts/bench/solve.md b/.wave/prompts/bench/solve.md new file mode 100644 index 0000000..ceeedd6 --- /dev/null +++ b/.wave/prompts/bench/solve.md @@ -0,0 +1,21 @@ +# SWE-bench Task: Fix the Issue + +You are solving a software engineering task from the SWE-bench benchmark. + +## Problem Statement + +{{ input }} + +## Instructions + +1. **Understand the problem**: Read the problem statement carefully. Identify which files and functions are involved. +2. **Explore the codebase**: Use Read, Glob, and Grep to find the relevant source files. Understand the existing code before making changes. +3. **Implement a minimal fix**: Make the smallest change that correctly solves the problem. Do not refactor unrelated code. +4. **Verify your fix**: Run the project's test suite to confirm your changes work and do not break existing tests. + +## Constraints + +- Make minimal, focused changes — only modify what is necessary to fix the issue. +- Do not add unrelated improvements, documentation, or refactoring. +- Do not create new files unless the fix requires it. +- Ensure all existing tests continue to pass after your changes. diff --git a/.wave/prompts/implement/create-pr.md b/.wave/prompts/implement/create-pr.md new file mode 100644 index 0000000..2732b7c --- /dev/null +++ b/.wave/prompts/implement/create-pr.md @@ -0,0 +1,131 @@ +You are creating a {{ forge.pr_term }} for the implemented issue. + +Input: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by the +plan step and is already checked out. All git operations here are isolated from +the main working tree. + +Read the issue assessment artifact to find the issue number, repository, branch name, and issue URL. + +## SAFETY: Do NOT Modify the Working Tree + +This step MUST NOT run `git checkout`, `git stash`, or any command that changes +the current branch or working tree state. The branch already exists from the +implement step — just push it and create the {{ forge.pr_term }}. + +## Instructions + +### Step 1: Load Context + +From the issue assessment artifact, extract: +- Issue number and title +- Repository (`owner/repo`) +- Branch name +- Issue URL + +### Step 2: Push the Branch + +Push the feature branch. If SSH push fails, retry with HTTPS: + +```bash +git push -u origin <BRANCH_NAME> || GIT_SSH_COMMAND="ssh -F /dev/null" git push -u origin <BRANCH_NAME> +``` + +### Step 3: Create {{ forge.pr_term }} + +Use the appropriate CLI for your platform ({{ forge.type }}) to create the {{ forge.pr_term }}. +The description MUST include `Related to #<NUMBER>` to link the issue (without auto-closing it when the PR is closed without merge). + +**For GitHub** (`gh`): +```bash +gh pr create --repo <OWNER/REPO> --head <BRANCH_NAME> --title "<concise title>" --body "$(cat <<'EOF' +## Summary +<3-5 bullet points describing the changes> + +Related to #<ISSUE_NUMBER> + +## Changes +<list of key files changed and why> + +## Test Plan +<how the changes were validated> +EOF +)" +``` + +**For GitLab** (`glab`): +```bash +cat > /tmp/mr-body.md <<'EOF' +## Summary +<3-5 bullet points describing the changes> + +Related to #<ISSUE_NUMBER> + +## Changes +<list of key files changed and why> + +## Test Plan +<how the changes were validated> +EOF +glab mr create --repo <OWNER/REPO> --source-branch <BRANCH_NAME> --target-branch main --title '<concise title>' --description "$(cat /tmp/mr-body.md)" +``` + +**For Bitbucket** (REST API): +```bash +cat > /tmp/bb-payload.json << 'PRBODY' +{ + "title": "PR title", + "description": "PR description\n\nRelated to #NUMBER", + "source": {"branch": {"name": "BRANCH_NAME"}}, + "destination": {"branch": {"name": "main"}}, + "close_source_branch": true +} +PRBODY + +curl -s -X POST -H "Authorization: Bearer $BB_TOKEN" -H "Content-Type: application/json" \ + -d @/tmp/bb-payload.json \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/pullrequests" \ + | jq '{id, url: .links.html.href}' +``` + +**For Gitea** (`tea`): +```bash +cat > /tmp/pr-body.md <<'EOF' +## Summary +<3-5 bullet points describing the changes> + +Related to #<ISSUE_NUMBER> + +## Changes +<list of key files changed and why> + +## Test Plan +<how the changes were validated> +EOF +tea pulls create --repo <OWNER/REPO> --head <BRANCH_NAME> --base main --title '<concise title>' --description "$(cat /tmp/pr-body.md)" +``` + +### Step 4: Request Review (Best-Effort) + +After the {{ forge.pr_term }} is created, attempt to add a reviewer. This is a best-effort +operation — if it fails, the {{ forge.pr_term }} is still created successfully. + +**For GitHub**: `gh pr edit --add-reviewer "copilot"` +**For GitLab**: `glab mr update <MR_NUMBER> --reviewer "<username>"` +**For Bitbucket**: Update PR via REST API with reviewers +**For Gitea**: Skip (not directly supported by tea CLI) + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT run `git checkout`, `git stash`, or any branch-switching commands +- The {{ forge.pr_term }} description MUST contain `Related to #<NUMBER>` to link to the issue +- Do NOT include Co-Authored-By or AI attribution in commits + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/prompts/implement/fetch-assess.md b/.wave/prompts/implement/fetch-assess.md new file mode 100644 index 0000000..38a410c --- /dev/null +++ b/.wave/prompts/implement/fetch-assess.md @@ -0,0 +1,99 @@ +You are fetching an issue and assessing whether it has enough detail to implement. + +Input: {{ input }} + +The input format is `owner/repo number` (e.g. `re-cinq/wave 42`). + +## Working Directory + +You are running in an isolated Wave workspace. The `{{ forge.cli_tool }}` CLI works from any +directory when using the `--repo` flag, so no directory change is needed. + +## Instructions + +### Step 1: Parse Input + +Extract the repository (`owner/repo`) and issue number from the input string. + +### Step 2: Fetch Issue + +Use the appropriate CLI for your platform ({{ forge.type }}) to fetch the issue with full details. + +**For GitHub** (`gh`): +```bash +gh issue view <NUMBER> --repo <OWNER/REPO> --json number,title,body,url,labels,state,author,comments +``` + +**For GitLab** (`glab`): +```bash +glab issue view <NUMBER> --repo <OWNER/REPO> --output json +``` + +**For Bitbucket** (REST API): +```bash +curl -s -H "Authorization: Bearer $BB_TOKEN" \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues/NUMBER" \ + | jq '{id, title, content: .content.raw, state, kind, reporter: .reporter.display_name, created_on, url: .links.html.href}' +``` +Also fetch comments: +```bash +curl -s -H "Authorization: Bearer $BB_TOKEN" \ + "https://api.bitbucket.org/2.0/repositories/WORKSPACE/REPO/issues/NUMBER/comments" \ + | jq '[.values[] | {id, content: .content.raw, user: .user.display_name, created_on}]' +``` + +**For Gitea** (`tea`): +```bash +tea issues view <NUMBER> --repo <OWNER/REPO> --output json +``` + +### Step 3: Assess Implementability + +Evaluate the issue against these criteria: + +1. **Clear description**: Does the issue describe what needs to change? (not just "X is broken") +2. **Sufficient context**: Can you identify which code/files are affected? +3. **Testable outcome**: Are there acceptance criteria, or can you infer them from the description? + +Score the issue 0-100: +- **80-100**: Well-specified, clear requirements, acceptance criteria present +- **60-79**: Adequate detail, some inference needed but feasible +- **40-59**: Marginal — missing key details but core intent is clear +- **0-39**: Too vague to implement — set `implementable` to `false` + +### Step 4: Determine Skip Steps + +Based on the issue quality, decide which speckit steps can be skipped: +- Issues with detailed specs can skip `specify`, `clarify`, `checklist`, `analyze` +- Issues with moderate detail might skip `specify` and `clarify` only +- Vague issues should skip nothing (but those should fail the assessment) + +### Step 5: Generate Branch Name + +Create a branch name using the pattern `<NNN>-<short-name>` where: +- `<NNN>` is the issue number zero-padded to 3 digits +- `<short-name>` is 2-3 words from the issue title, kebab-case + +### Step 6: Assess Complexity + +Estimate implementation complexity: +- **trivial**: Single file change, obvious fix (typo, config tweak) +- **simple**: 1-3 files, straightforward logic change +- **medium**: 3-10 files, new feature with tests +- **complex**: 10+ files, architectural changes, cross-cutting concerns + +## CRITICAL: Implementability Gate + +If the issue does NOT have enough detail to implement: +- Set `"implementable": false` in the output +- This will cause the contract validation to fail, aborting the pipeline +- Include `missing_info` listing what specific information is needed +- Include a `summary` explaining why the issue cannot be implemented as-is + +If the issue IS implementable: +- Set `"implementable": true` + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT modify the issue — this is read-only assessment diff --git a/.wave/prompts/implement/implement.md b/.wave/prompts/implement/implement.md new file mode 100644 index 0000000..3db45bd --- /dev/null +++ b/.wave/prompts/implement/implement.md @@ -0,0 +1,92 @@ +You are implementing an issue according to the plan and task breakdown. + +Input: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by the +plan step and is already checked out. All git operations here are isolated from +the main working tree. + +## Instructions + +### Step 1: Load Context + +1. Get the issue details and branch name from the issue assessment artifact +2. Get the task breakdown, file changes, and feature directory from the plan artifact + +### Step 2: Read Plan Files + +Navigate to the feature directory and read: +- `spec.md` — the full specification +- `plan.md` — the implementation plan +- `tasks.md` — the phased task breakdown + +### Step 3: Execute Implementation + +Follow the task breakdown phase by phase: + +**Setup first**: Initialize project structure, dependencies, configuration + +**Tests before code (TDD)**: +- Write tests that define expected behavior +- Run tests to confirm they fail for the right reason +- Implement the code to make tests pass + +**Core development**: Implement the changes specified in the plan + +**Integration**: Wire components together, update imports, middleware + +**Polish**: Edge cases, error handling, documentation updates + +### Step 4: Validate Between Phases + +After each phase, run: +```bash +{{ project.test_command }} +``` + +If tests fail, fix the issue before proceeding to the next phase. + +### Step 5: Mark Completed Tasks + +As you complete each task, mark it as `[X]` in `tasks.md`. + +### Step 6: Final Validation + +After all tasks are complete: +1. Run `{{ project.test_command }}` one final time +2. Verify all tasks in `tasks.md` are marked complete +3. Stage and commit all changes — YOU MUST run the git reset to exclude Wave internals: + ```bash + git add -A + git reset HEAD -- .wave/artifacts/ .wave/output/ .claude/ CLAUDE.md 2>/dev/null || true + git diff --cached --name-only | head -20 # verify no .wave/artifacts, .wave/output, .claude, or CLAUDE.md + git commit -m "feat: implement #<ISSUE_NUMBER> — <short description>" + ``` + + CRITICAL: Never use `Closes #N`, `Fixes #N`, or `Resolves #N` in commit messages — these auto-close issues on merge. Use the issue number without closing keywords as shown above. + CRITICAL: Never commit `.claude/settings.json`, `CLAUDE.md`, `.wave/artifacts/`, or `.wave/output/`. + These are Wave-managed files. The `specs/` directory IS allowed. + +Commit changes to the worktree branch. + +## Agent Usage + +Maximize parallelism with up to 6 Task agents for independent work: +- Agents 1-2: Setup and foundational tasks (Phase 1-2) +- Agents 3-4: Core implementation tasks (parallelizable [P] tasks) +- Agent 5: Test writing and validation +- Agent 6: Integration and polish tasks + +Coordinate agents to respect task dependencies: +- Sequential tasks (no [P] marker) must complete before dependents start +- Parallel tasks [P] affecting different files can run simultaneously +- Run test validation between phases + +## Error Handling + +- If a task fails, halt dependent tasks but continue independent ones +- Provide clear error context for debugging +- If tests fail, fix the issue before proceeding to the next phase diff --git a/.wave/prompts/implement/plan.md b/.wave/prompts/implement/plan.md new file mode 100644 index 0000000..b74ce89 --- /dev/null +++ b/.wave/prompts/implement/plan.md @@ -0,0 +1,91 @@ +You are creating an implementation plan for an issue. + +Input: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** checked out at `main` (detached HEAD). +Your working directory IS the project root. All git operations here are isolated +from the main working tree and will not affect it. + +Create a feature branch from this clean starting point. + +## Instructions + +### Step 1: Read Assessment + +From the issue assessment artifact, extract: +- Issue number, title, body, and repository +- Branch name from the assessment +- Complexity estimate +- Which speckit steps were skipped + +### Step 2: Create Feature Branch + +Create a feature branch using the branch name from the assessment: + +```bash +git checkout -b <BRANCH_NAME> +``` + +If the branch already exists (e.g. from a resume), check it out instead: +```bash +git checkout <BRANCH_NAME> +``` + +### Step 3: Write Spec from Issue + +In the feature directory (e.g. `specs/<BRANCH_NAME>/`), create `spec.md` with: +- Issue title as heading +- Full issue body +- Labels and metadata +- Any acceptance criteria extracted from the issue +- Link back to the original issue URL + +### Step 4: Create Implementation Plan + +Write `plan.md` in the feature directory with: + +1. **Objective**: What the issue asks for (1-2 sentences) +2. **Approach**: High-level strategy +3. **File Mapping**: Which files need to be created/modified/deleted +4. **Architecture Decisions**: Any design choices made +5. **Risks**: Potential issues and mitigations +6. **Testing Strategy**: What tests are needed + +### Step 5: Create Task Breakdown + +Write `tasks.md` in the feature directory with a phased breakdown: + +```markdown +# Tasks + +## Phase 1: Setup +- [ ] Task 1.1: Description +- [ ] Task 1.2: Description + +## Phase 2: Core Implementation +- [ ] Task 2.1: Description [P] (parallelizable) +- [ ] Task 2.2: Description [P] + +## Phase 3: Testing +- [ ] Task 3.1: Write unit tests +- [ ] Task 3.2: Write integration tests + +## Phase 4: Polish +- [ ] Task 4.1: Documentation updates +- [ ] Task 4.2: Final validation +``` + +Mark parallelizable tasks with `[P]`. + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT start implementation — only planning in this step +- Do NOT use WebSearch — all information is in the issue and codebase +- Do NOT create files or directories under `.wave/artifacts/` — that path is managed by the pipeline orchestrator + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/prompts/speckit-flow/analyze.md b/.wave/prompts/speckit-flow/analyze.md new file mode 100644 index 0000000..72110d5 --- /dev/null +++ b/.wave/prompts/speckit-flow/analyze.md @@ -0,0 +1,47 @@ +You are performing a cross-artifact consistency and quality analysis across the +specification, plan, and tasks before implementation begins. + +Feature context: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by a +previous step and is already checked out. + +## Instructions + +Follow the `/speckit.analyze` workflow: + +1. Find the feature directory and spec file path from the spec info artifact +2. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` + to find FEATURE_DIR and locate spec.md, plan.md, tasks.md +3. Load all three artifacts and build semantic models: + - Requirements inventory from spec.md + - User story/action inventory with acceptance criteria + - Task coverage mapping from tasks.md + - Constitution rule set from `.specify/memory/constitution.md` + +4. Run detection passes (limit to 50 findings total): + - **Duplication**: Near-duplicate requirements across artifacts + - **Ambiguity**: Vague adjectives, unresolved placeholders + - **Underspecification**: Requirements missing outcomes, tasks missing file paths + - **Constitution alignment**: Conflicts with MUST principles + - **Coverage gaps**: Requirements with no tasks, tasks with no requirements + - **Inconsistency**: Terminology drift, data entity mismatches, ordering contradictions + +5. Assign severity: CRITICAL / HIGH / MEDIUM / LOW +6. Produce a compact analysis report (do NOT modify files — read-only analysis) + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT use WebSearch — all information is in the spec artifacts +- This is a READ-ONLY analysis — do NOT modify any files + +## Output + +Produce a JSON analysis report matching the injected output schema. + +IMPORTANT: If CRITICAL issues are found, document them clearly but do NOT block +the pipeline. The implement step will handle resolution. diff --git a/.wave/prompts/speckit-flow/checklist.md b/.wave/prompts/speckit-flow/checklist.md new file mode 100644 index 0000000..ba66ee1 --- /dev/null +++ b/.wave/prompts/speckit-flow/checklist.md @@ -0,0 +1,40 @@ +You are generating quality checklists to validate requirement completeness before +implementation. + +Feature context: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by a +previous step and is already checked out. + +## Instructions + +Follow the `/speckit.checklist` workflow: + +1. Find the feature directory and spec file path from the spec info artifact +2. Run `.specify/scripts/bash/check-prerequisites.sh --json` to get FEATURE_DIR +3. Load feature context: spec.md, plan.md, tasks.md +4. Generate focused checklists as "unit tests for requirements": + - Each item tests the QUALITY of requirements, not the implementation + - Use format: `- [ ] CHK### - Question about requirement quality [Dimension]` + - Group by quality dimensions: Completeness, Clarity, Consistency, Coverage + +5. Create the following checklist files in `FEATURE_DIR/checklists/`: + - `review.md` — overall requirements quality validation + - Additional domain-specific checklists as warranted by the feature + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT use WebSearch — all information is in the spec artifacts + +## Checklist Anti-Patterns (AVOID) + +- WRONG: "Verify the button clicks correctly" (tests implementation) +- RIGHT: "Are interaction requirements defined for all clickable elements?" (tests requirements) + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/prompts/speckit-flow/clarify.md b/.wave/prompts/speckit-flow/clarify.md new file mode 100644 index 0000000..5d870ad --- /dev/null +++ b/.wave/prompts/speckit-flow/clarify.md @@ -0,0 +1,42 @@ +You are refining a feature specification by identifying and resolving ambiguities. + +Feature context: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by a +previous step and is already checked out. + +## Instructions + +Follow the `/speckit.clarify` workflow: + +1. Find the feature directory and spec file path from the spec info artifact +2. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` to confirm paths +3. Load the current spec and perform a focused ambiguity scan across: + - Functional scope and domain model + - Integration points and edge cases + - Terminology consistency +4. Generate up to 5 clarification questions (prioritized) +5. For each question, select the best option based on codebase context +6. Integrate each resolution directly into the spec file +7. Save the updated spec + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT use WebSearch — all clarifications should be resolved from codebase + context and the existing spec. The specify step already did the research. +- Keep the scope tight: only fix genuine ambiguities, don't redesign the spec + +## Non-Interactive Mode + +Since this runs in a pipeline, resolve all clarifications autonomously: +- Select the recommended option based on codebase patterns and existing architecture +- Document the rationale for each choice in the Clarifications section +- Err on the side of commonly-accepted industry standards + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/prompts/speckit-flow/create-pr.md b/.wave/prompts/speckit-flow/create-pr.md new file mode 100644 index 0000000..548356e --- /dev/null +++ b/.wave/prompts/speckit-flow/create-pr.md @@ -0,0 +1,60 @@ +You are creating a pull request for the implemented feature and requesting a review. + +Feature context: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by a +previous step and is already checked out. + +## Instructions + +1. Find the branch name and feature directory from the spec info artifact + +2. **Verify implementation**: Run `go test -race ./...` one final time to confirm + all tests pass. If tests fail, fix them before proceeding. + +3. **Stage changes**: Review all modified and new files with `git status` and `git diff`. + Stage relevant files — exclude any sensitive files (.env, credentials). + +4. **Commit**: Create a well-structured commit (or multiple commits if logical): + - Use conventional commit prefixes: `feat:`, `fix:`, `refactor:`, `test:`, `docs:` + - Write concise commit messages focused on the "why" + - Do NOT include Co-Authored-By or AI attribution lines + +5. **Push**: Push the branch to the remote repository: + ```bash + git push -u origin HEAD + ``` + +6. **Create Pull Request**: Use `{{ forge.cli_tool }} {{ forge.pr_command }} create` with a descriptive summary. Write the PR body to a temp file to avoid shell injection: + ```bash + cat > /tmp/pr-body.md <<'EOF' + ## Summary + <3-5 bullet points describing the changes> + + ## Spec + <link to spec file in specs/ directory> + + ## Test Plan + <how the changes were validated> + + ## Known Limitations + <any follow-up work needed> + EOF + {{ forge.cli_tool }} {{ forge.pr_command }} create --title '<concise title>' --body-file /tmp/pr-body.md + ``` + +7. **Request Copilot Review**: After the PR is created, request a review from Copilot: + ```bash + {{ forge.cli_tool }} {{ forge.pr_command }} edit --add-reviewer "copilot" + ``` + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/prompts/speckit-flow/implement.md b/.wave/prompts/speckit-flow/implement.md new file mode 100644 index 0000000..b70964f --- /dev/null +++ b/.wave/prompts/speckit-flow/implement.md @@ -0,0 +1,49 @@ +You are implementing a feature according to the specification, plan, and task breakdown. + +Feature context: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by a +previous step and is already checked out. + +## Instructions + +Follow the `/speckit.implement` workflow: + +1. Find the feature directory and spec file path from the spec info artifact +2. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` + to find FEATURE_DIR, load tasks.md, plan.md, and all available artifacts +3. Check checklists status — if any are incomplete, note them but proceed +4. Parse tasks.md and extract phase structure, dependencies, and execution order +5. Execute implementation phase-by-phase: + + **Setup first**: Initialize project structure, dependencies, configuration + **Tests before code**: Write tests for contracts and entities (TDD approach) + **Core development**: Implement models, services, CLI commands, endpoints + **Integration**: Database connections, middleware, logging, external services + **Polish**: Unit tests, performance optimization, documentation + +6. For each completed task, mark it as `[X]` in tasks.md +7. Run `go test -race ./...` after each phase to catch regressions early +8. Final validation: verify all tasks complete, tests pass, spec requirements met + +## Agent Usage + +Maximize parallelism with up to 6 Task agents for independent work: +- Agents 1-2: Setup and foundational tasks (Phase 1-2) +- Agents 3-4: Core implementation tasks (parallelizable [P] tasks) +- Agent 5: Test writing and validation +- Agent 6: Integration and polish tasks + +Coordinate agents to respect task dependencies: +- Sequential tasks (no [P] marker) must complete before dependents start +- Parallel tasks [P] affecting different files can run simultaneously +- Run test validation between phases + +## Error Handling + +- If a task fails, halt dependent tasks but continue independent ones +- Provide clear error context for debugging +- If tests fail, fix the issue before proceeding to the next phase diff --git a/.wave/prompts/speckit-flow/plan.md b/.wave/prompts/speckit-flow/plan.md new file mode 100644 index 0000000..8dc4801 --- /dev/null +++ b/.wave/prompts/speckit-flow/plan.md @@ -0,0 +1,41 @@ +You are creating an implementation plan for a feature specification. + +Feature context: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by a +previous step and is already checked out. + +## Instructions + +Follow the `/speckit.plan` workflow: + +1. Find the feature directory and spec file path from the spec info artifact +2. Run `.specify/scripts/bash/setup-plan.sh --json` to get FEATURE_SPEC, IMPL_PLAN, + SPECS_DIR, and BRANCH paths +3. Load the feature spec and `.specify/memory/constitution.md` +4. Follow the plan template phases: + + **Phase 0 — Outline & Research**: + - Extract unknowns from the spec (NEEDS CLARIFICATION markers, tech decisions) + - Research best practices for each technology choice + - Consolidate findings into `research.md` with Decision/Rationale/Alternatives + + **Phase 1 — Design & Contracts**: + - Extract entities from spec → write `data-model.md` + - Generate API contracts from functional requirements → `/contracts/` + - Run `.specify/scripts/bash/update-agent-context.sh claude` + +5. Evaluate constitution compliance at each phase gate +6. Stop after Phase 1 — report branch, plan path, and generated artifacts + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT use WebSearch — all information is in the spec and codebase + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/prompts/speckit-flow/specify.md b/.wave/prompts/speckit-flow/specify.md new file mode 100644 index 0000000..4b3acbc --- /dev/null +++ b/.wave/prompts/speckit-flow/specify.md @@ -0,0 +1,50 @@ +You are creating a feature specification for the following request: + +{{ input }} + +## Working Directory + +You are running in an **isolated git worktree** checked out at `main` (detached HEAD). +Your working directory IS the project root. All git operations here are isolated +from the main working tree and will not affect it. + +Use `create-new-feature.sh` to create the feature branch from this clean starting point. + +## Instructions + +Follow the `/speckit.specify` workflow to generate a complete feature specification: + +1. Generate a concise short name (2-4 words) for the feature branch +2. Check existing branches to determine the next available number: + ```bash + git fetch --all --prune + git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-' + git branch | grep -E '^[* ]*[0-9]+-' + ``` +3. Run the feature creation script: + ```bash + .specify/scripts/bash/create-new-feature.sh --json --number <N> --short-name "<name>" "{{ input }}" + ``` +4. Load `.specify/templates/spec-template.md` for the required structure +5. Write the specification to the SPEC_FILE returned by the script +6. Create the quality checklist at `FEATURE_DIR/checklists/requirements.md` +7. Run self-validation against the checklist (up to 3 iterations) + +## Agent Usage + +Use 1-3 Task agents to parallelize independent work: +- Agent 1: Analyze the codebase to understand existing patterns and architecture +- Agent 2: Research domain-specific best practices for the feature +- Agent 3: Draft specification sections in parallel + +## Quality Standards + +- Focus on WHAT and WHY, not HOW (no implementation details) +- Every requirement must be testable and unambiguous +- Maximum 3 `[NEEDS CLARIFICATION]` markers — make informed guesses for the rest +- Include user stories with acceptance criteria, data model, edge cases +- Success criteria must be measurable and technology-agnostic + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/prompts/speckit-flow/tasks.md b/.wave/prompts/speckit-flow/tasks.md new file mode 100644 index 0000000..45906f8 --- /dev/null +++ b/.wave/prompts/speckit-flow/tasks.md @@ -0,0 +1,52 @@ +You are generating an actionable, dependency-ordered task breakdown for implementation. + +Feature context: {{ input }} + +## Working Directory + +You are running in an **isolated git worktree** shared with previous pipeline steps. +Your working directory IS the project root. The feature branch was created by a +previous step and is already checked out. + +## Instructions + +Follow the `/speckit.tasks` workflow: + +1. Find the feature directory and spec file path from the spec info artifact +2. Run `.specify/scripts/bash/check-prerequisites.sh --json` to get FEATURE_DIR + and AVAILABLE_DOCS +3. Load from FEATURE_DIR: + - **Required**: plan.md (tech stack, structure), spec.md (user stories, priorities) + - **Optional**: data-model.md, contracts/, research.md, quickstart.md +4. Execute task generation: + - Extract user stories with priorities (P1, P2, P3) from spec.md + - Map entities and endpoints to user stories + - Generate tasks organized by user story + +5. Write `tasks.md` following the strict checklist format: + ``` + - [ ] [TaskID] [P?] [Story?] Description with file path + ``` + +6. Organize into phases: + - Phase 1: Setup (project initialization) + - Phase 2: Foundational (blocking prerequisites) + - Phase 3+: One phase per user story (priority order) + - Final: Polish & cross-cutting concerns + +## CONSTRAINTS + +- Do NOT spawn Task subagents — work directly in the main context +- Do NOT use WebSearch — all information is in the spec artifacts +- Keep the scope tight: generate tasks from existing artifacts only + +## Quality Requirements + +- Every task must have a unique ID (T001, T002...), description, and file path +- Mark parallelizable tasks with [P] +- Each user story phase must be independently testable +- Tasks must be specific enough for an LLM to complete without additional context + +## Output + +Produce a JSON status report matching the injected output schema. diff --git a/.wave/retros/audit-doc-20260328-173838-e10a.json b/.wave/retros/audit-doc-20260328-173838-e10a.json new file mode 100644 index 0000000..dcfb44f --- /dev/null +++ b/.wave/retros/audit-doc-20260328-173838-e10a.json @@ -0,0 +1,55 @@ +{ + "run_id": "audit-doc-20260328-173838-e10a", + "pipeline": "audit-doc", + "timestamp": "2026-03-28T17:44:29.469054927+01:00", + "quantitative": { + "total_duration_ms": 351467, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 121126, + "steps": [ + { + "name": "analyze-consistency", + "duration_ms": 138132, + "retries": 1, + "status": "success", + "adapter": "reviewer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 33399 + }, + { + "name": "compose-report", + "duration_ms": 34911, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 9682 + }, + { + "name": "publish", + "duration_ms": 28920, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 6852 + }, + { + "name": "scan-changes", + "duration_ms": 148949, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 71193 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/audit-dx-20260328-135948-267b.json b/.wave/retros/audit-dx-20260328-135948-267b.json new file mode 100644 index 0000000..3280e95 --- /dev/null +++ b/.wave/retros/audit-dx-20260328-135948-267b.json @@ -0,0 +1,25 @@ +{ + "run_id": "audit-dx-20260328-135948-267b", + "pipeline": "audit-dx", + "timestamp": "2026-03-28T14:05:51.419194117+01:00", + "quantitative": { + "total_duration_ms": 363418, + "total_steps": 1, + "success_count": 1, + "failure_count": 0, + "total_retries": 1, + "total_tokens": 62008, + "steps": [ + { + "name": "audit", + "duration_ms": 362563, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 62008 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/audit-pedagogy-20260328-175313-79a5.json b/.wave/retros/audit-pedagogy-20260328-175313-79a5.json new file mode 100644 index 0000000..b7bac78 --- /dev/null +++ b/.wave/retros/audit-pedagogy-20260328-175313-79a5.json @@ -0,0 +1,45 @@ +{ + "run_id": "audit-pedagogy-20260328-175313-79a5", + "pipeline": "audit-pedagogy", + "timestamp": "2026-03-28T18:28:08.744627015+01:00", + "quantitative": { + "total_duration_ms": 2095744, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 464512, + "steps": [ + { + "name": "pedagogy-audit", + "duration_ms": 462774, + "retries": 1, + "status": "success", + "adapter": "pedagogy-auditor", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 180801 + }, + { + "name": "improvement-plan", + "duration_ms": 468610, + "retries": 1, + "status": "success", + "adapter": "planner", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 147524 + }, + { + "name": "scan-lessons", + "duration_ms": 1164034, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 136187 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/audit-pedagogy-20260328-194243-c189.json b/.wave/retros/audit-pedagogy-20260328-194243-c189.json new file mode 100644 index 0000000..d8c44a8 --- /dev/null +++ b/.wave/retros/audit-pedagogy-20260328-194243-c189.json @@ -0,0 +1,45 @@ +{ + "run_id": "audit-pedagogy-20260328-194243-c189", + "pipeline": "audit-pedagogy", + "timestamp": "2026-03-28T20:06:22.605829579+01:00", + "quantitative": { + "total_duration_ms": 1419605, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 604110, + "steps": [ + { + "name": "scan-lessons", + "duration_ms": 414047, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 206487 + }, + { + "name": "pedagogy-audit", + "duration_ms": 488832, + "retries": 1, + "status": "success", + "adapter": "pedagogy-auditor", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 253543 + }, + { + "name": "improvement-plan", + "duration_ms": 515724, + "retries": 1, + "status": "success", + "adapter": "planner", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 144080 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/audit-pedagogy-20260328-213312-b6f8.json b/.wave/retros/audit-pedagogy-20260328-213312-b6f8.json new file mode 100644 index 0000000..53fb59e --- /dev/null +++ b/.wave/retros/audit-pedagogy-20260328-213312-b6f8.json @@ -0,0 +1,45 @@ +{ + "run_id": "audit-pedagogy-20260328-213312-b6f8", + "pipeline": "audit-pedagogy", + "timestamp": "2026-03-28T22:03:59.970266055+01:00", + "quantitative": { + "total_duration_ms": 1847969, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 637289, + "steps": [ + { + "name": "scan-lessons", + "duration_ms": 375421, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 204387 + }, + { + "name": "pedagogy-audit", + "duration_ms": 1008342, + "retries": 1, + "status": "success", + "adapter": "pedagogy-auditor", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 291369 + }, + { + "name": "improvement-plan", + "duration_ms": 463197, + "retries": 1, + "status": "success", + "adapter": "planner", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 141533 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/audit-pedagogy-20260329-001752-e77c.json b/.wave/retros/audit-pedagogy-20260329-001752-e77c.json new file mode 100644 index 0000000..fcf36b3 --- /dev/null +++ b/.wave/retros/audit-pedagogy-20260329-001752-e77c.json @@ -0,0 +1,45 @@ +{ + "run_id": "audit-pedagogy-20260329-001752-e77c", + "pipeline": "audit-pedagogy", + "timestamp": "2026-03-29T00:48:56.965668661+01:00", + "quantitative": { + "total_duration_ms": 1864965, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 554878, + "steps": [ + { + "name": "scan-lessons", + "duration_ms": 902237, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 232749 + }, + { + "name": "pedagogy-audit", + "duration_ms": 478644, + "retries": 1, + "status": "success", + "adapter": "pedagogy-auditor", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 172432 + }, + { + "name": "improvement-plan", + "duration_ms": 483592, + "retries": 1, + "status": "success", + "adapter": "planner", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 149697 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/audit-pedagogy-20260329-210909-22e5.json b/.wave/retros/audit-pedagogy-20260329-210909-22e5.json new file mode 100644 index 0000000..dfcbaab --- /dev/null +++ b/.wave/retros/audit-pedagogy-20260329-210909-22e5.json @@ -0,0 +1,45 @@ +{ + "run_id": "audit-pedagogy-20260329-210909-22e5", + "pipeline": "audit-pedagogy", + "timestamp": "2026-03-29T21:55:07.75273694+02:00", + "quantitative": { + "total_duration_ms": 2758751, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 700066, + "steps": [ + { + "name": "scan-lessons", + "duration_ms": 1018409, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 434348 + }, + { + "name": "pedagogy-audit", + "duration_ms": 1245983, + "retries": 1, + "status": "success", + "adapter": "pedagogy-auditor", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 159059 + }, + { + "name": "improvement-plan", + "duration_ms": 492559, + "retries": 1, + "status": "success", + "adapter": "planner", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 106659 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/audit-security-20260328-152711-3121.json b/.wave/retros/audit-security-20260328-152711-3121.json new file mode 100644 index 0000000..7d9db70 --- /dev/null +++ b/.wave/retros/audit-security-20260328-152711-3121.json @@ -0,0 +1,45 @@ +{ + "run_id": "audit-security-20260328-152711-3121", + "pipeline": "audit-security", + "timestamp": "2026-03-28T15:46:08.556131003+01:00", + "quantitative": { + "total_duration_ms": 1137555, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 146555, + "steps": [ + { + "name": "report", + "duration_ms": 70593, + "retries": 1, + "status": "success", + "adapter": "summarizer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 28353 + }, + { + "name": "scan", + "duration_ms": 727575, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 61193 + }, + { + "name": "deep-dive", + "duration_ms": 338698, + "retries": 1, + "status": "success", + "adapter": "auditor", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 57009 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/doc-onboard-20260328-161951-4e6a.json b/.wave/retros/doc-onboard-20260328-161951-4e6a.json new file mode 100644 index 0000000..5aa8650 --- /dev/null +++ b/.wave/retros/doc-onboard-20260328-161951-4e6a.json @@ -0,0 +1,35 @@ +{ + "run_id": "doc-onboard-20260328-161951-4e6a", + "pipeline": "doc-onboard", + "timestamp": "2026-03-28T16:24:37.293760508+01:00", + "quantitative": { + "total_duration_ms": 286293, + "total_steps": 2, + "success_count": 2, + "failure_count": 0, + "total_retries": 2, + "total_tokens": 191906, + "steps": [ + { + "name": "survey", + "duration_ms": 170644, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 160822 + }, + { + "name": "guide", + "duration_ms": 114447, + "retries": 1, + "status": "success", + "adapter": "philosopher", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 31084 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-feature-20260328-133515-7069.json b/.wave/retros/impl-feature-20260328-133515-7069.json new file mode 100644 index 0000000..54f1cc7 --- /dev/null +++ b/.wave/retros/impl-feature-20260328-133515-7069.json @@ -0,0 +1,55 @@ +{ + "run_id": "impl-feature-20260328-133515-7069", + "pipeline": "impl-feature", + "timestamp": "2026-03-28T14:06:12.053004643+01:00", + "quantitative": { + "total_duration_ms": 1857052, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 365559, + "steps": [ + { + "name": "explore", + "duration_ms": 212960, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 98098 + }, + { + "name": "plan", + "duration_ms": 334792, + "retries": 1, + "status": "success", + "adapter": "planner", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 38782 + }, + { + "name": "implement", + "duration_ms": 1210682, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 210266 + }, + { + "name": "publish", + "duration_ms": 92556, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 18413 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-feature-20260328-200101-ecae.json b/.wave/retros/impl-feature-20260328-200101-ecae.json new file mode 100644 index 0000000..f1033c5 --- /dev/null +++ b/.wave/retros/impl-feature-20260328-200101-ecae.json @@ -0,0 +1,55 @@ +{ + "run_id": "impl-feature-20260328-200101-ecae", + "pipeline": "impl-feature", + "timestamp": "2026-03-28T20:24:46.695641262+01:00", + "quantitative": { + "total_duration_ms": 1425694, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 365030, + "steps": [ + { + "name": "plan", + "duration_ms": 201070, + "retries": 1, + "status": "success", + "adapter": "planner", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 32991 + }, + { + "name": "implement", + "duration_ms": 955124, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 208294 + }, + { + "name": "publish", + "duration_ms": 76891, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 19425 + }, + { + "name": "explore", + "duration_ms": 184210, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 104320 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-hotfix-20260328-162004-c571.json b/.wave/retros/impl-hotfix-20260328-162004-c571.json new file mode 100644 index 0000000..b9d341f --- /dev/null +++ b/.wave/retros/impl-hotfix-20260328-162004-c571.json @@ -0,0 +1,45 @@ +{ + "run_id": "impl-hotfix-20260328-162004-c571", + "pipeline": "impl-hotfix", + "timestamp": "2026-03-28T16:28:21.857579727+01:00", + "quantitative": { + "total_duration_ms": 497856, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 129585, + "steps": [ + { + "name": "investigate", + "duration_ms": 103764, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 26483 + }, + { + "name": "fix", + "duration_ms": 161741, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 67906 + }, + { + "name": "verify", + "duration_ms": 217428, + "retries": 1, + "status": "success", + "adapter": "reviewer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 35196 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-improve-20260328-163932-ceeb.json b/.wave/retros/impl-improve-20260328-163932-ceeb.json new file mode 100644 index 0000000..c58918b --- /dev/null +++ b/.wave/retros/impl-improve-20260328-163932-ceeb.json @@ -0,0 +1,45 @@ +{ + "run_id": "impl-improve-20260328-163932-ceeb", + "pipeline": "impl-improve", + "timestamp": "2026-03-28T16:55:10.227500059+01:00", + "quantitative": { + "total_duration_ms": 938226, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 125769, + "steps": [ + { + "name": "assess", + "duration_ms": 226931, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 46777 + }, + { + "name": "implement", + "duration_ms": 152385, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 32312 + }, + { + "name": "verify", + "duration_ms": 543757, + "retries": 1, + "status": "success", + "adapter": "reviewer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 46680 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-issue-20260328-191418-09a9.json b/.wave/retros/impl-issue-20260328-191418-09a9.json new file mode 100644 index 0000000..abc100a --- /dev/null +++ b/.wave/retros/impl-issue-20260328-191418-09a9.json @@ -0,0 +1,55 @@ +{ + "run_id": "impl-issue-20260328-191418-09a9", + "pipeline": "impl-issue", + "timestamp": "2026-03-28T19:27:14.219250828+01:00", + "quantitative": { + "total_duration_ms": 776218, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 131026, + "steps": [ + { + "name": "plan", + "duration_ms": 146322, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 39838 + }, + { + "name": "implement", + "duration_ms": 474670, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 51492 + }, + { + "name": "create-pr", + "duration_ms": 76889, + "retries": 1, + "status": "success", + "adapter": "gitea-commenter", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 18776 + }, + { + "name": "fetch-assess", + "duration_ms": 69364, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 20920 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-issue-20260328-191418-09bb.json b/.wave/retros/impl-issue-20260328-191418-09bb.json new file mode 100644 index 0000000..c4b3c50 --- /dev/null +++ b/.wave/retros/impl-issue-20260328-191418-09bb.json @@ -0,0 +1,55 @@ +{ + "run_id": "impl-issue-20260328-191418-09bb", + "pipeline": "impl-issue", + "timestamp": "2026-03-28T19:42:21.134708056+01:00", + "quantitative": { + "total_duration_ms": 1683133, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 247274, + "steps": [ + { + "name": "fetch-assess", + "duration_ms": 68146, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 19319 + }, + { + "name": "plan", + "duration_ms": 365179, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 70490 + }, + { + "name": "implement", + "duration_ms": 1148553, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 135800 + }, + { + "name": "create-pr", + "duration_ms": 93134, + "retries": 1, + "status": "success", + "adapter": "gitea-commenter", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 21665 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-issue-20260328-200048-e45d.json b/.wave/retros/impl-issue-20260328-200048-e45d.json new file mode 100644 index 0000000..372d103 --- /dev/null +++ b/.wave/retros/impl-issue-20260328-200048-e45d.json @@ -0,0 +1,55 @@ +{ + "run_id": "impl-issue-20260328-200048-e45d", + "pipeline": "impl-issue", + "timestamp": "2026-03-28T20:11:43.007034719+01:00", + "quantitative": { + "total_duration_ms": 655006, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 177117, + "steps": [ + { + "name": "fetch-assess", + "duration_ms": 68004, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 17506 + }, + { + "name": "plan", + "duration_ms": 178286, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 47713 + }, + { + "name": "implement", + "duration_ms": 322386, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 94155 + }, + { + "name": "create-pr", + "duration_ms": 78469, + "retries": 1, + "status": "success", + "adapter": "gitea-commenter", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 17743 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-issue-20260328-234704-0b6f.json b/.wave/retros/impl-issue-20260328-234704-0b6f.json new file mode 100644 index 0000000..eb273b4 --- /dev/null +++ b/.wave/retros/impl-issue-20260328-234704-0b6f.json @@ -0,0 +1,55 @@ +{ + "run_id": "impl-issue-20260328-234704-0b6f", + "pipeline": "impl-issue", + "timestamp": "2026-03-28T23:58:39.344905826+01:00", + "quantitative": { + "total_duration_ms": 695344, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 223340, + "steps": [ + { + "name": "fetch-assess", + "duration_ms": 48293, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 17405 + }, + { + "name": "plan", + "duration_ms": 208336, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 82514 + }, + { + "name": "implement", + "duration_ms": 344205, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 103629 + }, + { + "name": "create-pr", + "duration_ms": 86207, + "retries": 1, + "status": "success", + "adapter": "gitea-commenter", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 19792 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/impl-issue-20260329-232103-73b5.json b/.wave/retros/impl-issue-20260329-232103-73b5.json new file mode 100644 index 0000000..d84462f --- /dev/null +++ b/.wave/retros/impl-issue-20260329-232103-73b5.json @@ -0,0 +1,55 @@ +{ + "run_id": "impl-issue-20260329-232103-73b5", + "pipeline": "impl-issue", + "timestamp": "2026-03-29T23:29:44.126928823+02:00", + "quantitative": { + "total_duration_ms": 521125, + "total_steps": 4, + "success_count": 4, + "failure_count": 0, + "total_retries": 4, + "total_tokens": 112974, + "steps": [ + { + "name": "fetch-assess", + "duration_ms": 40512, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 22697 + }, + { + "name": "plan", + "duration_ms": 175860, + "retries": 1, + "status": "success", + "adapter": "implementer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 27127 + }, + { + "name": "implement", + "duration_ms": 199165, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 44718 + }, + { + "name": "create-pr", + "duration_ms": 96796, + "retries": 1, + "status": "success", + "adapter": "gitea-commenter", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 18432 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/ops-hello-world-20260328-133020-fbb0.json b/.wave/retros/ops-hello-world-20260328-133020-fbb0.json new file mode 100644 index 0000000..602158c --- /dev/null +++ b/.wave/retros/ops-hello-world-20260328-133020-fbb0.json @@ -0,0 +1,35 @@ +{ + "run_id": "ops-hello-world-20260328-133020-fbb0", + "pipeline": "ops-hello-world", + "timestamp": "2026-03-28T13:31:13.737091172+01:00", + "quantitative": { + "total_duration_ms": 53736, + "total_steps": 2, + "success_count": 2, + "failure_count": 0, + "total_retries": 2, + "total_tokens": 19820, + "steps": [ + { + "name": "greet", + "duration_ms": 21078, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 10025 + }, + { + "name": "verify", + "duration_ms": 31639, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 9795 + } + ] + } +} \ No newline at end of file diff --git a/.wave/retros/test-gen-20260328-152719-741b.json b/.wave/retros/test-gen-20260328-152719-741b.json new file mode 100644 index 0000000..393e1e8 --- /dev/null +++ b/.wave/retros/test-gen-20260328-152719-741b.json @@ -0,0 +1,45 @@ +{ + "run_id": "test-gen-20260328-152719-741b", + "pipeline": "test-gen", + "timestamp": "2026-03-28T16:03:02.238631962+01:00", + "quantitative": { + "total_duration_ms": 2143237, + "total_steps": 3, + "success_count": 3, + "failure_count": 0, + "total_retries": 3, + "total_tokens": 362054, + "steps": [ + { + "name": "analyze-coverage", + "duration_ms": 231684, + "retries": 1, + "status": "success", + "adapter": "navigator", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 49840 + }, + { + "name": "generate-tests", + "duration_ms": 693552, + "retries": 1, + "status": "success", + "adapter": "craftsman", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 173292 + }, + { + "name": "verify-coverage", + "duration_ms": 1206945, + "retries": 1, + "status": "success", + "adapter": "reviewer", + "exit_code": 0, + "files_changed": 0, + "tokens_used": 138922 + } + ] + } +} \ No newline at end of file diff --git a/.wave/state.db b/.wave/state.db new file mode 100644 index 0000000000000000000000000000000000000000..2585f185133869b66039fafbf6fe23a981d3e0c6 GIT binary patch literal 1368064 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 literal 0 HcmV?d00001