@ddtcorex/dsh-maestro-review 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/git-worktree-tool.js +1 -1
- package/lib/git-worktree-tool.js.map +1 -1
- package/lib/govard-audit-lint-tool.d.ts.map +1 -1
- package/lib/govard-audit-lint-tool.js +29 -5
- package/lib/govard-audit-lint-tool.js.map +1 -1
- package/lib/notify.d.ts +9 -0
- package/lib/notify.d.ts.map +1 -1
- package/lib/notify.js +50 -3
- package/lib/notify.js.map +1 -1
- package/lib/orchestrator.d.ts +28 -0
- package/lib/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator.js +194 -21
- package/lib/orchestrator.js.map +1 -1
- package/lib/providers/gitlab.d.ts +1 -1
- package/lib/providers/gitlab.d.ts.map +1 -1
- package/lib/providers/gitlab.js +24 -28
- package/lib/providers/gitlab.js.map +1 -1
- package/lib/skills-tool.d.ts +2 -2
- package/lib/skills-tool.d.ts.map +1 -1
- package/lib/skills-tool.js +17 -2
- package/lib/skills-tool.js.map +1 -1
- package/package.json +9 -9
- package/src/host/git-worktree-tool.ts +1 -1
- package/src/host/govard-audit-lint-tool.ts +24 -4
- package/src/host/notify.ts +55 -3
- package/src/host/orchestrator.ts +200 -23
- package/src/host/providers/gitlab.ts +26 -74
- package/src/host/skills-tool.ts +18 -3
- package/lib/gitlab-webhook.d.ts +0 -17
- package/lib/gitlab-webhook.d.ts.map +0 -1
- package/lib/gitlab-webhook.js +0 -144
- package/lib/gitlab-webhook.js.map +0 -1
- package/src/host/gitlab-webhook.ts +0 -166
package/lib/skills-tool.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
export declare const name = "maestro-skills-tool";
|
|
3
3
|
export declare const inject: string[];
|
|
4
|
-
export type ReviewSkillProfile = 'magento2' | 'generic';
|
|
4
|
+
export type ReviewSkillProfile = 'magento2' | 'laravel' | 'symfony' | 'wordpress' | 'generic';
|
|
5
5
|
/**
|
|
6
6
|
* Exact skill names rather than a fuzzy search: a review must not silently
|
|
7
7
|
* omit a security or quality pass just because a similarly named skill exists.
|
|
8
8
|
*/
|
|
9
9
|
export declare const REVIEW_PROFILE_SKILLS: Record<ReviewSkillProfile, readonly string[]>;
|
|
10
|
-
export declare const MAESTRO_SKILLS_INSTALL_COMMAND = "curl -fsSL https://raw.githubusercontent.com/ddtcorex/maestro-skills/master/install.sh | bash -s -- --scope personal --target dsh --skills govard-toolbox,govard-magento,magento2-dev-core,magento2-frontend-dev,magento2-hyva-dev,magento2-code-review,magento2-linter,magento2-security-scan,magento2-performance-audit -y";
|
|
10
|
+
export declare const MAESTRO_SKILLS_INSTALL_COMMAND = "curl -fsSL https://raw.githubusercontent.com/ddtcorex/maestro-skills/master/install.sh | bash -s -- --scope personal --target dsh --skills govard-toolbox,govard-magento,govard-laravel,govard-symfony,govard-wordpress,php-dev-core,magento2-dev-core,magento2-frontend-dev,magento2-hyva-dev,magento2-code-review,magento2-linter,magento2-security-scan,magento2-performance-audit -y";
|
|
11
11
|
export declare function loadedReviewProfile(ctx: Context): ReviewSkillProfile | undefined;
|
|
12
12
|
interface SkillSummaryLike {
|
|
13
13
|
name: string;
|
package/lib/skills-tool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-tool.d.ts","sourceRoot":"","sources":["../src/host/skills-tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,eAAO,MAAM,IAAI,wBAAwB,CAAA;AACzC,eAAO,MAAM,MAAM,UAAsB,CAAA;AAEzC,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"skills-tool.d.ts","sourceRoot":"","sources":["../src/host/skills-tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,eAAO,MAAM,IAAI,wBAAwB,CAAA;AACzC,eAAO,MAAM,MAAM,UAAsB,CAAA;AAEzC,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAA;AAE7F;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CA+B/E,CAAA;AAED,eAAO,MAAM,8BAA8B,6XAA6X,CAAA;AAQxa,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS,CAEhF;AAQD,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,UAAU,iBAAiB;IACzB,IAAI,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAA;IACnC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAA;CAC5D;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAE7D;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA8HxC"}
|
package/lib/skills-tool.js
CHANGED
|
@@ -19,10 +19,25 @@ export const REVIEW_PROFILE_SKILLS = {
|
|
|
19
19
|
'magento2-security-scan',
|
|
20
20
|
'magento2-performance-audit',
|
|
21
21
|
],
|
|
22
|
+
laravel: [
|
|
23
|
+
'govard-toolbox',
|
|
24
|
+
'govard-laravel',
|
|
25
|
+
'php-dev-core',
|
|
26
|
+
],
|
|
27
|
+
symfony: [
|
|
28
|
+
'govard-toolbox',
|
|
29
|
+
'govard-symfony',
|
|
30
|
+
'php-dev-core',
|
|
31
|
+
],
|
|
32
|
+
wordpress: [
|
|
33
|
+
'govard-toolbox',
|
|
34
|
+
'govard-wordpress',
|
|
35
|
+
'php-dev-core',
|
|
36
|
+
],
|
|
22
37
|
/** Diff review against general best practices; no project skill set required. */
|
|
23
38
|
generic: [],
|
|
24
39
|
};
|
|
25
|
-
export const MAESTRO_SKILLS_INSTALL_COMMAND = 'curl -fsSL https://raw.githubusercontent.com/ddtcorex/maestro-skills/master/install.sh | bash -s -- --scope personal --target dsh --skills govard-toolbox,govard-magento,magento2-dev-core,magento2-frontend-dev,magento2-hyva-dev,magento2-code-review,magento2-linter,magento2-security-scan,magento2-performance-audit -y';
|
|
40
|
+
export const MAESTRO_SKILLS_INSTALL_COMMAND = 'curl -fsSL https://raw.githubusercontent.com/ddtcorex/maestro-skills/master/install.sh | bash -s -- --scope personal --target dsh --skills govard-toolbox,govard-magento,govard-laravel,govard-symfony,govard-wordpress,php-dev-core,magento2-dev-core,magento2-frontend-dev,magento2-hyva-dev,magento2-code-review,magento2-linter,magento2-security-scan,magento2-performance-audit -y';
|
|
26
41
|
// This is deliberately process-local and keyed by the reviewer Agent object,
|
|
27
42
|
// which every Cordis child context inherits. Preset mounting inserts child
|
|
28
43
|
// contexts, so keying on the plugin's context would make a successful load
|
|
@@ -105,7 +120,7 @@ export function apply(ctx) {
|
|
|
105
120
|
name: 'maestro_load_review_profile',
|
|
106
121
|
description: 'Load the complete, exact skill set required for a configured review profile. Fails if any required skill is unavailable.',
|
|
107
122
|
parameters: {
|
|
108
|
-
profile: { type: 'string', required: true, description: 'Review profile configured for the project. Supported: "magento2" (full Magento skill set), "generic" (no skills required).' },
|
|
123
|
+
profile: { type: 'string', required: true, description: 'Review profile configured for the project. Supported: "magento2" (full Magento skill set), "laravel" (Govard Laravel + PHP), "symfony" (Govard Symfony + PHP), "wordpress" (Govard WordPress + PHP), "generic" (no skills required).' },
|
|
109
124
|
},
|
|
110
125
|
output: {
|
|
111
126
|
schema: {
|
package/lib/skills-tool.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-tool.js","sourceRoot":"","sources":["../src/host/skills-tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAGnD,MAAM,CAAC,MAAM,IAAI,GAAG,qBAAqB,CAAA;AACzC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;AAIzC;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAkD;IAClF,QAAQ,EAAE;QACR,gBAAgB;QAChB,gBAAgB;QAChB,mBAAmB;QACnB,yEAAyE;QACzE,+EAA+E;QAC/E,uBAAuB;QACvB,mBAAmB;QACnB,sBAAsB;QACtB,iBAAiB;QACjB,wBAAwB;QACxB,4BAA4B;KAC7B;IACD,iFAAiF;IACjF,OAAO,EAAE,EAAE;CACZ,CAAA;AAED,MAAM,CAAC,MAAM,8BAA8B,GAAG,
|
|
1
|
+
{"version":3,"file":"skills-tool.js","sourceRoot":"","sources":["../src/host/skills-tool.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ddtcorex/dsh-maestro-review",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Maestro Review — pluggable ReviewProvider (GitLab/GitHub) + orchestrator for automated MR review",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"typescript": "^5.8.0",
|
|
32
32
|
"vitest": "^2.0.0",
|
|
33
33
|
"@types/node": "^22.0.0",
|
|
34
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
35
|
-
"@deepseek-ai/dsh-agent-default-model": "0.1.
|
|
36
|
-
"@deepseek-ai/dsh-agent-presets": "0.1.
|
|
37
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
38
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
39
|
-
"@deepseek-ai/dsh-session-title": "0.1.
|
|
40
|
-
"@deepseek-ai/dsh-subagent": "0.1.
|
|
41
|
-
"@deepseek-ai/dsh-tools": "0.1.
|
|
34
|
+
"@deepseek-ai/dsh-agent": "0.1.2-alpha.2",
|
|
35
|
+
"@deepseek-ai/dsh-agent-default-model": "0.1.2-alpha.2",
|
|
36
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.2-alpha.2",
|
|
37
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.2",
|
|
38
|
+
"@deepseek-ai/dsh-session": "0.1.2-alpha.2",
|
|
39
|
+
"@deepseek-ai/dsh-session-title": "0.1.2-alpha.2",
|
|
40
|
+
"@deepseek-ai/dsh-subagent": "0.1.2-alpha.2",
|
|
41
|
+
"@deepseek-ai/dsh-tools": "0.1.2-alpha.2"
|
|
42
42
|
},
|
|
43
43
|
"dsh": {
|
|
44
44
|
"bundle": {
|
|
@@ -101,7 +101,7 @@ export function apply(ctx:Context, config:{rootPath?:string}={}):void{
|
|
|
101
101
|
if(await exists(worktreePath)){
|
|
102
102
|
return {op:'create', created:false, worktreePath, branch, headSha:'', alreadyExisted:true, reason:'already exists'} as never
|
|
103
103
|
}
|
|
104
|
-
const res=await runGit(root, ['worktree','add','
|
|
104
|
+
const res=await runGit(root, ['worktree','add','-b', branch,'--', worktreePath, base])
|
|
105
105
|
if(res.code!==0){
|
|
106
106
|
if(res.stderr.includes('permission denied') || res.stderr.includes('EACCES')){
|
|
107
107
|
return {op:'create', created:false, worktreePath, branch, headSha:'', alreadyExisted:false, reason:'blocked by sandbox — working in place'} as never
|
|
@@ -40,7 +40,9 @@ async function isInsideRoot(r:string,t:string):Promise<boolean>{
|
|
|
40
40
|
export function cleanJson(raw:string):string{
|
|
41
41
|
return raw.replace(/\n\s*ERROR audit run.*$/s, '').replace(/\s{2,}ERROR audit run.*$/s, '').trimEnd()
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
// Govard 1.67 auto timeout: 90s-30m framework-aware (15m floor for wordpress/magento2 → 22.5m auto)
|
|
44
|
+
// Keep kill timeout above the largest auto value so the outer watchdog doesn't cancel a valid auto run.
|
|
45
|
+
const DEFAULT_TIMEOUT=900_000
|
|
44
46
|
|
|
45
47
|
function run(cmd:string, args:string[], cwd:string, timeoutMs:number):Promise<{code:number|null, stdout:string, stderr:string, timedOut:boolean}>{
|
|
46
48
|
return new Promise((resolvePromise)=>{
|
|
@@ -60,7 +62,7 @@ export function apply(ctx:Context, config:{rootPath?:string, timeoutMs?:number}=
|
|
|
60
62
|
const defaultTimeout=config.timeoutMs ?? DEFAULT_TIMEOUT
|
|
61
63
|
ctx.effect(()=>ctx.tools.register(defineTool({
|
|
62
64
|
name:'govard_audit_lint',
|
|
63
|
-
description:'Run govard audit --checks lint --format json and return structured phpcs/phpstan results. Use before hand-parsing text.',
|
|
65
|
+
description:'Run govard audit --checks lint --format json and return structured phpcs/phpstan results. Use before hand-parsing text. Govard 1.67+ uses --timeout auto (framework-aware 90s-30m, 22.5m for wordpress/magento2) by default.',
|
|
64
66
|
parameters:{
|
|
65
67
|
worktreePath:{type:'string'},
|
|
66
68
|
checks:{type:'array', items:{type:'string'}},
|
|
@@ -68,6 +70,11 @@ export function apply(ctx:Context, config:{rootPath?:string, timeoutMs?:number}=
|
|
|
68
70
|
phpVersions:{type:'array', items:{type:'string'}},
|
|
69
71
|
noLintResultCache:{type:'boolean'},
|
|
70
72
|
timeoutMs:{type:'number'},
|
|
73
|
+
// New in 1.67/2.7: framework-aware timeout and native lint provider
|
|
74
|
+
timeout:{type:'string', description:'Govard --timeout (e.g. auto, 300s, 15m, 0 for no timeout). Default auto.'},
|
|
75
|
+
lintProvider:{type:'string', description:'--lint-provider (govard or external). Default govard.'},
|
|
76
|
+
scope:{type:'string', enum:['project','diff']},
|
|
77
|
+
base:{type:'string', description:'--base ref for diff scope'},
|
|
71
78
|
},
|
|
72
79
|
output:{
|
|
73
80
|
schema:{
|
|
@@ -95,9 +102,22 @@ export function apply(ctx:Context, config:{rootPath?:string, timeoutMs?:number}=
|
|
|
95
102
|
const checkPath= rawPath ?? ''
|
|
96
103
|
if(checkPath!=='' && !(await isInsideRoot(root, checkPath))) return {text:`Path "${checkPath}" escapes the workspace root.`, truncated:false} as never
|
|
97
104
|
const timeoutMs=(args as {timeoutMs?:number}).timeoutMs ?? defaultTimeout
|
|
98
|
-
if(timeoutMs<5000 || timeoutMs>
|
|
105
|
+
if(timeoutMs<5000 || timeoutMs>1_800_000) return {text:'timeoutMs out of range 5000-1800000 (5s-30m). Use --timeout auto for framework-aware estimation.', truncated:false} as never
|
|
99
106
|
|
|
100
|
-
const
|
|
107
|
+
const a = args as {checks?:string[]; mode?:string; scope?:string; base?:string; phpVersions?:string[]; noLintResultCache?:boolean; timeout?:string; lintProvider?:string}
|
|
108
|
+
const checks = a.checks && a.checks.length ? a.checks.join(',') : 'lint'
|
|
109
|
+
const mode = a.mode ?? 'auto'
|
|
110
|
+
const scope = a.scope
|
|
111
|
+
const base = a.base
|
|
112
|
+
const phpVersionsArg = a.phpVersions
|
|
113
|
+
const noLintResultCache = a.noLintResultCache
|
|
114
|
+
const timeout = a.timeout ?? 'auto'
|
|
115
|
+
const lintProvider = a.lintProvider ?? 'govard'
|
|
116
|
+
const cliArgs=['audit','run','--checks',checks,'--format','json','--mode',mode,'--timeout',timeout,'--lint-provider',lintProvider]
|
|
117
|
+
if(scope) cliArgs.push('--scope', scope)
|
|
118
|
+
if(base) cliArgs.push('--base', base)
|
|
119
|
+
if(phpVersionsArg && phpVersionsArg.length) cliArgs.push('--php', phpVersionsArg.join(','))
|
|
120
|
+
if(noLintResultCache) cliArgs.push('--no-lint-result-cache')
|
|
101
121
|
const result=await run('govard', cliArgs, worktreePath, timeoutMs)
|
|
102
122
|
if(result.timedOut){
|
|
103
123
|
return {ok:false, exitCode: result.code ?? 124, timedOut:true, worktreePath, lint:{phpcs:{violations:[]}, phpstan:{errors:[]}, pubMediaGuard:{violations:[]}}, summary:{status:null, phpVersions:[], matrixComplete:false, findingCount:0, truncated:false}, rawJson:{}, errors:[{code:'timeout', message:`timed out after ${timeoutMs}ms` }], diagnostics:result.stderr.slice(0,4000)} as never
|
package/src/host/notify.ts
CHANGED
|
@@ -14,15 +14,67 @@ export interface NotifierLike {
|
|
|
14
14
|
send(providerId: string, target: Record<string, unknown>, message: { text: string }): Promise<NotifyDeliveryResult>
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function escapeHtml(text: string): string {
|
|
18
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function formatSummary(text: string, maxLen = 160): string {
|
|
22
|
+
const normalized = text.replace(/\s+/g, ' ').trim()
|
|
23
|
+
if (normalized.length <= maxLen) return normalized
|
|
24
|
+
const cut = normalized.slice(0, maxLen)
|
|
25
|
+
const lastSpace = cut.lastIndexOf(' ')
|
|
26
|
+
const truncated = (lastSpace > 80 ? cut.slice(0, lastSpace) : cut).trim()
|
|
27
|
+
return `${truncated} …`
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
export function reviewDigestText(notification: {
|
|
18
31
|
projectPath: string
|
|
19
32
|
mrIid: number
|
|
20
33
|
status: 'completed' | 'failed'
|
|
21
34
|
summary?: string
|
|
35
|
+
mode?: string
|
|
36
|
+
profile?: string
|
|
37
|
+
gitlabBaseUrl?: string
|
|
38
|
+
findings?: { newCount: number; replyCount: number; failedCount: number }
|
|
39
|
+
durationMs?: number
|
|
22
40
|
}): string {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
41
|
+
const isOk = notification.status === 'completed'
|
|
42
|
+
const statusLabel = isOk ? '✅ Completed' : '⚠️ Failed'
|
|
43
|
+
const mrUrl = notification.gitlabBaseUrl !== undefined
|
|
44
|
+
? `${notification.gitlabBaseUrl.replace(/\/$/, '')}/${notification.projectPath}/-/merge_requests/${notification.mrIid}`
|
|
45
|
+
: undefined
|
|
46
|
+
const mrLink = mrUrl !== undefined
|
|
47
|
+
? `<a href="${escapeHtml(mrUrl)}">!${notification.mrIid}</a>`
|
|
48
|
+
: `!${notification.mrIid}`
|
|
49
|
+
|
|
50
|
+
const metaParts: string[] = []
|
|
51
|
+
if (notification.mode !== undefined) metaParts.push(`<code>${escapeHtml(notification.mode)}</code>`)
|
|
52
|
+
if (notification.profile !== undefined) metaParts.push(`<code>${escapeHtml(notification.profile)}</code>`)
|
|
53
|
+
if (notification.durationMs !== undefined && notification.durationMs > 0) {
|
|
54
|
+
const s = Math.round(notification.durationMs / 1000)
|
|
55
|
+
metaParts.push(s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`)
|
|
56
|
+
}
|
|
57
|
+
const metaLine = metaParts.length > 0 ? ` · ${metaParts.join(' · ')}` : ''
|
|
58
|
+
|
|
59
|
+
const findingsLine = notification.findings !== undefined
|
|
60
|
+
? (() => {
|
|
61
|
+
const f = notification.findings
|
|
62
|
+
const parts: string[] = []
|
|
63
|
+
if (f.newCount > 0) parts.push(`${f.newCount} new`)
|
|
64
|
+
if (f.replyCount > 0) parts.push(`${f.replyCount} reply`)
|
|
65
|
+
if (f.failedCount > 0) parts.push(`${f.failedCount} failed`)
|
|
66
|
+
if (parts.length === 0) parts.push('no inline findings')
|
|
67
|
+
return `\n<b>Findings:</b> ${parts.join(' · ')}`
|
|
68
|
+
})()
|
|
69
|
+
: ''
|
|
70
|
+
|
|
71
|
+
const summaryBlock = notification.summary !== undefined && notification.summary.trim() !== ''
|
|
72
|
+
? `\n\n${escapeHtml(formatSummary(notification.summary.trim()))}`
|
|
73
|
+
: ''
|
|
74
|
+
|
|
75
|
+
const footerLink = mrUrl !== undefined ? `\n\n<a href="${escapeHtml(mrUrl)}">View MR →</a>` : ''
|
|
76
|
+
|
|
77
|
+
return `<b>🤖 Maestro Review</b> — <code>${escapeHtml(notification.projectPath)}</code> ${mrLink}\n<b>Status:</b> ${statusLabel}${metaLine}${findingsLine}${summaryBlock}${footerLink}`
|
|
26
78
|
}
|
|
27
79
|
|
|
28
80
|
export function pinRotationText(pin: string): string {
|
package/src/host/orchestrator.ts
CHANGED
|
@@ -58,6 +58,72 @@ export { gitlabProvider }
|
|
|
58
58
|
const execFileAsync = promisify(execFile)
|
|
59
59
|
const GIT_TIMEOUT_MS = 60_000
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Build a user-friendly GitLab Markdown comment for a completed review.
|
|
63
|
+
* Shared design language with `reviewDigestText` (Telegram HTML) — same
|
|
64
|
+
* header, status, findings structure, footer. Both use `🤖 Maestro Review`
|
|
65
|
+
* as the anchor so filtering/search stays consistent.
|
|
66
|
+
*/
|
|
67
|
+
export function buildReviewComment(opts: {
|
|
68
|
+
projectPath: string
|
|
69
|
+
mrIid: number
|
|
70
|
+
gitlabBaseUrl: string
|
|
71
|
+
mode: string
|
|
72
|
+
profile?: string
|
|
73
|
+
summary: string
|
|
74
|
+
failures: string[]
|
|
75
|
+
findings?: { newCount: number; replyCount: number }
|
|
76
|
+
durationMs?: number
|
|
77
|
+
isDiffOnly?: boolean
|
|
78
|
+
isDiscussion?: boolean
|
|
79
|
+
}): string {
|
|
80
|
+
const mrUrl = `${opts.gitlabBaseUrl.replace(/\/$/, '')}/${opts.projectPath}/-/merge_requests/${opts.mrIid}`
|
|
81
|
+
const title = opts.isDiffOnly === true ? '## 🤖 Maestro Review — Diff-only' : '## 🤖 Maestro Review'
|
|
82
|
+
const metaBits: string[] = []
|
|
83
|
+
metaBits.push(`**\`${opts.projectPath}\` !${opts.mrIid}**`)
|
|
84
|
+
metaBits.push('✅ Completed')
|
|
85
|
+
metaBits.push(`\`${opts.mode}\``)
|
|
86
|
+
if (opts.profile !== undefined) metaBits.push(`\`${opts.profile}\``)
|
|
87
|
+
if (opts.durationMs !== undefined && opts.durationMs > 0) {
|
|
88
|
+
const s = Math.round(opts.durationMs / 1000)
|
|
89
|
+
metaBits.push(s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`)
|
|
90
|
+
}
|
|
91
|
+
const metaLine = metaBits.join(' · ')
|
|
92
|
+
const scopeNote = opts.isDiffOnly === true
|
|
93
|
+
? '> **Scope:** Diff-only — reviewed the GitLab diff without a local checkout, Magento environment, static analysis, or tests. For a full review, add this project in Settings → Maestro and mention again.\n\n'
|
|
94
|
+
: ''
|
|
95
|
+
const findingsLine = opts.findings !== undefined
|
|
96
|
+
? (() => {
|
|
97
|
+
const parts: string[] = []
|
|
98
|
+
if (opts.findings.newCount > 0) parts.push(`💬 ${opts.findings.newCount} new`)
|
|
99
|
+
if (opts.findings.replyCount > 0) parts.push(`🔁 ${opts.findings.replyCount} updated`)
|
|
100
|
+
if (parts.length === 0) parts.push('no inline findings')
|
|
101
|
+
return `\n\n**Findings:** ${parts.join(' · ')}`
|
|
102
|
+
})()
|
|
103
|
+
: ''
|
|
104
|
+
const failuresBlock = opts.failures.length > 0
|
|
105
|
+
? `\n\n<details>\n<summary>⚠️ Failed to post (${opts.failures.length})</summary>\n\n${opts.failures.map((f) => `- \`${f}\``).join('\n')}\n\n</details>`
|
|
106
|
+
: ''
|
|
107
|
+
const footer = `\n\n---\n\n<sub>Generated by Maestro · [View MR →](${mrUrl})</sub>`
|
|
108
|
+
return `${title}\n\n${metaLine}\n\n${scopeNote}${opts.summary}${findingsLine}${failuresBlock}${footer}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function buildNotStartedComment(opts: { gitlabBaseUrl: string; projectPath: string; mrIid: number }): string {
|
|
112
|
+
const mrUrl = `${opts.gitlabBaseUrl.replace(/\/$/, '')}/${opts.projectPath}/-/merge_requests/${opts.mrIid}`
|
|
113
|
+
return [
|
|
114
|
+
'## 🤖 Maestro Review — Not started',
|
|
115
|
+
'',
|
|
116
|
+
`**\`${opts.projectPath}\` !${opts.mrIid}** · ⏸️ Not started`,
|
|
117
|
+
'',
|
|
118
|
+
'> Deep review requires a project mapping with a local checkout and Magento environment.',
|
|
119
|
+
'> Add this project in **Settings → Maestro**, then mention the reviewer again.',
|
|
120
|
+
'',
|
|
121
|
+
`---`,
|
|
122
|
+
'',
|
|
123
|
+
`<sub>Generated by Maestro · [View MR →](${mrUrl})</sub>`,
|
|
124
|
+
].join('\n')
|
|
125
|
+
}
|
|
126
|
+
|
|
61
127
|
export const name = 'maestro-orchestrator'
|
|
62
128
|
export const inject = ['agentDefaultModel', 'agents', 'agentPresets', 'sessionTitle']
|
|
63
129
|
|
|
@@ -146,7 +212,7 @@ export const Config: z<Config> = z.object({
|
|
|
146
212
|
projectMappings: z.array(z.object({
|
|
147
213
|
projectPath: z.string().required(),
|
|
148
214
|
localRepoPath: z.string().required(),
|
|
149
|
-
reviewProfile: z.union([z.const('magento2'), z.const('generic')]).default('magento2'),
|
|
215
|
+
reviewProfile: z.union([z.const('magento2'), z.const('laravel'), z.const('symfony'), z.const('wordpress'), z.const('generic')]).default('magento2'),
|
|
150
216
|
})).required(),
|
|
151
217
|
gitlabBaseUrl: z.string().required(),
|
|
152
218
|
gitlabToken: z.string().role('secret'),
|
|
@@ -406,9 +472,28 @@ export async function runReviewAndAudit(payload: ReviewRequest, deps: ReviewAndA
|
|
|
406
472
|
const labels = ['Reviewer', 'Auditor']
|
|
407
473
|
if (settled[0].status === 'fulfilled') {
|
|
408
474
|
const { summary, failures } = settled[0].value
|
|
409
|
-
|
|
475
|
+
// Try to parse findings counts from summary like "2 new inline comment(s), 1 thread(s) updated."
|
|
476
|
+
const summaryText = summary ?? ''
|
|
477
|
+
const newMatch = /(\d+)\s+new inline/.exec(summaryText)
|
|
478
|
+
const replyMatch = /(\d+)\s+thread\(s\) updated/.exec(summaryText)
|
|
479
|
+
const newCount = newMatch !== null ? Number(newMatch[1]) : 0
|
|
480
|
+
const replyCount = replyMatch !== null ? Number(replyMatch[1]) : 0
|
|
481
|
+
// If deps carry gitlab context, use rich comment; otherwise fallback to simple
|
|
482
|
+
const richOpts = (deps as unknown as { gitlabBaseUrl?: string; reviewProfile?: string }).gitlabBaseUrl !== undefined
|
|
483
|
+
? buildReviewComment({
|
|
484
|
+
projectPath: payload.projectPath,
|
|
485
|
+
mrIid: payload.mrIid,
|
|
486
|
+
gitlabBaseUrl: (deps as unknown as { gitlabBaseUrl: string }).gitlabBaseUrl,
|
|
487
|
+
mode: payload.mode,
|
|
488
|
+
profile: (deps as unknown as { reviewProfile?: string }).reviewProfile,
|
|
489
|
+
summary: summaryText,
|
|
490
|
+
failures,
|
|
491
|
+
findings: { newCount, replyCount },
|
|
492
|
+
})
|
|
493
|
+
: `## 🤖 Maestro Review\n\n**\`${payload.projectPath}\` !${payload.mrIid}** · ✅ Completed · \`${payload.mode}\`${(deps as unknown as { reviewProfile?: string }).reviewProfile !== undefined ? ` · \`${(deps as unknown as { reviewProfile: string }).reviewProfile}\`` : ''}\n\n${summaryText}${failures.length > 0 ? `\n\n<details>\n<summary>⚠️ Failed to post (${failures.length})</summary>\n\n${failures.map((f) => `- \`${f}\``).join('\n')}\n\n</details>` : ''}`
|
|
494
|
+
sections.push(richOpts)
|
|
410
495
|
} else {
|
|
411
|
-
sections.push(`##
|
|
496
|
+
sections.push(`## 🤖 Maestro Review — Reviewer failed\n\n**\`${payload.projectPath}\` !${payload.mrIid}** · ❌ Failed\n\n${settled[0].reason instanceof Error ? settled[0].reason.message : String(settled[0].reason)}`)
|
|
412
497
|
}
|
|
413
498
|
const auditorResult = settled[1]
|
|
414
499
|
if (shouldAudit && auditorResult !== undefined && auditorResult.status === 'fulfilled') {
|
|
@@ -446,7 +531,18 @@ export async function runDiffOnlyReview(payload: ReviewRequest, deps: DiffOnlyRe
|
|
|
446
531
|
inFlightKeys.add(key)
|
|
447
532
|
try {
|
|
448
533
|
const { summary, failures } = await deps.runReviewer(payload)
|
|
449
|
-
const
|
|
534
|
+
const diffBody = (deps as unknown as { gitlabBaseUrl?: string }).gitlabBaseUrl !== undefined
|
|
535
|
+
? buildReviewComment({
|
|
536
|
+
projectPath: payload.projectPath,
|
|
537
|
+
mrIid: payload.mrIid,
|
|
538
|
+
gitlabBaseUrl: (deps as unknown as { gitlabBaseUrl: string }).gitlabBaseUrl,
|
|
539
|
+
mode: payload.mode,
|
|
540
|
+
summary: summary ?? '',
|
|
541
|
+
failures,
|
|
542
|
+
isDiffOnly: true,
|
|
543
|
+
})
|
|
544
|
+
: `## 🤖 Maestro Review — Diff-only\n\n> **Scope:** Diff-only — reviewed the GitLab diff without a local checkout, Magento environment, static analysis, or tests. For a full review, add this project in Settings → Maestro and mention again.\n\n${summary}${failures.length > 0 ? `\n\n<details>\n<summary>⚠️ Failed to post (${failures.length})</summary>\n\n${failures.map((f) => `- \`${f}\``).join('\n')}\n\n</details>` : ''}`
|
|
545
|
+
const body = diffBody
|
|
450
546
|
try {
|
|
451
547
|
if (payload.scope.kind === 'discussion') await deps.replyToDiscussion(payload.scope.discussionId, body)
|
|
452
548
|
else await deps.postComment(body)
|
|
@@ -466,7 +562,11 @@ export async function declineUnmappedDeepReview(payload: ReviewRequest, deps: Re
|
|
|
466
562
|
const key = reviewKey(payload)
|
|
467
563
|
if (inFlightKeys.has(key)) return
|
|
468
564
|
inFlightKeys.add(key)
|
|
469
|
-
const
|
|
565
|
+
const depsWithUrl = deps as unknown as { gitlabBaseUrl?: string; projectPath?: string }
|
|
566
|
+
const baseUrl = depsWithUrl.gitlabBaseUrl
|
|
567
|
+
const body = baseUrl !== undefined
|
|
568
|
+
? buildNotStartedComment({ gitlabBaseUrl: baseUrl, projectPath: payload.projectPath, mrIid: payload.mrIid })
|
|
569
|
+
: '## 🤖 Maestro Review — Not started\n\n**`' + payload.projectPath + '` !' + payload.mrIid + '** · ⏸️ Not started\n\n> Deep review requires a project mapping with a local checkout and Magento environment.\n> Add this project in **Settings → Maestro**, then mention the reviewer again.'
|
|
470
570
|
try {
|
|
471
571
|
try {
|
|
472
572
|
if (payload.scope.kind === 'discussion') await deps.replyToDiscussion(payload.scope.discussionId, body)
|
|
@@ -497,6 +597,21 @@ function assertSafeBranchName(sourceBranch: string): void {
|
|
|
497
597
|
}
|
|
498
598
|
}
|
|
499
599
|
|
|
600
|
+
export function isBranchNotFoundError(err: unknown): boolean {
|
|
601
|
+
const rawStderr = (err as { stderr?: unknown })?.stderr
|
|
602
|
+
const rawMsg = (err as { message?: unknown })?.message
|
|
603
|
+
const toText = (v: unknown): string => {
|
|
604
|
+
if (typeof v === 'string') return v
|
|
605
|
+
if (v instanceof Uint8Array || (typeof Buffer !== 'undefined' && Buffer.isBuffer(v as unknown))) return Buffer.from(v as Uint8Array).toString('utf8')
|
|
606
|
+
if (typeof v === 'object' && v !== null && 'toString' in v) try { return String(v) } catch {}
|
|
607
|
+
return ''
|
|
608
|
+
}
|
|
609
|
+
const stderrText = toText(rawStderr)
|
|
610
|
+
const msgText = toText(rawMsg)
|
|
611
|
+
const text = stderrText !== '' ? stderrText : msgText !== '' ? msgText : String(err ?? '')
|
|
612
|
+
return text.includes("couldn't find remote ref") || text.includes('could not find remote branch') || text.includes('unknown revision or path')
|
|
613
|
+
}
|
|
614
|
+
|
|
500
615
|
/**
|
|
501
616
|
* Keep a Govard audit worktree isolated from the developer's primary checkout.
|
|
502
617
|
* Without this local override, both checkouts inherit the same `project_name`
|
|
@@ -510,7 +625,17 @@ export function govardWorktreeOverride(projectId: number, mrIid: number, keySuff
|
|
|
510
625
|
export async function ensureWorktree(localRepoPath: string, sourceBranch: string, projectId: number, mrIid: number, keySuffix?: string): Promise<string> {
|
|
511
626
|
assertSafeBranchName(sourceBranch)
|
|
512
627
|
const worktreePath = join('/tmp', `maestro-mr-${projectId}-${mrIid}${keySuffix === undefined ? '' : `-${keySuffix}`}`)
|
|
513
|
-
|
|
628
|
+
try {
|
|
629
|
+
await execFileAsync('git', ['fetch', '--', 'origin', sourceBranch], { cwd: localRepoPath, timeout: GIT_TIMEOUT_MS })
|
|
630
|
+
} catch (err) {
|
|
631
|
+
if (isBranchNotFoundError(err)) {
|
|
632
|
+
const e = new Error(`branch not found on origin: ${sourceBranch}`)
|
|
633
|
+
;(e as unknown as { code: string }).code = 'BRANCH_NOT_FOUND'
|
|
634
|
+
;(e as unknown as { cause: unknown }).cause = err
|
|
635
|
+
throw e
|
|
636
|
+
}
|
|
637
|
+
throw err
|
|
638
|
+
}
|
|
514
639
|
// A host restart can interrupt an active review before its `finally` cleanup.
|
|
515
640
|
// Recover only this deterministic Maestro-owned path so the next delivery can
|
|
516
641
|
// retry; an unrelated worktree is never targeted.
|
|
@@ -766,23 +891,51 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
766
891
|
?? { provider: 'fallback', model: 'fallback' }
|
|
767
892
|
const reviewModelSelection = resolveReviewModel(userConfig, mapping, fallbackSelection)
|
|
768
893
|
// Opt-in Telegram digest; a delivery failure is logged and dropped.
|
|
769
|
-
const
|
|
894
|
+
const reviewStartMsOuter = Date.now()
|
|
895
|
+
const notifyTelegram = (
|
|
896
|
+
status: 'completed' | 'failed',
|
|
897
|
+
summary?: string,
|
|
898
|
+
extra?: { findings?: { newCount: number; replyCount: number; failedCount: number } },
|
|
899
|
+
): void => {
|
|
770
900
|
if (userConfig.telegramReviewNotifications !== true) return
|
|
771
901
|
const notifier = ctx.get?.('maestroNotifier') as NotifierLike | undefined
|
|
772
902
|
if (notifier === undefined) return
|
|
903
|
+
const findings = extra?.findings
|
|
773
904
|
void notifier.send(
|
|
774
905
|
'telegram',
|
|
775
906
|
{ botToken: userConfig.telegramBotToken, chatId: userConfig.telegramChatId },
|
|
776
|
-
{
|
|
907
|
+
{
|
|
908
|
+
text: reviewDigestText({
|
|
909
|
+
projectPath: payload.projectPath,
|
|
910
|
+
mrIid: payload.mrIid,
|
|
911
|
+
status,
|
|
912
|
+
summary,
|
|
913
|
+
mode: payload.mode,
|
|
914
|
+
profile: mapping?.reviewProfile,
|
|
915
|
+
gitlabBaseUrl: resolved.gitlabBaseUrl,
|
|
916
|
+
findings,
|
|
917
|
+
durationMs: Date.now() - reviewStartMsOuter,
|
|
918
|
+
}),
|
|
919
|
+
},
|
|
777
920
|
).then((result) => {
|
|
778
921
|
if (!result.sent && result.reason === 'request-failed') {
|
|
779
922
|
console.error(`maestro-orchestrator: Telegram review notification for MR !${String(payload.mrIid)} failed to deliver`)
|
|
780
923
|
}
|
|
781
924
|
})
|
|
782
925
|
}
|
|
783
|
-
/** First line + bounded excerpt of a posted report, for the history log.
|
|
784
|
-
|
|
785
|
-
|
|
926
|
+
/** First line + bounded excerpt of a posted report, for the history log.
|
|
927
|
+
* Word-boundary truncation so Telegram never shows half-cut code (`:src="item…`).
|
|
928
|
+
* If the report starts with the markdown header, strip it — Telegram already has its own header.
|
|
929
|
+
*/
|
|
930
|
+
const summarize = (body: string | undefined): string | undefined => {
|
|
931
|
+
if (body === undefined) return undefined
|
|
932
|
+
const withoutHeader = body.replace(/^## 🤖 Maestro Review[^\n]*\n\s*/, '').trim()
|
|
933
|
+
const cleaned = withoutHeader.replace(/\s+/g, ' ').trim()
|
|
934
|
+
if (cleaned.length <= 160) return cleaned
|
|
935
|
+
const cut = cleaned.slice(0, 160)
|
|
936
|
+
const lastSpace = cut.lastIndexOf(' ')
|
|
937
|
+
return `${(lastSpace > 80 ? cut.slice(0, lastSpace) : cut).trim()} …`
|
|
938
|
+
}
|
|
786
939
|
// Award-emoji acknowledgements only make sense on the MR itself; inline
|
|
787
940
|
// discussion reviews answer in-thread instead.
|
|
788
941
|
const signals = payload.scope.kind === 'mr'
|
|
@@ -814,7 +967,7 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
814
967
|
try {
|
|
815
968
|
if (mapping === undefined) {
|
|
816
969
|
if (payload.mode === 'deep') {
|
|
817
|
-
await declineUnmappedDeepReview(payload, { postComment, replyToDiscussion, writeFailedReport })
|
|
970
|
+
await declineUnmappedDeepReview(payload, { postComment, replyToDiscussion, writeFailedReport, gitlabBaseUrl: resolved.gitlabBaseUrl } as unknown as ReviewCommentDeps)
|
|
818
971
|
await recordReviewFinish(historyId, { status: 'completed', summary: 'Deep review declined (unmapped project)' })
|
|
819
972
|
notifyTelegram('completed', 'Deep review declined (unmapped project)')
|
|
820
973
|
await signals?.finish('completed')
|
|
@@ -825,22 +978,46 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
825
978
|
postComment,
|
|
826
979
|
replyToDiscussion,
|
|
827
980
|
writeFailedReport,
|
|
828
|
-
|
|
981
|
+
gitlabBaseUrl: resolved.gitlabBaseUrl,
|
|
982
|
+
} as unknown as DiffOnlyReviewDeps)
|
|
829
983
|
await recordReviewFinish(historyId, { status: 'completed', summary: summarize(diffBody) })
|
|
830
984
|
notifyTelegram('completed', summarize(diffBody))
|
|
831
985
|
await signals?.finish('completed')
|
|
832
986
|
return
|
|
833
987
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
988
|
+
let fullBody: string
|
|
989
|
+
try {
|
|
990
|
+
fullBody = await runReviewAndAudit(payload, {
|
|
991
|
+
localRepoPath: mapping.localRepoPath,
|
|
992
|
+
ensureWorktree,
|
|
993
|
+
removeWorktree,
|
|
994
|
+
runReviewer: (worktreePath, p) => runReviewer(worktreePath, p, resolved, mapping.reviewProfile ?? 'magento2', reviewModelSelection, incrementalBlock),
|
|
995
|
+
runAuditor: (worktreePath, p) => runAuditor(worktreePath, p, resolved, reviewModelSelection),
|
|
996
|
+
postComment,
|
|
997
|
+
replyToDiscussion,
|
|
998
|
+
writeFailedReport,
|
|
999
|
+
gitlabBaseUrl: resolved.gitlabBaseUrl,
|
|
1000
|
+
reviewProfile: mapping.reviewProfile ?? 'magento2',
|
|
1001
|
+
} as unknown as ReviewAndAuditDeps)
|
|
1002
|
+
} catch (err) {
|
|
1003
|
+
const isBranchNotFound = (err as { code?: string })?.code === 'BRANCH_NOT_FOUND' || isBranchNotFoundError(err)
|
|
1004
|
+
if (isBranchNotFound) {
|
|
1005
|
+
const diffBody = await runDiffOnlyReview(payload, {
|
|
1006
|
+
runReviewer: (p) => runReviewer(undefined, p, resolved, undefined, reviewModelSelection, incrementalBlock),
|
|
1007
|
+
postComment,
|
|
1008
|
+
replyToDiscussion,
|
|
1009
|
+
writeFailedReport,
|
|
1010
|
+
gitlabBaseUrl: resolved.gitlabBaseUrl,
|
|
1011
|
+
} as unknown as DiffOnlyReviewDeps)
|
|
1012
|
+
const summary = summarize(diffBody)
|
|
1013
|
+
const branchNote = `fallback diff-only: branch ${payload.sourceBranch} not found on origin — ${summary ?? ''}`.trim()
|
|
1014
|
+
await recordReviewFinish(historyId, { status: 'completed', summary: branchNote })
|
|
1015
|
+
notifyTelegram('completed', branchNote)
|
|
1016
|
+
await signals?.finish('completed')
|
|
1017
|
+
return
|
|
1018
|
+
}
|
|
1019
|
+
throw err
|
|
1020
|
+
}
|
|
844
1021
|
await recordReviewFinish(historyId, { status: 'completed', summary: summarize(fullBody) })
|
|
845
1022
|
notifyTelegram('completed', summarize(fullBody))
|
|
846
1023
|
await signals?.finish('completed')
|