opencode-mad 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/orchestrator.md +64 -0
- package/install.js +1 -1
- package/package.json +1 -1
- package/plugins/mad-plugin.ts +264 -1
- package/skills/mad-workflow/SKILL.md +8 -0
package/agents/orchestrator.md
CHANGED
|
@@ -516,6 +516,69 @@ Wait for all testers to complete. Only proceed to merge if ALL are marked done.
|
|
|
516
516
|
|
|
517
517
|
---
|
|
518
518
|
|
|
519
|
+
## Phase 5.5: Final Global Check
|
|
520
|
+
|
|
521
|
+
**IMPORTANT: Run this after all merges are complete!**
|
|
522
|
+
|
|
523
|
+
Use `mad_final_check` to verify the entire project's build and lint status:
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
mad_final_check()
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
This will:
|
|
530
|
+
1. Run all configured build/lint commands (npm run build, npm run lint, etc.)
|
|
531
|
+
2. Compare any errors against files modified during the session
|
|
532
|
+
3. Categorize errors as "session errors" or "pre-existing errors"
|
|
533
|
+
|
|
534
|
+
### Handling Results
|
|
535
|
+
|
|
536
|
+
#### If session errors are found:
|
|
537
|
+
These are bugs introduced by the MAD session. Create a fix worktree:
|
|
538
|
+
|
|
539
|
+
```
|
|
540
|
+
mad_worktree_create(
|
|
541
|
+
branch: "fix-session-errors",
|
|
542
|
+
task: "Fix build/lint errors introduced during session:
|
|
543
|
+
[list of errors]
|
|
544
|
+
|
|
545
|
+
YOU OWN ALL FILES in this worktree."
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
Task(
|
|
549
|
+
subagent_type: "mad-fixer",
|
|
550
|
+
description: "Fix session errors",
|
|
551
|
+
prompt: "Work in worktree 'fix-session-errors'. Fix the build/lint errors, commit, and call mad_done."
|
|
552
|
+
)
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
#### If only pre-existing errors are found:
|
|
556
|
+
These are NOT caused by the session. Inform the user:
|
|
557
|
+
|
|
558
|
+
```
|
|
559
|
+
"Your session completed successfully! No new errors were introduced.
|
|
560
|
+
|
|
561
|
+
However, I found [N] pre-existing build/lint errors that were already in the codebase.
|
|
562
|
+
Would you like me to fix them? (Note: these are not caused by our changes today)"
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
If the user says yes, create a worktree to fix them:
|
|
566
|
+
|
|
567
|
+
```
|
|
568
|
+
mad_worktree_create(
|
|
569
|
+
branch: "fix-preexisting-errors",
|
|
570
|
+
task: "Fix pre-existing build/lint errors (NOT from this session):
|
|
571
|
+
[list of errors]
|
|
572
|
+
|
|
573
|
+
These errors existed before the MAD session started."
|
|
574
|
+
)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
#### If no errors:
|
|
578
|
+
Celebrate! The project is clean.
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
519
582
|
## Available Tools
|
|
520
583
|
|
|
521
584
|
| Tool | Description |
|
|
@@ -530,6 +593,7 @@ Wait for all testers to complete. Only proceed to merge if ALL are marked done.
|
|
|
530
593
|
| `mad_blocked` | Mark task blocked |
|
|
531
594
|
| `mad_read_task` | Read task description |
|
|
532
595
|
| `mad_log` | Log events for debugging |
|
|
596
|
+
| `mad_final_check` | Run global build/lint and categorize errors |
|
|
533
597
|
|
|
534
598
|
## Subagents
|
|
535
599
|
|
package/install.js
CHANGED
package/package.json
CHANGED
package/plugins/mad-plugin.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { execSync } from "child_process"
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// Current version of opencode-mad
|
|
16
|
-
const CURRENT_VERSION = "0.3.
|
|
16
|
+
const CURRENT_VERSION = "0.3.7"
|
|
17
17
|
|
|
18
18
|
// Update notification state (shown only once per session)
|
|
19
19
|
let updateNotificationShown = false
|
|
@@ -728,6 +728,269 @@ Latest version: ${updateInfo.latest}`
|
|
|
728
728
|
}
|
|
729
729
|
},
|
|
730
730
|
}),
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Final check - run global build/lint and categorize errors
|
|
734
|
+
*/
|
|
735
|
+
mad_final_check: tool({
|
|
736
|
+
description: `Run global build/lint checks on the main project after all merges.
|
|
737
|
+
Compares errors against files modified during the MAD session to distinguish:
|
|
738
|
+
- Session errors: caused by changes made during this session
|
|
739
|
+
- Pre-existing errors: already present before the session started
|
|
740
|
+
|
|
741
|
+
Use this at the end of the MAD workflow to ensure code quality.`,
|
|
742
|
+
args: {
|
|
743
|
+
baseCommit: tool.schema.string().optional().describe("The commit SHA from before the MAD session started. If not provided, will try to detect from reflog."),
|
|
744
|
+
},
|
|
745
|
+
async execute(args, context) {
|
|
746
|
+
try {
|
|
747
|
+
const gitRoot = getGitRoot()
|
|
748
|
+
|
|
749
|
+
// 1. Determine base commit for comparison
|
|
750
|
+
let baseCommit = args.baseCommit
|
|
751
|
+
if (!baseCommit) {
|
|
752
|
+
// Try to find the commit before MAD session started (look for last commit before worktrees were created)
|
|
753
|
+
const reflogResult = runCommand('git reflog --format="%H %gs" -n 50', gitRoot)
|
|
754
|
+
if (reflogResult.success) {
|
|
755
|
+
// Find first commit that's not a merge from a MAD branch
|
|
756
|
+
const lines = reflogResult.output.split('\n')
|
|
757
|
+
for (const line of lines) {
|
|
758
|
+
if (!line.includes('merge') || (!line.includes('feat-') && !line.includes('fix-'))) {
|
|
759
|
+
baseCommit = line.split(' ')[0]
|
|
760
|
+
break
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (!baseCommit) {
|
|
765
|
+
baseCommit = 'HEAD~10' // Fallback
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// 2. Get list of files modified during session
|
|
770
|
+
const diffResult = runCommand(`git diff ${baseCommit}..HEAD --name-only`, gitRoot)
|
|
771
|
+
const modifiedFiles = diffResult.success
|
|
772
|
+
? diffResult.output.split('\n').filter(f => f.trim()).map(f => f.trim())
|
|
773
|
+
: []
|
|
774
|
+
|
|
775
|
+
let report = getUpdateNotification() + `# Final Project Check\n\n`
|
|
776
|
+
report += `📊 **Session Summary:**\n`
|
|
777
|
+
report += `- Base commit: \`${baseCommit.substring(0, 8)}\`\n`
|
|
778
|
+
report += `- Files modified: ${modifiedFiles.length}\n\n`
|
|
779
|
+
|
|
780
|
+
// 3. Detect project type and run checks
|
|
781
|
+
const packageJson = join(gitRoot, "package.json")
|
|
782
|
+
const goMod = join(gitRoot, "go.mod")
|
|
783
|
+
const cargoToml = join(gitRoot, "Cargo.toml")
|
|
784
|
+
const pyProject = join(gitRoot, "pyproject.toml")
|
|
785
|
+
|
|
786
|
+
interface CheckError {
|
|
787
|
+
file: string
|
|
788
|
+
line?: number
|
|
789
|
+
message: string
|
|
790
|
+
isSessionError: boolean
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const allErrors: CheckError[] = []
|
|
794
|
+
let checksRun = 0
|
|
795
|
+
|
|
796
|
+
// Helper to parse errors and categorize them
|
|
797
|
+
const parseAndCategorize = (output: string, checkName: string) => {
|
|
798
|
+
// Common patterns for file:line:message
|
|
799
|
+
const patterns = [
|
|
800
|
+
/^(.+?):(\d+):\d*:?\s*(.+)$/gm, // file:line:col: message
|
|
801
|
+
/^(.+?)\((\d+),\d+\):\s*(.+)$/gm, // file(line,col): message (TypeScript)
|
|
802
|
+
/^\s*(.+?):(\d+)\s+(.+)$/gm, // file:line message
|
|
803
|
+
]
|
|
804
|
+
|
|
805
|
+
for (const pattern of patterns) {
|
|
806
|
+
let match
|
|
807
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
808
|
+
const file = match[1].trim().replace(/\\/g, '/')
|
|
809
|
+
const line = parseInt(match[2])
|
|
810
|
+
const message = match[3].trim()
|
|
811
|
+
|
|
812
|
+
// Check if this file was modified during session
|
|
813
|
+
const isSessionError = modifiedFiles.some(mf =>
|
|
814
|
+
file.endsWith(mf) || mf.endsWith(file) || file.includes(mf) || mf.includes(file)
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
allErrors.push({ file, line, message, isSessionError })
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Run checks based on project type
|
|
823
|
+
if (existsSync(packageJson)) {
|
|
824
|
+
const pkg = JSON.parse(readFileSync(packageJson, "utf-8"))
|
|
825
|
+
|
|
826
|
+
if (pkg.scripts?.lint) {
|
|
827
|
+
checksRun++
|
|
828
|
+
report += `## 🔍 Lint Check\n`
|
|
829
|
+
const lintResult = runCommand("npm run lint 2>&1", gitRoot)
|
|
830
|
+
if (lintResult.success) {
|
|
831
|
+
report += `✅ Lint passed\n\n`
|
|
832
|
+
} else {
|
|
833
|
+
report += `❌ Lint failed\n`
|
|
834
|
+
parseAndCategorize(lintResult.error || lintResult.output, "lint")
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (pkg.scripts?.build) {
|
|
839
|
+
checksRun++
|
|
840
|
+
report += `## 🔨 Build Check\n`
|
|
841
|
+
const buildResult = runCommand("npm run build 2>&1", gitRoot)
|
|
842
|
+
if (buildResult.success) {
|
|
843
|
+
report += `✅ Build passed\n\n`
|
|
844
|
+
} else {
|
|
845
|
+
report += `❌ Build failed\n`
|
|
846
|
+
parseAndCategorize(buildResult.error || buildResult.output, "build")
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (pkg.scripts?.typecheck || pkg.scripts?.["type-check"]) {
|
|
851
|
+
checksRun++
|
|
852
|
+
const cmd = pkg.scripts?.typecheck ? "npm run typecheck" : "npm run type-check"
|
|
853
|
+
report += `## 📝 TypeCheck\n`
|
|
854
|
+
const tcResult = runCommand(`${cmd} 2>&1`, gitRoot)
|
|
855
|
+
if (tcResult.success) {
|
|
856
|
+
report += `✅ TypeCheck passed\n\n`
|
|
857
|
+
} else {
|
|
858
|
+
report += `❌ TypeCheck failed\n`
|
|
859
|
+
parseAndCategorize(tcResult.error || tcResult.output, "typecheck")
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (existsSync(goMod)) {
|
|
865
|
+
checksRun++
|
|
866
|
+
report += `## 🔨 Go Build\n`
|
|
867
|
+
const goBuild = runCommand("go build ./... 2>&1", gitRoot)
|
|
868
|
+
if (goBuild.success) {
|
|
869
|
+
report += `✅ Go build passed\n\n`
|
|
870
|
+
} else {
|
|
871
|
+
parseAndCategorize(goBuild.error || goBuild.output, "go build")
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
checksRun++
|
|
875
|
+
report += `## 🔍 Go Vet\n`
|
|
876
|
+
const goVet = runCommand("go vet ./... 2>&1", gitRoot)
|
|
877
|
+
if (goVet.success) {
|
|
878
|
+
report += `✅ Go vet passed\n\n`
|
|
879
|
+
} else {
|
|
880
|
+
parseAndCategorize(goVet.error || goVet.output, "go vet")
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (existsSync(cargoToml)) {
|
|
885
|
+
checksRun++
|
|
886
|
+
report += `## 🔨 Cargo Check\n`
|
|
887
|
+
const cargoCheck = runCommand("cargo check 2>&1", gitRoot)
|
|
888
|
+
if (cargoCheck.success) {
|
|
889
|
+
report += `✅ Cargo check passed\n\n`
|
|
890
|
+
} else {
|
|
891
|
+
parseAndCategorize(cargoCheck.error || cargoCheck.output, "cargo")
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
checksRun++
|
|
895
|
+
report += `## 🔍 Cargo Clippy\n`
|
|
896
|
+
const clippy = runCommand("cargo clippy 2>&1", gitRoot)
|
|
897
|
+
if (clippy.success) {
|
|
898
|
+
report += `✅ Clippy passed\n\n`
|
|
899
|
+
} else {
|
|
900
|
+
parseAndCategorize(clippy.error || clippy.output, "clippy")
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (existsSync(pyProject)) {
|
|
905
|
+
checksRun++
|
|
906
|
+
report += `## 🔍 Python Lint (ruff/flake8)\n`
|
|
907
|
+
let pyLint = runCommand("ruff check . 2>&1", gitRoot)
|
|
908
|
+
if (!pyLint.success && pyLint.error?.includes("not found")) {
|
|
909
|
+
pyLint = runCommand("flake8 . 2>&1", gitRoot)
|
|
910
|
+
}
|
|
911
|
+
if (pyLint.success) {
|
|
912
|
+
report += `✅ Python lint passed\n\n`
|
|
913
|
+
} else {
|
|
914
|
+
parseAndCategorize(pyLint.error || pyLint.output, "python lint")
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
checksRun++
|
|
918
|
+
report += `## 📝 Python Type Check (mypy)\n`
|
|
919
|
+
const mypy = runCommand("mypy . 2>&1", gitRoot)
|
|
920
|
+
if (mypy.success) {
|
|
921
|
+
report += `✅ Mypy passed\n\n`
|
|
922
|
+
} else {
|
|
923
|
+
parseAndCategorize(mypy.error || mypy.output, "mypy")
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (checksRun === 0) {
|
|
928
|
+
report += `⚠️ No build/lint scripts detected in this project.\n`
|
|
929
|
+
report += `Supported: package.json (npm), go.mod, Cargo.toml, pyproject.toml\n`
|
|
930
|
+
logEvent("warn", "mad_final_check: no checks detected", { gitRoot })
|
|
931
|
+
return report
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// 4. Categorize and report errors
|
|
935
|
+
const sessionErrors = allErrors.filter(e => e.isSessionError)
|
|
936
|
+
const preExistingErrors = allErrors.filter(e => !e.isSessionError)
|
|
937
|
+
|
|
938
|
+
report += `---\n\n## 📋 Error Summary\n\n`
|
|
939
|
+
|
|
940
|
+
if (allErrors.length === 0) {
|
|
941
|
+
report += `🎉 **All checks passed!** No errors detected.\n`
|
|
942
|
+
logEvent("info", "mad_final_check: all checks passed", { checksRun })
|
|
943
|
+
return report
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (sessionErrors.length > 0) {
|
|
947
|
+
report += `### ❌ Session Errors (${sessionErrors.length})\n`
|
|
948
|
+
report += `*These errors are in files modified during this session:*\n\n`
|
|
949
|
+
for (const err of sessionErrors.slice(0, 10)) {
|
|
950
|
+
report += `- \`${err.file}${err.line ? `:${err.line}` : ''}\`: ${err.message.substring(0, 100)}\n`
|
|
951
|
+
}
|
|
952
|
+
if (sessionErrors.length > 10) {
|
|
953
|
+
report += `- ... and ${sessionErrors.length - 10} more\n`
|
|
954
|
+
}
|
|
955
|
+
report += `\n`
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (preExistingErrors.length > 0) {
|
|
959
|
+
report += `### ⚠️ Pre-existing Errors (${preExistingErrors.length})\n`
|
|
960
|
+
report += `*These errors are NOT caused by this session - they existed before:*\n\n`
|
|
961
|
+
for (const err of preExistingErrors.slice(0, 10)) {
|
|
962
|
+
report += `- \`${err.file}${err.line ? `:${err.line}` : ''}\`: ${err.message.substring(0, 100)}\n`
|
|
963
|
+
}
|
|
964
|
+
if (preExistingErrors.length > 10) {
|
|
965
|
+
report += `- ... and ${preExistingErrors.length - 10} more\n`
|
|
966
|
+
}
|
|
967
|
+
report += `\n`
|
|
968
|
+
report += `💡 **These pre-existing errors are not your fault!**\n`
|
|
969
|
+
report += `Would you like me to create a worktree to fix them? Just say "fix pre-existing errors".\n`
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// 5. Final verdict
|
|
973
|
+
report += `\n---\n\n`
|
|
974
|
+
if (sessionErrors.length > 0) {
|
|
975
|
+
report += `⚠️ **Action required:** Fix the ${sessionErrors.length} session error(s) before considering this session complete.\n`
|
|
976
|
+
} else if (preExistingErrors.length > 0) {
|
|
977
|
+
report += `✅ **Session successful!** Your changes introduced no new errors.\n`
|
|
978
|
+
report += `The ${preExistingErrors.length} pre-existing error(s) can be fixed separately if desired.\n`
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
logEvent("info", "mad_final_check completed", {
|
|
982
|
+
checksRun,
|
|
983
|
+
sessionErrors: sessionErrors.length,
|
|
984
|
+
preExistingErrors: preExistingErrors.length
|
|
985
|
+
})
|
|
986
|
+
|
|
987
|
+
return report
|
|
988
|
+
} catch (e: any) {
|
|
989
|
+
logEvent("error", "mad_final_check exception", { error: e.message, stack: e.stack })
|
|
990
|
+
return getUpdateNotification() + `❌ Error running final check: ${e.message}`
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
}),
|
|
731
994
|
},
|
|
732
995
|
|
|
733
996
|
// Event hooks
|
|
@@ -103,6 +103,13 @@ Remove finished worktrees:
|
|
|
103
103
|
mad_cleanup(worktree: "feat-feature-name")
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
### 8. Final Check
|
|
107
|
+
Verify global project health:
|
|
108
|
+
```
|
|
109
|
+
mad_final_check()
|
|
110
|
+
```
|
|
111
|
+
This distinguishes session errors from pre-existing issues.
|
|
112
|
+
|
|
106
113
|
## Best Practices
|
|
107
114
|
|
|
108
115
|
1. **Keep subtasks focused** - Each should be completable in one session
|
|
@@ -123,6 +130,7 @@ mad_cleanup(worktree: "feat-feature-name")
|
|
|
123
130
|
| `mad_done` | Mark task complete |
|
|
124
131
|
| `mad_blocked` | Mark task blocked |
|
|
125
132
|
| `mad_read_task` | Read task description |
|
|
133
|
+
| `mad_final_check` | Run global build/lint and categorize errors |
|
|
126
134
|
|
|
127
135
|
## Example
|
|
128
136
|
|