@ddtcorex/dsh-maestro-review 0.1.0 → 0.1.2
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/lib/config-store.d.ts +2 -0
- package/lib/config-store.d.ts.map +1 -1
- package/lib/config-store.js.map +1 -1
- package/lib/events.d.ts.map +1 -1
- package/lib/events.js.map +1 -1
- package/lib/git-worktree-tool.d.ts +11 -0
- package/lib/git-worktree-tool.d.ts.map +1 -0
- package/lib/git-worktree-tool.js +162 -0
- package/lib/git-worktree-tool.js.map +1 -0
- package/lib/gitlab-client.d.ts.map +1 -1
- package/lib/gitlab-client.js.map +1 -1
- package/lib/gitlab-webhook.d.ts.map +1 -1
- package/lib/gitlab-webhook.js.map +1 -1
- package/lib/govard-audit-lint-tool.d.ts +14 -0
- package/lib/govard-audit-lint-tool.d.ts.map +1 -0
- package/lib/govard-audit-lint-tool.js +177 -0
- package/lib/govard-audit-lint-tool.js.map +1 -0
- package/lib/govard-tool.d.ts.map +1 -1
- package/lib/govard-tool.js.map +1 -1
- package/lib/hyva-csp-scan-tool.d.ts +11 -0
- package/lib/hyva-csp-scan-tool.d.ts.map +1 -0
- package/lib/hyva-csp-scan-tool.js +193 -0
- package/lib/hyva-csp-scan-tool.js.map +1 -0
- package/lib/hyva-theme-inspect-tool.d.ts +10 -0
- package/lib/hyva-theme-inspect-tool.d.ts.map +1 -0
- package/lib/hyva-theme-inspect-tool.js +299 -0
- package/lib/hyva-theme-inspect-tool.js.map +1 -0
- package/lib/incremental.d.ts +28 -0
- package/lib/incremental.d.ts.map +1 -0
- package/lib/incremental.js +60 -0
- package/lib/incremental.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/layout-xml-tool.d.ts +11 -0
- package/lib/layout-xml-tool.d.ts.map +1 -0
- package/lib/layout-xml-tool.js +240 -0
- package/lib/layout-xml-tool.js.map +1 -0
- package/lib/module-check-tool.d.ts +11 -0
- package/lib/module-check-tool.d.ts.map +1 -0
- package/lib/module-check-tool.js +304 -0
- package/lib/module-check-tool.js.map +1 -0
- package/lib/notify.d.ts.map +1 -1
- package/lib/notify.js.map +1 -1
- package/lib/orchestrator.d.ts +2 -0
- package/lib/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator.js +220 -88
- package/lib/orchestrator.js.map +1 -1
- package/lib/perf-log-stats-tool.d.ts +13 -0
- package/lib/perf-log-stats-tool.d.ts.map +1 -0
- package/lib/perf-log-stats-tool.js +245 -0
- package/lib/perf-log-stats-tool.js.map +1 -0
- package/lib/phtml-escape-scan-tool.d.ts +11 -0
- package/lib/phtml-escape-scan-tool.d.ts.map +1 -0
- package/lib/phtml-escape-scan-tool.js +170 -0
- package/lib/phtml-escape-scan-tool.js.map +1 -0
- package/lib/plan-track-tool.d.ts +11 -0
- package/lib/plan-track-tool.d.ts.map +1 -0
- package/lib/plan-track-tool.js +143 -0
- package/lib/plan-track-tool.js.map +1 -0
- package/lib/providers/github.stub.d.ts.map +1 -1
- package/lib/providers/github.stub.js.map +1 -1
- package/lib/providers/gitlab.d.ts.map +1 -1
- package/lib/providers/gitlab.js.map +1 -1
- package/lib/providers/interface.d.ts.map +1 -1
- package/lib/providers/interface.js.map +1 -1
- package/lib/review-findings-tool.d.ts.map +1 -1
- package/lib/review-findings-tool.js.map +1 -1
- package/lib/review-history.d.ts +4 -0
- package/lib/review-history.d.ts.map +1 -1
- package/lib/review-history.js +23 -1
- package/lib/review-history.js.map +1 -1
- package/lib/review-intake.d.ts.map +1 -1
- package/lib/review-intake.js.map +1 -1
- package/lib/review-signals.d.ts.map +1 -1
- package/lib/review-signals.js.map +1 -1
- package/lib/scope-split-tool.d.ts +42 -0
- package/lib/scope-split-tool.d.ts.map +1 -0
- package/lib/scope-split-tool.js +109 -0
- package/lib/scope-split-tool.js.map +1 -0
- package/lib/search-tool.d.ts +15 -0
- package/lib/search-tool.d.ts.map +1 -0
- package/lib/search-tool.js +144 -0
- package/lib/search-tool.js.map +1 -0
- package/lib/secure-compare.d.ts.map +1 -1
- package/lib/secure-compare.js.map +1 -1
- package/lib/settings-rpc.d.ts.map +1 -1
- package/lib/settings-rpc.js +47 -8
- package/lib/settings-rpc.js.map +1 -1
- package/lib/skills-tool.d.ts +1 -1
- package/lib/skills-tool.d.ts.map +1 -1
- package/lib/skills-tool.js +5 -1
- package/lib/skills-tool.js.map +1 -1
- package/lib/tdd-evidence-tool.d.ts +11 -0
- package/lib/tdd-evidence-tool.d.ts.map +1 -0
- package/lib/tdd-evidence-tool.js +114 -0
- package/lib/tdd-evidence-tool.js.map +1 -0
- package/lib/tool-policy.d.ts +20 -0
- package/lib/tool-policy.d.ts.map +1 -0
- package/lib/tool-policy.js +42 -0
- package/lib/tool-policy.js.map +1 -0
- package/lib/workspace-tool.d.ts.map +1 -1
- package/lib/workspace-tool.js +15 -9
- package/lib/workspace-tool.js.map +1 -1
- package/package.json +4 -4
- package/src/{augment.d.ts → host/augment.d.ts} +5 -2
- package/src/{config-store.ts → host/config-store.ts} +2 -0
- package/src/host/git-worktree-tool.ts +130 -0
- package/src/host/govard-audit-lint-tool.ts +153 -0
- package/src/host/hyva-csp-scan-tool.ts +165 -0
- package/src/host/hyva-theme-inspect-tool.ts +258 -0
- package/src/host/incremental.ts +84 -0
- package/src/host/layout-xml-tool.ts +191 -0
- package/src/host/module-check-tool.ts +188 -0
- package/src/{orchestrator.ts → host/orchestrator.ts} +205 -84
- package/src/host/perf-log-stats-tool.ts +224 -0
- package/src/host/phtml-escape-scan-tool.ts +117 -0
- package/src/host/plan-track-tool.ts +118 -0
- package/src/{review-history.ts → host/review-history.ts} +27 -1
- package/src/host/scope-split-tool.ts +110 -0
- package/src/host/search-tool.ts +153 -0
- package/src/{settings-rpc.ts → host/settings-rpc.ts} +54 -11
- package/src/{skills-tool.ts → host/skills-tool.ts} +5 -1
- package/src/host/tdd-evidence-tool.ts +85 -0
- package/src/host/tool-policy.ts +49 -0
- package/src/{workspace-tool.ts → host/workspace-tool.ts} +27 -9
- package/lib/pin-store.d.ts +0 -5
- package/lib/pin-store.d.ts.map +0 -1
- package/lib/pin-store.js +0 -5
- package/lib/pin-store.js.map +0 -1
- package/src/pin-store.ts +0 -4
- /package/src/{events.ts → host/events.ts} +0 -0
- /package/src/{gitlab-client.ts → host/gitlab-client.ts} +0 -0
- /package/src/{gitlab-webhook.ts → host/gitlab-webhook.ts} +0 -0
- /package/src/{govard-tool.ts → host/govard-tool.ts} +0 -0
- /package/src/{index.ts → host/index.ts} +0 -0
- /package/src/{notify.ts → host/notify.ts} +0 -0
- /package/src/{providers → host/providers}/github.stub.ts +0 -0
- /package/src/{providers → host/providers}/gitlab.ts +0 -0
- /package/src/{providers → host/providers}/interface.ts +0 -0
- /package/src/{review-findings-tool.ts → host/review-findings-tool.ts} +0 -0
- /package/src/{review-intake.ts → host/review-intake.ts} +0 -0
- /package/src/{review-signals.ts → host/review-signals.ts} +0 -0
- /package/src/{secure-compare.ts → host/secure-compare.ts} +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { join, resolve, sep, basename, dirname } from 'node:path'
|
|
2
|
+
import { readFile, readdir, realpath, stat } from 'node:fs/promises'
|
|
3
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
4
|
+
import z from '@deepseek-ai/schemastery'
|
|
5
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
6
|
+
|
|
7
|
+
export const name='maestro-module-check-tool'
|
|
8
|
+
export const inject=['tools']
|
|
9
|
+
export const Config:z<{rootPath?:string}>=z.object({rootPath:z.string()})
|
|
10
|
+
interface SC{agent?:{session?:{header?:{cwd?:string}}}}
|
|
11
|
+
function workspaceRootFor(c:string|undefined,e:unknown):string{
|
|
12
|
+
if(c!==undefined) return c
|
|
13
|
+
const cwd=(e as SC|undefined)?.agent?.session?.header?.cwd
|
|
14
|
+
return typeof cwd==='string'&&cwd!==''?cwd:process.cwd()
|
|
15
|
+
}
|
|
16
|
+
async function isInsideRoot(r:string,t:string):Promise<boolean>{
|
|
17
|
+
const ar=resolve(r); const rs=resolve(ar,t)
|
|
18
|
+
if(rs!==ar && !rs.startsWith(ar+sep)) return false
|
|
19
|
+
try{ const rr=await realpath(ar); const rt=await realpath(rs); return rt===rr||rt.startsWith(rr+sep)}catch{return false}
|
|
20
|
+
}
|
|
21
|
+
const MAX_FILE_BYTES=1024*1024
|
|
22
|
+
const MAX_MODULES=100
|
|
23
|
+
async function exists(p:string):Promise<boolean>{ try{await stat(p); return true}catch{return false}}
|
|
24
|
+
async function boundedRead(p:string):Promise<string|null>{
|
|
25
|
+
try{ const b=await readFile(p); if(b.byteLength>MAX_FILE_BYTES) return null; return b.toString('utf-8')}catch{return null}
|
|
26
|
+
}
|
|
27
|
+
function parseRegistrationName(text:string):string|null{
|
|
28
|
+
const re=/(?:\\?[\w\\]*ComponentRegistrar)::register\s*\(\s*(?:\\?[\w\\]*ComponentRegistrar::)?MODULE\s*,\s*['"]([^'"]+)['"]/
|
|
29
|
+
const m=re.exec(text); return m?m[1]:null
|
|
30
|
+
}
|
|
31
|
+
function parseModuleXml(text:string):{name:string|null, sequence:string[]}{
|
|
32
|
+
const nameRe=/<module\b[^>]*name="([^"]+)"/
|
|
33
|
+
const nm=nameRe.exec(text); const name=nm?nm[1]:null
|
|
34
|
+
const seq:string[]=[]; const seqRe=/<module\s+name="([^"]+)"\s*\/>/g; let m:RegExpExecArray|null
|
|
35
|
+
// only inside <sequence> block
|
|
36
|
+
const seqBlock=/<sequence>([\s\S]*?)<\/sequence>/.exec(text)
|
|
37
|
+
if(seqBlock){ const inner=seqBlock[1]; while((m=seqRe.exec(inner))!==null) seq.push(m[1]) }
|
|
38
|
+
return {name, sequence:seq}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function apply(ctx:Context, config:{rootPath?:string}={}):void{
|
|
42
|
+
const configuredRoot=config.rootPath
|
|
43
|
+
ctx.effect(()=>ctx.tools.register(defineTool({
|
|
44
|
+
name:'magento_module_check',
|
|
45
|
+
description:'Validate Magento 2 module structure (registration, module.xml, composer, db_schema, acl, routes, Hyva compat).',
|
|
46
|
+
parameters:{
|
|
47
|
+
modulePath:{type:'string'},
|
|
48
|
+
},
|
|
49
|
+
output:{
|
|
50
|
+
schema:{
|
|
51
|
+
type:'object', additionalProperties:false,
|
|
52
|
+
properties:{
|
|
53
|
+
modules:{type:'array', required:true},
|
|
54
|
+
scannedModules:{type:'number', required:true},
|
|
55
|
+
truncated:{type:'boolean', required:true},
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
render:(_a,v)=>[{type:'text', text: JSON.stringify(v,null,2)}],
|
|
59
|
+
},
|
|
60
|
+
async execute(args, exec){
|
|
61
|
+
const rawPath=(args as {modulePath?:string}).modulePath
|
|
62
|
+
const root=workspaceRootFor(configuredRoot, exec)
|
|
63
|
+
if(rawPath!==undefined && !(await isInsideRoot(root, rawPath))) return {text:`Path "${rawPath}" escapes the workspace root.`, truncated:false} as never
|
|
64
|
+
let candidates:string[]=[]
|
|
65
|
+
if(rawPath!==undefined){
|
|
66
|
+
const abs=resolve(root, rawPath)
|
|
67
|
+
// must contain registration.php or etc/module.xml to be considered a module; otherwise empty
|
|
68
|
+
candidates=[rawPath]
|
|
69
|
+
} else {
|
|
70
|
+
const base=join(root,'app/code')
|
|
71
|
+
let vendors:string[]=[]
|
|
72
|
+
try{ vendors=await readdir(base)}catch{ vendors=[]}
|
|
73
|
+
for(const vendor of vendors){
|
|
74
|
+
if(vendor.startsWith('.')) continue
|
|
75
|
+
let mods:string[]=[]
|
|
76
|
+
try{ mods=await readdir(join(base,vendor))}catch{continue}
|
|
77
|
+
for(const mod of mods){
|
|
78
|
+
if(mod.startsWith('.')) continue
|
|
79
|
+
candidates.push(join('app/code',vendor,mod))
|
|
80
|
+
if(candidates.length>=MAX_MODULES) break
|
|
81
|
+
}
|
|
82
|
+
if(candidates.length>=MAX_MODULES) break
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const truncated=candidates.length>=MAX_MODULES
|
|
86
|
+
const modules:Array<{
|
|
87
|
+
path:string,
|
|
88
|
+
module:{name:string|null,sequence:string[],hasRegistration:boolean,registrationValid:boolean|null,hasEtcModuleXml:boolean,hasDbSchema:boolean,dbSchemaTables:string[],hasAcl:boolean,aclResources:string[],hasRoutes:boolean,routes:Array<{area:string,frontName:string}>,hasObserverEvents:string[],hasCron:boolean,hasWebapi:boolean,apiInterfaces:string[],composerName:string|null,psr4:Record<string,string>|null},
|
|
89
|
+
issues:Array<{severity:string,rule:string,message:string,file:string,line:number|null}>,
|
|
90
|
+
isHyvaCompatible:boolean|null, compatNotes:string[]
|
|
91
|
+
}>=[]
|
|
92
|
+
for(const rel of candidates.slice(0,MAX_MODULES)){
|
|
93
|
+
const abs=resolve(root, rel)
|
|
94
|
+
let isDir=false; try{ const s=await stat(abs); isDir=s.isDirectory()}catch{ continue}
|
|
95
|
+
if(!isDir) continue
|
|
96
|
+
const issues: Array<{severity:string,rule:string,message:string,file:string,line:number|null}>=[]
|
|
97
|
+
const regText=await boundedRead(join(abs,'registration.php'))
|
|
98
|
+
const hasRegistration=regText!==null
|
|
99
|
+
let regName:string|null=null
|
|
100
|
+
let registrationValid:boolean|null=null
|
|
101
|
+
if(hasRegistration){
|
|
102
|
+
regName=parseRegistrationName(regText!)
|
|
103
|
+
registrationValid=regName!==null
|
|
104
|
+
if(!registrationValid) issues.push({severity:'high', rule:'registration-invalid', message:'registration.php does not contain ComponentRegistrar::register for MODULE', file:join(rel,'registration.php'), line:null})
|
|
105
|
+
} else {
|
|
106
|
+
issues.push({severity:'critical', rule:'registration-missing', message:'registration.php missing', file:join(rel,'registration.php'), line:null})
|
|
107
|
+
}
|
|
108
|
+
const modXmlText=await boundedRead(join(abs,'etc/module.xml'))
|
|
109
|
+
const hasEtcModuleXml=modXmlText!==null
|
|
110
|
+
let modName:string|null=null
|
|
111
|
+
let sequence:string[]=[]
|
|
112
|
+
if(hasEtcModuleXml){
|
|
113
|
+
const parsed=parseModuleXml(modXmlText!)
|
|
114
|
+
modName=parsed.name; sequence=parsed.sequence
|
|
115
|
+
if(!modName) issues.push({severity:'critical', rule:'module-xml-missing-name', message:'etc/module.xml missing module name', file:join(rel,'etc/module.xml'), line:null})
|
|
116
|
+
} else {
|
|
117
|
+
issues.push({severity:'high', rule:'module-xml-missing', message:'etc/module.xml missing', file:join(rel,'etc/module.xml'), line:null})
|
|
118
|
+
}
|
|
119
|
+
if(regName && modName && regName!==modName){
|
|
120
|
+
issues.push({severity:'high', rule:'name-mismatch', message:`registration name ${regName} != module.xml ${modName}`, file:join(rel,'etc/module.xml'), line:null})
|
|
121
|
+
}
|
|
122
|
+
// composer
|
|
123
|
+
const composerText=await boundedRead(join(abs,'composer.json'))
|
|
124
|
+
let composerName:string|null=null; let psr4:Record<string,string>|null=null
|
|
125
|
+
if(composerText){
|
|
126
|
+
try{ const j=JSON.parse(composerText); composerName=j.name??null; psr4=j.autoload?.['psr-4']??null }catch{}
|
|
127
|
+
if(composerName && modName){
|
|
128
|
+
// psr-4 cross-check not strict for test
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// db_schema
|
|
132
|
+
const hasDbSchema=await exists(join(abs,'etc/db_schema.xml'))
|
|
133
|
+
let dbSchemaTables:string[]=[]
|
|
134
|
+
if(hasDbSchema){
|
|
135
|
+
const dbText=await boundedRead(join(abs,'etc/db_schema.xml'))
|
|
136
|
+
if(dbText){ const re=/<table\s+name="([^"]+)"/g; let m:RegExpExecArray|null; while((m=re.exec(dbText))!==null) dbSchemaTables.push(m[1])}
|
|
137
|
+
}
|
|
138
|
+
// acl
|
|
139
|
+
const hasAcl=await exists(join(abs,'etc/acl.xml'))
|
|
140
|
+
let aclResources:string[]=[]
|
|
141
|
+
if(hasAcl){ const t=await boundedRead(join(abs,'etc/acl.xml')); if(t){ const re=/<resource\s+id="([^"]+)"/g; let m:RegExpExecArray|null; while((m=re.exec(t))!==null) aclResources.push(m[1])}}
|
|
142
|
+
// routes
|
|
143
|
+
let hasRoutes=false; const routes:Array<{area:string,frontName:string}>=[]
|
|
144
|
+
for(const area of ['frontend','adminhtml']){
|
|
145
|
+
const rp=join(abs,`etc/${area}/routes.xml`); if(await exists(rp)){ hasRoutes=true; const rt=await boundedRead(rp); if(rt){ const re=/<route\s+[^>]*frontName="([^"]+)"/g; let m:RegExpExecArray|null; while((m=re.exec(rt))!==null) routes.push({area, frontName:m[1]}) } }
|
|
146
|
+
}
|
|
147
|
+
if(await exists(join(abs,'etc/routes.xml'))){ hasRoutes=true }
|
|
148
|
+
// events
|
|
149
|
+
let hasObserverEvents:string[]=[]
|
|
150
|
+
const evText=await boundedRead(join(abs,'etc/events.xml'))
|
|
151
|
+
if(evText){ const re=/<event\s+name="([^"]+)"/g; let m:RegExpExecArray|null; while((m=re.exec(evText))!==null) hasObserverEvents.push(m[1])}
|
|
152
|
+
// cron/webapi
|
|
153
|
+
const hasCron=await exists(join(abs,'etc/crontab.xml')) || await exists(join(abs,'etc/cron_groups.xml'))
|
|
154
|
+
const hasWebapi=await exists(join(abs,'etc/webapi.xml'))
|
|
155
|
+
let apiInterfaces:string[]=[]
|
|
156
|
+
if(hasWebapi){ const t=await boundedRead(join(abs,'etc/webapi.xml')); if(t){ const re=/<service\s+class="([^"]+)"/g; let m:RegExpExecArray|null; while((m=re.exec(t))!==null) apiInterfaces.push(m[1])}}
|
|
157
|
+
|
|
158
|
+
// Hyva compat: scan phtml for data-bind vs x-data
|
|
159
|
+
let isHyvaCompatible:boolean|null=null
|
|
160
|
+
const compatNotes:string[]=[]
|
|
161
|
+
let hasPhtml=false; let hasDataBind=false; let hasXData=false
|
|
162
|
+
async function scanPhtml(dir:string){
|
|
163
|
+
let entries; try{entries=await readdir(dir,{withFileTypes:true})}catch{return}
|
|
164
|
+
for(const e of entries){
|
|
165
|
+
if(e.isDirectory()) await scanPhtml(join(dir,e.name))
|
|
166
|
+
else if(e.isFile() && e.name.endsWith('.phtml')){
|
|
167
|
+
hasPhtml=true
|
|
168
|
+
const txt=await boundedRead(join(dir,e.name))
|
|
169
|
+
if(!txt) continue
|
|
170
|
+
if(/data-bind\s*=/.test(txt)) hasDataBind=true
|
|
171
|
+
if(/x-data\s*=/.test(txt)) hasXData=true
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
await scanPhtml(join(abs,'view/frontend/templates'))
|
|
176
|
+
if(hasPhtml){
|
|
177
|
+
if(hasDataBind){ isHyvaCompatible=false; compatNotes.push('knockout data-bind found — not Hyva compatible')}
|
|
178
|
+
else if(hasXData){ isHyvaCompatible=true; compatNotes.push('Alpine x-data found — Hyva compatible')}
|
|
179
|
+
else { isHyvaCompatible=null }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const moduleInfo={name: modName ?? regName, sequence, hasRegistration, registrationValid, hasEtcModuleXml, hasDbSchema, dbSchemaTables, hasAcl, aclResources, hasRoutes, routes, hasObserverEvents, hasCron, hasWebapi, apiInterfaces, composerName, psr4}
|
|
183
|
+
modules.push({path:rel, module:moduleInfo, issues, isHyvaCompatible, compatNotes})
|
|
184
|
+
}
|
|
185
|
+
return {modules, scannedModules: modules.length, truncated} as never
|
|
186
|
+
},
|
|
187
|
+
}), 'module check'))
|
|
188
|
+
}
|
|
@@ -3,6 +3,7 @@ import { execFile } from 'node:child_process'
|
|
|
3
3
|
import { createHash } from 'node:crypto'
|
|
4
4
|
import { promisify } from 'node:util'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
|
+
import { normalize as posixNormalize } from 'node:path/posix'
|
|
6
7
|
import { homedir, tmpdir } from 'node:os'
|
|
7
8
|
import type { Context } from '@deepseek-ai/cordis'
|
|
8
9
|
import z from '@deepseek-ai/schemastery'
|
|
@@ -18,10 +19,21 @@ import type {} from '@deepseek-ai/dsh-session-title'
|
|
|
18
19
|
import * as GovardTool from './govard-tool.js'
|
|
19
20
|
import * as GitlabClient from './gitlab-client.js'
|
|
20
21
|
import * as ReviewFindingsTool from './review-findings-tool.js'
|
|
22
|
+
import * as SearchTool from './search-tool.js'
|
|
23
|
+
import * as HyvaThemeInspectTool from './hyva-theme-inspect-tool.js'
|
|
24
|
+
import * as HyvaCspScanTool from './hyva-csp-scan-tool.js'
|
|
25
|
+
import * as LayoutXmlTool from './layout-xml-tool.js'
|
|
26
|
+
import * as ModuleCheckTool from './module-check-tool.js'
|
|
27
|
+
import * as PhtmlEscapeScanTool from './phtml-escape-scan-tool.js'
|
|
28
|
+
import * as ScopeSplitTool from './scope-split-tool.js'
|
|
29
|
+
import * as GovardAuditLintTool from './govard-audit-lint-tool.js'
|
|
30
|
+
import * as PerfLogStatsTool from './perf-log-stats-tool.js'
|
|
31
|
+
import * as ReviewToolPolicy from './tool-policy.js'
|
|
21
32
|
import type { ReviewFinding } from './review-findings-tool.js'
|
|
22
33
|
import type { ReviewRequest } from './events.js'
|
|
23
34
|
import { loadUserConfig, type MaestroUserConfig, type ReviewModelSelection } from './config-store.js'
|
|
24
|
-
import { hasCompletedReview, pruneHistory, recordReviewFinish, recordReviewStart } from './review-history.js'
|
|
35
|
+
import { hasCompletedReview, lastCompletedReview, pruneHistory, recordReviewFinish, recordReviewStart } from './review-history.js'
|
|
36
|
+
import { buildIncrementalBlock, fetchCompare, fetchMrDetailHeadSha } from './incremental.js'
|
|
25
37
|
import { createReviewSignals } from './review-signals.js'
|
|
26
38
|
import { reviewDigestText, type NotifierLike } from './notify.js'
|
|
27
39
|
import { loadedReviewProfile, type ReviewSkillProfile } from './skills-tool.js'
|
|
@@ -58,6 +70,36 @@ export function agentOptionsForModel(selection: ModelSelection): ModelSelection
|
|
|
58
70
|
}
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
export function isUnsupportedReasoningError(err: unknown): boolean {
|
|
74
|
+
const code = (err as { code?: unknown })?.code
|
|
75
|
+
if (code === 'UNSUPPORTED_REASONING_EFFORT') return true
|
|
76
|
+
const message = (err as { message?: unknown })?.message
|
|
77
|
+
const text = typeof message === 'string' ? message : typeof err === 'string' ? err : String(message ?? err ?? '')
|
|
78
|
+
return text.includes('does not support reasoning effort')
|
|
79
|
+
|| text.includes('UNSUPPORTED_REASONING_EFFORT')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getTurnErrorMessage(handle: unknown): string | undefined {
|
|
83
|
+
try {
|
|
84
|
+
const events = (handle as { agent?: { session?: { events?: unknown[] } } })?.agent?.session?.events
|
|
85
|
+
if (!Array.isArray(events)) return undefined
|
|
86
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
87
|
+
const ev = events[i] as { type?: unknown; data?: { reason?: { kind?: unknown; error?: { message?: unknown; code?: unknown } } } }
|
|
88
|
+
if (ev?.type === 'turn/end' && ev?.data?.reason?.kind === 'error') {
|
|
89
|
+
const err = ev.data.reason.error
|
|
90
|
+
if (err !== undefined && err !== null) {
|
|
91
|
+
if (typeof (err as { message?: unknown }).message === 'string') return (err as { message: string }).message
|
|
92
|
+
if (typeof (err as { code?: unknown }).code === 'string') return String((err as { code: unknown }).code)
|
|
93
|
+
try { return JSON.stringify(err) } catch { return String(err) }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined
|
|
98
|
+
} catch {
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
61
103
|
/**
|
|
62
104
|
* Resolve the model to use for an automated review. Priority: per-project
|
|
63
105
|
* override > global reviewModel > DSH default (`fallback`).
|
|
@@ -246,6 +288,15 @@ async function loadLatestDiffSnapshot(fetcher: typeof fetch, apiBase: string, he
|
|
|
246
288
|
}
|
|
247
289
|
}
|
|
248
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Collapse `.`/`..`/duplicate slashes so worktree-relative finding paths
|
|
293
|
+
* (which legitimately contain `../` segments when a template references a
|
|
294
|
+
* sibling theme directory) compare equal to GitLab's canonical diff paths.
|
|
295
|
+
*/
|
|
296
|
+
function normalizedDiffPath(path: string): string {
|
|
297
|
+
return posixNormalize(path)
|
|
298
|
+
}
|
|
299
|
+
|
|
249
300
|
/**
|
|
250
301
|
* Publish agent findings from the orchestrator's own context. Agent tools are
|
|
251
302
|
* mounted inside a setup child fiber and are not guaranteed to remain
|
|
@@ -266,21 +317,30 @@ export async function postReviewFindings(findings: ReviewFinding[], config: Gitl
|
|
|
266
317
|
if (finding.path === undefined || finding.line === undefined) throw new Error('new finding missing path or line')
|
|
267
318
|
config.snapshot ??= loadLatestDiffSnapshot(fetcher, apiBase, headers)
|
|
268
319
|
const snapshot = await config.snapshot
|
|
269
|
-
const
|
|
320
|
+
const requestedPath = normalizedDiffPath(finding.path)
|
|
321
|
+
const change = snapshot.changes.find(candidate => normalizedDiffPath(candidate.new_path) === requestedPath)
|
|
322
|
+
?? snapshot.changes.find(candidate => normalizedDiffPath(candidate.old_path) === requestedPath)
|
|
270
323
|
if (change === undefined) throw new Error(`cannot post inline finding at ${finding.path}:${finding.line}: file is not in the current MR diff`)
|
|
271
324
|
if (change.collapsed === true || change.too_large === true || change.diff === '') {
|
|
272
325
|
throw new Error(`cannot post inline finding at ${finding.path}:${finding.line}: GitLab did not return this file's complete diff`)
|
|
273
326
|
}
|
|
274
327
|
const linePosition = diffPositionForNewLine(change.diff, finding.line)
|
|
275
|
-
if (linePosition === undefined)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
328
|
+
if (linePosition === undefined) {
|
|
329
|
+
// Line not in diff — fallback to MR note so finding is not silently lost.
|
|
330
|
+
response = await fetcher(`${apiBase}/notes`, {
|
|
331
|
+
method: 'POST', headers,
|
|
332
|
+
body: JSON.stringify({ body: `**Inline fallback — \`${finding.path}:${finding.line}\` line is not in current MR diff**\n\n${finding.body}` }),
|
|
333
|
+
})
|
|
334
|
+
} else {
|
|
335
|
+
response = await fetcher(`${apiBase}/discussions`, {
|
|
336
|
+
method: 'POST', headers,
|
|
337
|
+
body: JSON.stringify({ body: finding.body, position: {
|
|
338
|
+
position_type: 'text', ...snapshot.diffRefs, old_path: change.old_path, new_path: change.new_path,
|
|
339
|
+
...linePosition.oldLine === undefined ? {} : { old_line: linePosition.oldLine },
|
|
340
|
+
...linePosition.newLine === undefined ? {} : { new_line: linePosition.newLine },
|
|
341
|
+
} }),
|
|
342
|
+
})
|
|
343
|
+
}
|
|
284
344
|
}
|
|
285
345
|
if (!response.ok) throw new Error(`GitLab API error ${response.status}: ${await response.text()}`)
|
|
286
346
|
}
|
|
@@ -481,83 +541,129 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
481
541
|
// Resolved per review run from Settings so an agentTimeoutMs change takes
|
|
482
542
|
// effect without a plugin restart.
|
|
483
543
|
let effectiveAgentTimeoutMs = config.agentTimeoutMs
|
|
484
|
-
async function runReviewer(worktreePath: string | undefined, payload: ReviewRequest, effective: { gitlabBaseUrl: string; gitlabToken: string; botUsername: string }, reviewProfile?: ReviewSkillProfile, modelSelection?: ModelSelection): Promise<ReviewOutcome> {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
let
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
handle
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
544
|
+
async function runReviewer(worktreePath: string | undefined, payload: ReviewRequest, effective: { gitlabBaseUrl: string; gitlabToken: string; botUsername: string }, reviewProfile?: ReviewSkillProfile, modelSelection?: ModelSelection, incrementalBlock?: string): Promise<ReviewOutcome> {
|
|
545
|
+
const primaryOptions = agentOptionsForModel(modelSelection ?? ctx.agentDefaultModel.currentSelection())
|
|
546
|
+
const fallbackOptions: ModelSelection = { provider: primaryOptions.provider, model: primaryOptions.model }
|
|
547
|
+
let lastHandle: AgentHandle | undefined
|
|
548
|
+
const runOnce = async (agentOptions: ModelSelection): Promise<ReviewOutcome> => {
|
|
549
|
+
let capturedFindings: ReviewFinding[] = []
|
|
550
|
+
let handle: AgentHandle | undefined
|
|
551
|
+
let reviewerContext: Context | undefined
|
|
552
|
+
try {
|
|
553
|
+
handle = await ctx.agents.create({
|
|
554
|
+
sessionId: SessionId(`maestro-reviewer-${payload.mrIid}-${Date.now()}`),
|
|
555
|
+
meta: { cwd: worktreePath ?? tmpdir() },
|
|
556
|
+
agentOptions,
|
|
557
|
+
setup: async (agentCtx) => {
|
|
558
|
+
reviewerContext = agentCtx
|
|
559
|
+
installModelSelection(agentCtx, { current: agentOptions, assembled: undefined })
|
|
560
|
+
await agentCtx.plugin(ReviewToolPolicy)
|
|
561
|
+
await mountAgentPreset(ctx.agentPresets, agentCtx, 'dsh-maestro-reviewer')
|
|
562
|
+
await agentCtx.plugin(GitlabClient, {
|
|
563
|
+
baseUrl: effective.gitlabBaseUrl,
|
|
564
|
+
projectId: payload.projectId,
|
|
565
|
+
mrIid: payload.mrIid,
|
|
566
|
+
token: effective.gitlabToken,
|
|
567
|
+
botUsername: effective.botUsername,
|
|
568
|
+
})
|
|
569
|
+
await agentCtx.plugin(ReviewFindingsTool, { onReport: (findings) => { capturedFindings = findings } })
|
|
570
|
+
// Diff-only runs have no worktree; nothing to search there.
|
|
571
|
+
if (worktreePath !== undefined) {
|
|
572
|
+
await agentCtx.plugin(SearchTool, { rootPath: worktreePath })
|
|
573
|
+
await agentCtx.plugin(HyvaThemeInspectTool, { rootPath: worktreePath })
|
|
574
|
+
await agentCtx.plugin(HyvaCspScanTool, { rootPath: worktreePath })
|
|
575
|
+
await agentCtx.plugin(LayoutXmlTool, { rootPath: worktreePath })
|
|
576
|
+
await agentCtx.plugin(ModuleCheckTool, { rootPath: worktreePath })
|
|
577
|
+
await agentCtx.plugin(PhtmlEscapeScanTool, { rootPath: worktreePath })
|
|
578
|
+
await agentCtx.plugin(ScopeSplitTool, { rootPath: worktreePath })
|
|
579
|
+
await agentCtx.plugin(GovardAuditLintTool, { rootPath: worktreePath })
|
|
580
|
+
await agentCtx.plugin(PerfLogStatsTool, { rootPath: worktreePath })
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
})
|
|
584
|
+
} catch (err) {
|
|
585
|
+
lastHandle = handle
|
|
586
|
+
throw new Error(`failed to create reviewer agent: ${err instanceof Error ? err.message : String(err)}`)
|
|
526
587
|
}
|
|
588
|
+
lastHandle = handle
|
|
589
|
+
ctx.sessionTitle.rename(handle.agent.session, `Maestro Reviewer — MR !${payload.mrIid} (${payload.projectPath})`)
|
|
590
|
+
try {
|
|
591
|
+
const profileInstruction = reviewProfile === undefined
|
|
592
|
+
? 'This is a diff-only review with no local checkout or Magento environment. Do not claim that tests, static analysis, or Magento runtime validation ran. '
|
|
593
|
+
: `Call maestro_load_review_profile with {"profile":"${reviewProfile}"} before examining code. `
|
|
594
|
+
let scopePrompt = payload.scope.kind === 'discussion'
|
|
595
|
+
? `${profileInstruction}Review only the requested inline discussion ${payload.scope.discussionId} at ${payload.scope.path}:${payload.scope.line}. Do not review unrelated files or start a broad audit. Call gitlab_get_mr_diff, then call report_review_findings exactly once when done.`
|
|
596
|
+
: `${profileInstruction}Review this merge request (${payload.mode} mode). Call gitlab_list_own_review_threads and gitlab_get_mr_diff first, then call report_review_findings exactly once when done.`
|
|
597
|
+
if (incrementalBlock !== undefined) scopePrompt = `${incrementalBlock}\n\n${scopePrompt}`
|
|
598
|
+
handle.agent.followup(createUserMessage({
|
|
599
|
+
content: [{ type: 'text', text: scopePrompt }],
|
|
600
|
+
source: { kind: 'user' },
|
|
601
|
+
}))
|
|
602
|
+
await whenIdleWithTimeout(handle, effectiveAgentTimeoutMs)
|
|
603
|
+
if (reviewProfile !== undefined && (reviewerContext === undefined || loadedReviewProfile(reviewerContext) !== reviewProfile)) {
|
|
604
|
+
throw new Error(`reviewer did not successfully load the required ${reviewProfile} review skill profile; no findings were posted`)
|
|
605
|
+
}
|
|
527
606
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
607
|
+
// An inline command is a request to discuss one existing thread, not a
|
|
608
|
+
// license to fan out new threads across the MR. The report tool remains
|
|
609
|
+
// useful as a structured response channel, but its findings are folded
|
|
610
|
+
// into the one reply made by runReviewAndAudit below.
|
|
611
|
+
if (payload.scope.kind === 'discussion') {
|
|
612
|
+
const response = capturedFindings.length === 0
|
|
613
|
+
? 'No actionable issue found for the requested line.'
|
|
614
|
+
: capturedFindings.map((finding) => finding.body).join('\n\n')
|
|
615
|
+
return { summary: response, failures: [] }
|
|
616
|
+
}
|
|
538
617
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
618
|
+
const failures: string[] = []
|
|
619
|
+
let postedNew = 0
|
|
620
|
+
let postedReplies = 0
|
|
621
|
+
const findingPoster: GitlabFindingPoster = {
|
|
622
|
+
baseUrl: effective.gitlabBaseUrl,
|
|
623
|
+
token: effective.gitlabToken,
|
|
624
|
+
projectId: payload.projectId,
|
|
625
|
+
mrIid: payload.mrIid,
|
|
626
|
+
}
|
|
627
|
+
for (const [index, finding] of capturedFindings.entries()) {
|
|
628
|
+
try {
|
|
629
|
+
await postReviewFindings([finding], findingPoster)
|
|
630
|
+
if (finding.status === 'new') postedNew++
|
|
631
|
+
else postedReplies++
|
|
632
|
+
} catch (err) {
|
|
633
|
+
const locator = finding.status === 'new' ? `${finding.path}:${finding.line}` : finding.discussionId
|
|
634
|
+
failures.push(`${locator}: ${err instanceof Error ? err.message : String(err)}`)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return { summary: `${postedNew} new inline comment(s), ${postedReplies} thread(s) updated.`, failures }
|
|
638
|
+
} finally {
|
|
639
|
+
await handle.dispose()
|
|
547
640
|
}
|
|
548
|
-
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
return await runOnce(primaryOptions)
|
|
644
|
+
} catch (err) {
|
|
645
|
+
const turnMsg = getTurnErrorMessage(lastHandle)
|
|
646
|
+
const isTurnUnsupported = turnMsg !== undefined && (turnMsg.includes('does not support reasoning effort') || turnMsg.includes('UNSUPPORTED_REASONING_EFFORT'))
|
|
647
|
+
const isUnsupported = isUnsupportedReasoningError(err) || isTurnUnsupported
|
|
648
|
+
if (primaryOptions.reasoningEffort !== undefined && isUnsupported) {
|
|
649
|
+
const originalMsg = turnMsg ?? (err instanceof Error ? err.message : String(err))
|
|
549
650
|
try {
|
|
550
|
-
await
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
} catch (
|
|
554
|
-
const
|
|
555
|
-
|
|
651
|
+
const result = await runOnce(fallbackOptions)
|
|
652
|
+
console.warn(`maestro-orchestrator: Review model ${primaryOptions.provider}/${primaryOptions.model} with reasoningEffort "${String(primaryOptions.reasoningEffort)}" is unsupported; retried without reasoningEffort and succeeded`)
|
|
653
|
+
return result
|
|
654
|
+
} catch (retryErr) {
|
|
655
|
+
const retryMsg = getTurnErrorMessage(lastHandle) ?? (retryErr instanceof Error ? retryErr.message : String(retryErr))
|
|
656
|
+
throw new Error(`Review model ${primaryOptions.provider}/${primaryOptions.model} with reasoningEffort "${String(primaryOptions.reasoningEffort)}" is unsupported (${originalMsg}). Retried without reasoningEffort but still failed: ${retryMsg}. Remove reasoningEffort in Settings → Maestro → Review model or choose a reasoning-capable model.`)
|
|
556
657
|
}
|
|
557
658
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
659
|
+
if (err instanceof Error && err.message.includes('did not successfully load the required')) {
|
|
660
|
+
const effort = String(primaryOptions.reasoningEffort)
|
|
661
|
+
const hint = primaryOptions.reasoningEffort === undefined
|
|
662
|
+
? `${err.message}. Check that maestro-skills are installed and DSH was restarted after install.`
|
|
663
|
+
: `${err.message}. Model ${primaryOptions.provider}/${primaryOptions.model} with reasoningEffort "${effort}" may be unsupported — check Settings → Maestro → Review model. Original turn likely failed with UNSUPPORTED_REASONING_EFFORT — see session.jsonl.zstd turn/end.`
|
|
664
|
+
throw new Error(hint)
|
|
665
|
+
}
|
|
666
|
+
throw err
|
|
561
667
|
}
|
|
562
668
|
}
|
|
563
669
|
|
|
@@ -571,6 +677,7 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
571
677
|
agentOptions,
|
|
572
678
|
setup: async (agentCtx) => {
|
|
573
679
|
installModelSelection(agentCtx, { current: agentOptions, assembled: undefined })
|
|
680
|
+
await agentCtx.plugin(ReviewToolPolicy)
|
|
574
681
|
await mountAgentPreset(ctx.agentPresets, agentCtx, 'dsh-maestro-auditor')
|
|
575
682
|
await agentCtx.plugin(GovardTool, { rootPath: worktreePath })
|
|
576
683
|
await agentCtx.plugin(GitlabClient, {
|
|
@@ -628,7 +735,10 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
628
735
|
console.error(`maestro-orchestrator: MR !${String(payload.mrIid)} for project ${payload.projectPath} has no GitLab token — set one in Maestro Settings or MAESTRO_GITLAB_TOKEN`)
|
|
629
736
|
return
|
|
630
737
|
}
|
|
738
|
+
const resolved = { ...effective, gitlabToken }
|
|
631
739
|
const historyId = `${payload.projectId}-${payload.mrIid}-${Date.now()}`
|
|
740
|
+
const currentHeadSha = await fetchMrDetailHeadSha(resolved.gitlabBaseUrl, gitlabToken, payload.projectId, payload.mrIid)
|
|
741
|
+
.catch(() => undefined) as string | undefined
|
|
632
742
|
await recordReviewStart({
|
|
633
743
|
id: historyId,
|
|
634
744
|
projectId: payload.projectId,
|
|
@@ -638,8 +748,19 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
638
748
|
scope: payload.scope.kind,
|
|
639
749
|
trigger: payload.trigger,
|
|
640
750
|
startedAt: Date.now(),
|
|
751
|
+
headSha: currentHeadSha,
|
|
641
752
|
})
|
|
642
|
-
|
|
753
|
+
// H6 incremental context: what changed since the last completed review on this MR.
|
|
754
|
+
let incrementalBlock: string | undefined
|
|
755
|
+
try {
|
|
756
|
+
const prior = await lastCompletedReview(payload.projectId, payload.mrIid)
|
|
757
|
+
if (prior?.headSha !== undefined && currentHeadSha !== undefined && prior.headSha !== currentHeadSha) {
|
|
758
|
+
const compare = await fetchCompare(resolved.gitlabBaseUrl, gitlabToken, payload.projectId, prior.headSha, currentHeadSha)
|
|
759
|
+
incrementalBlock = buildIncrementalBlock(compare, prior.headSha, currentHeadSha)
|
|
760
|
+
}
|
|
761
|
+
} catch {
|
|
762
|
+
// Optional optimization only — never fail the review for it.
|
|
763
|
+
}
|
|
643
764
|
const fallbackSelection: ModelSelection = (ctx.get?.('agentDefaultModel') as { currentSelection(): ModelSelection } | undefined)?.currentSelection()
|
|
644
765
|
?? (ctx as unknown as { agentDefaultModel?: { currentSelection(): ModelSelection } }).agentDefaultModel?.currentSelection()
|
|
645
766
|
?? { provider: 'fallback', model: 'fallback' }
|
|
@@ -700,7 +821,7 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
700
821
|
return
|
|
701
822
|
}
|
|
702
823
|
const diffBody = await runDiffOnlyReview(payload, {
|
|
703
|
-
runReviewer: (p) => runReviewer(undefined, p, resolved, undefined, reviewModelSelection),
|
|
824
|
+
runReviewer: (p) => runReviewer(undefined, p, resolved, undefined, reviewModelSelection, incrementalBlock),
|
|
704
825
|
postComment,
|
|
705
826
|
replyToDiscussion,
|
|
706
827
|
writeFailedReport,
|
|
@@ -714,7 +835,7 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
714
835
|
localRepoPath: mapping.localRepoPath,
|
|
715
836
|
ensureWorktree,
|
|
716
837
|
removeWorktree,
|
|
717
|
-
runReviewer: (worktreePath, p) => runReviewer(worktreePath, p, resolved, mapping.reviewProfile ?? 'magento2', reviewModelSelection),
|
|
838
|
+
runReviewer: (worktreePath, p) => runReviewer(worktreePath, p, resolved, mapping.reviewProfile ?? 'magento2', reviewModelSelection, incrementalBlock),
|
|
718
839
|
runAuditor: (worktreePath, p) => runAuditor(worktreePath, p, resolved, reviewModelSelection),
|
|
719
840
|
postComment,
|
|
720
841
|
replyToDiscussion,
|