@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.
@@ -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;
@@ -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;AAEvD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAgB/E,CAAA;AAED,eAAO,MAAM,8BAA8B,iUAAiU,CAAA;AAQ5W,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"}
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"}
@@ -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: {
@@ -1 +1 @@
1
- {"version":3,"file":"skills-tool.js","sourceRoot":"","sources":["../src/host/skills-tool.ts"],"names":[],"mappings":"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"}
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.1.2",
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.0-rc.8",
35
- "@deepseek-ai/dsh-agent-default-model": "0.1.0-rc.8",
36
- "@deepseek-ai/dsh-agent-presets": "0.1.0-rc.8",
37
- "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
38
- "@deepseek-ai/dsh-session": "0.1.0-rc.8",
39
- "@deepseek-ai/dsh-session-title": "0.1.0-rc.8",
40
- "@deepseek-ai/dsh-subagent": "0.1.0-rc.8",
41
- "@deepseek-ai/dsh-tools": "0.1.0-rc.8"
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','--', worktreePath,'-b', branch, base])
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
- const DEFAULT_TIMEOUT=120000
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>300000) return {text:'timeoutMs out of range 5000-300000', truncated:false} as never
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 cliArgs=['audit','run','--checks','lint','--format','json']
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
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
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 outcome = notification.status === 'completed' ? '✅ completed' : '⚠️ failed'
24
- const summary = notification.summary === undefined ? '' : `\n${notification.summary}`
25
- return `Maestro review of ${notification.projectPath} !${notification.mrIid}: ${outcome}${summary}`
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 {
@@ -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
- sections.push(`## Maestro Review\n\n${summary}${failures.length > 0 ? `\n\n**Failed to post:**\n${failures.map(f => `- ${f}`).join('\n')}` : ''}`)
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(`## ${labels[0]} failed\n\n${settled[0].reason instanceof Error ? settled[0].reason.message : String(settled[0].reason)}`)
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 body = `## Maestro Diff-only review\n\n**Scope limitation:** Reviewed only the changed GitLab diff. No local checkout, Magento environment, static analysis, or tests were available.\n\n${summary}${failures.length > 0 ? `\n\n**Failed to post:**\n${failures.map(f => `- ${f}`).join('\n')}` : ''}`
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 body = '## Maestro review not started\n\nDeep review requires a project mapping with a local checkout and Magento environment. Add this project in Settings → Maestro, then mention the reviewer again.'
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
- await execFileAsync('git', ['fetch', '--', 'origin', sourceBranch], { cwd: localRepoPath, timeout: GIT_TIMEOUT_MS })
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 notifyTelegram = (status: 'completed' | 'failed', summary?: string): void => {
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
- { text: reviewDigestText({ projectPath: payload.projectPath, mrIid: payload.mrIid, status, summary }) },
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
- const summarize = (body: string | undefined): string | undefined =>
785
- body === undefined ? undefined : body.replace(/\s+/g, ' ').trim().slice(0, 200)
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
- const fullBody = await runReviewAndAudit(payload, {
835
- localRepoPath: mapping.localRepoPath,
836
- ensureWorktree,
837
- removeWorktree,
838
- runReviewer: (worktreePath, p) => runReviewer(worktreePath, p, resolved, mapping.reviewProfile ?? 'magento2', reviewModelSelection, incrementalBlock),
839
- runAuditor: (worktreePath, p) => runAuditor(worktreePath, p, resolved, reviewModelSelection),
840
- postComment,
841
- replyToDiscussion,
842
- writeFailedReport,
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')