@ddtcorex/dsh-maestro-review 0.9.0 → 0.11.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.
Files changed (88) hide show
  1. package/README.md +10 -10
  2. package/cordis.patch.yml +4 -4
  3. package/lib/client.js +484 -0
  4. package/lib/config-store.d.ts +1 -1
  5. package/lib/config-store.d.ts.map +1 -1
  6. package/lib/config-store.js +43 -3
  7. package/lib/config-store.js.map +1 -1
  8. package/lib/govard-lint-detection.d.ts +20 -0
  9. package/lib/govard-lint-detection.d.ts.map +1 -0
  10. package/lib/govard-lint-detection.js +48 -0
  11. package/lib/govard-lint-detection.js.map +1 -0
  12. package/lib/orchestrator.d.ts +45 -9
  13. package/lib/orchestrator.d.ts.map +1 -1
  14. package/lib/orchestrator.js +82 -24
  15. package/lib/orchestrator.js.map +1 -1
  16. package/lib/search-tool.d.ts.map +1 -1
  17. package/lib/search-tool.js.map +1 -1
  18. package/lib/settings-rpc.d.ts +13 -11
  19. package/lib/settings-rpc.d.ts.map +1 -1
  20. package/lib/settings-rpc.js +45 -142
  21. package/lib/settings-rpc.js.map +1 -1
  22. package/lib/skill-provider.d.ts.map +1 -1
  23. package/lib/skill-provider.js +5 -2
  24. package/lib/skill-provider.js.map +1 -1
  25. package/lib/source.d.ts +23 -0
  26. package/lib/source.d.ts.map +1 -0
  27. package/lib/source.js +16 -0
  28. package/lib/source.js.map +1 -0
  29. package/lib/types/client/BrandMark.d.ts +22 -0
  30. package/lib/types/client/BrandMark.d.ts.map +1 -0
  31. package/lib/types/client/index.d.ts +12 -0
  32. package/lib/types/client/index.d.ts.map +1 -0
  33. package/lib/types/client/maestro-mark.d.ts +20 -0
  34. package/lib/types/client/maestro-mark.d.ts.map +1 -0
  35. package/lib/types/client/review/ReviewSettings.d.ts +15 -0
  36. package/lib/types/client/review/ReviewSettings.d.ts.map +1 -0
  37. package/lib/types/client/review/styles.d.ts +11 -0
  38. package/lib/types/client/review/styles.d.ts.map +1 -0
  39. package/lib/types/client/settings-nav-icon.d.ts +20 -0
  40. package/lib/types/client/settings-nav-icon.d.ts.map +1 -0
  41. package/lib/vendor/store.d.ts +60 -0
  42. package/lib/vendor/store.d.ts.map +1 -0
  43. package/lib/vendor/store.js +500 -0
  44. package/lib/vendor/store.js.map +1 -0
  45. package/lib/webhook-secret.d.ts +31 -0
  46. package/lib/webhook-secret.d.ts.map +1 -0
  47. package/lib/webhook-secret.js +38 -0
  48. package/lib/webhook-secret.js.map +1 -0
  49. package/package.json +36 -18
  50. package/presets/maestro-auditor.patch.yml +7 -4
  51. package/presets/maestro-coder/agent.cordis.yml +3 -11
  52. package/presets/maestro-coder/preset.yml +1 -1
  53. package/profiles/reviewer-ci/cordis.patch.yml +5 -4
  54. package/profiles/reviewer-ci/package.json +3 -3
  55. package/profiles/reviewer-ci/pnpm-lock.yaml +3524 -3239
  56. package/profiles/reviewer-ci/pnpm-workspace.yaml +1 -1
  57. package/skills/dsh-native-tools/SKILL.md +5 -5
  58. package/src/client/BrandMark.tsx +42 -0
  59. package/src/client/index.tsx +109 -0
  60. package/src/client/maestro-mark.ts +21 -0
  61. package/src/client/review/ReviewSettings.tsx +236 -0
  62. package/src/client/review/styles.ts +108 -0
  63. package/src/client/settings-nav-icon.ts +68 -0
  64. package/src/host/config-store.ts +42 -3
  65. package/src/host/govard-lint-detection.ts +47 -0
  66. package/src/host/orchestrator.ts +98 -25
  67. package/src/host/search-tool.ts +3 -4
  68. package/src/host/settings-rpc.ts +48 -145
  69. package/src/host/skill-provider.ts +5 -2
  70. package/src/host/source.ts +26 -0
  71. package/src/host/vendor/store.ts +531 -0
  72. package/src/host/webhook-secret.ts +43 -0
  73. package/templates/reviewer-project.gitlab-ci.yml +1 -1
  74. package/lib/govard-audit-lint-tool.d.ts +0 -95
  75. package/lib/govard-audit-lint-tool.d.ts.map +0 -1
  76. package/lib/govard-audit-lint-tool.js +0 -286
  77. package/lib/govard-audit-lint-tool.js.map +0 -1
  78. package/lib/govard-tool.d.ts +0 -11
  79. package/lib/govard-tool.d.ts.map +0 -1
  80. package/lib/govard-tool.js +0 -95
  81. package/lib/govard-tool.js.map +0 -1
  82. package/lib/workspace-tool.d.ts +0 -10
  83. package/lib/workspace-tool.d.ts.map +0 -1
  84. package/lib/workspace-tool.js +0 -92
  85. package/lib/workspace-tool.js.map +0 -1
  86. package/src/host/govard-audit-lint-tool.ts +0 -286
  87. package/src/host/govard-tool.ts +0 -114
  88. package/src/host/workspace-tool.ts +0 -108
@@ -1 +0,0 @@
1
- {"version":3,"file":"workspace-tool.js","sourceRoot":"","sources":["../src/host/workspace-tool.ts"],"names":[],"mappings":"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"}
@@ -1,286 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import { basename, dirname, resolve, sep } from 'node:path'
3
- import { realpath } from 'node:fs/promises'
4
- import type { Context } from '@deepseek-ai/cordis'
5
- import z from '@deepseek-ai/schemastery'
6
- import { defineTool } from '@deepseek-ai/dsh-tools'
7
-
8
- export const name='maestro-govard-audit-lint-tool'
9
- export const inject=['tools']
10
- export const Config:z<{rootPath?:string, timeoutMs?:number, defaultBase?:string, allowXdebug?:boolean}> = z.object({rootPath:z.string(), timeoutMs:z.number(), defaultBase:z.string(), allowXdebug:z.boolean()})
11
-
12
- export interface AuditLintOptions {
13
- checks?: string[]
14
- mode?: string
15
- scope?: string
16
- base?: string
17
- phpVersions?: string[]
18
- noLintResultCache?: boolean
19
- timeout?: string
20
- lintProvider?: string
21
- allowXdebug?: boolean
22
- }
23
-
24
- /** Explicit call base wins, then the review-wired default (MR base_sha). */
25
- export function resolveLintBase(argsBase: string | undefined, defaultBase: string | undefined): string | undefined {
26
- return argsBase ?? defaultBase
27
- }
28
-
29
- export function buildAuditCliArgs(a: AuditLintOptions): string[] {
30
- const checks = a.checks && a.checks.length ? a.checks.join(',') : 'lint'
31
- const mode = a.mode ?? 'auto'
32
- const timeout = a.timeout ?? 'auto'
33
- const lintProvider = a.lintProvider ?? 'govard'
34
- const cliArgs=['audit','run','--checks',checks,'--format','json','--mode',mode,'--timeout',timeout,'--lint-provider',lintProvider]
35
- if(a.scope) cliArgs.push('--scope', a.scope)
36
- const base = resolveLintBase(a.base, undefined)
37
- if(base) cliArgs.push('--base', base)
38
- if(a.phpVersions && a.phpVersions.length) cliArgs.push('--php', a.phpVersions.join(','))
39
- if(a.noLintResultCache) cliArgs.push('--no-lint-result-cache')
40
- if(a.allowXdebug) cliArgs.push('--allow-xdebug')
41
- return cliArgs
42
- }
43
-
44
- interface SC{agent?:{session?:{header?:{cwd?:string}}}}
45
- function workspaceRootFor(c:string|undefined, e:unknown):string{
46
- if(c!==undefined) return c
47
- const cwd=(e as SC|undefined)?.agent?.session?.header?.cwd
48
- return typeof cwd==='string'&&cwd!==''?cwd:process.cwd()
49
- }
50
- async function isInsideRoot(r:string,t:string):Promise<boolean>{
51
- const ar=resolve(r); const rs=resolve(ar,t)
52
- if(rs!==ar && !rs.startsWith(ar+sep)) return false
53
- let realRoot:string
54
- try{ realRoot=await realpath(ar)}catch{ return true }
55
- let existing=rs
56
- const pending:string[]=[]
57
- while(true){
58
- let real:string
59
- try{ real=await realpath(existing)}catch(err){
60
- if((err as NodeJS.ErrnoException).code!=='ENOENT') return false
61
- const parent=dirname(existing)
62
- if(parent===existing) return false
63
- pending.unshift(basename(existing))
64
- existing=parent
65
- continue
66
- }
67
- const realTarget=pending.length>0? resolve(real,...pending):real
68
- if(realTarget!==realRoot && !realTarget.startsWith(realRoot+sep)) return false
69
- return true
70
- }
71
- }
72
- export function cleanJson(raw:string):string{
73
- // pterm pads the level name: " ERROR audit run ..." (multi-space).
74
- return raw.replace(/\n\s*ERROR\s+audit run.*$/s, '').replace(/\s{2,}ERROR\s+audit run.*$/s, '').trimEnd()
75
- }
76
-
77
- interface LintViolationLike { path?: string; line?: number; rule?: string; message?: string }
78
-
79
- interface LintResultLike {
80
- ok: boolean
81
- exitCode?: number
82
- lint?: {
83
- phpcs?: { violations?: LintViolationLike[] }
84
- phpstan?: { errors?: LintViolationLike[] }
85
- pubMediaGuard?: { violations?: LintViolationLike[] }
86
- }
87
- summary?: { findingCount?: number }
88
- diagnostics?: string
89
- }
90
-
91
- /**
92
- * One-line-plus render for the agent: a bare "audit lint failed" hides the
93
- * violations the reviewer needs, so carry counts plus the top findings.
94
- */
95
- export interface CollectedLintFindings {
96
- phpcsViolations: Array<{ path?: string; line?: number; column?: number; rule?: string; message?: string; severity?: string }>
97
- phpstanErrors: Array<{ path?: string; line?: number; message?: string }>
98
- pubMediaViolations: unknown[]
99
- compat: Array<{ tool?: string; path?: string; line?: number; rule?: string; message?: string }>
100
- total: number
101
- }
102
-
103
- /**
104
- * Collect findings across govard audit JSON shapes. The live envelope nests
105
- * them at results[].evidence.php_results[].findings (older callers used a
106
- * top-level findings array or evidence.php_results) — and non-phpcs/phpstan
107
- * tools (e.g. M2-LINT-COMPAT internal errors) go to the compat bucket
108
- * instead of being silently dropped from counts.
109
- */
110
- export function collectLintFindings(parsed: unknown): CollectedLintFindings {
111
- const p = (parsed ?? {}) as Record<string, any>
112
- const lists: unknown[][] = []
113
- if (Array.isArray(p.findings)) lists.push(p.findings)
114
- const ev = p.evidence as Record<string, any> | undefined
115
- if (Array.isArray(ev?.php_results)) lists.push(...ev.php_results.map((r: any) => r?.findings).filter(Array.isArray))
116
- if (Array.isArray(p.php_results)) lists.push(...p.php_results.map((r: any) => r?.findings).filter(Array.isArray))
117
- // Live shape (govard audit run): jobs[].evidence.php_results[].findings.
118
- // Older shapes (results[], bare evidence) kept for backward compatibility.
119
- for (const key of ['jobs', 'results'] as const) {
120
- if (!Array.isArray(p[key])) continue
121
- for (const r of p[key] as Array<Record<string, any>>) {
122
- const e = r?.evidence as Record<string, any> | undefined
123
- const pr = e?.php_results ?? r?.php_results
124
- if (Array.isArray(pr)) lists.push(...pr.map((x: any) => x?.findings).filter(Array.isArray))
125
- if (Array.isArray(r?.findings)) lists.push(r.findings)
126
- }
127
- }
128
- const out: CollectedLintFindings = { phpcsViolations: [], phpstanErrors: [], pubMediaViolations: [], compat: [], total: 0 }
129
- // DSH tool output must be lossless JSON: drop undefined fields, the
130
- // runtime rejects values that do not survive a JSON round-trip.
131
- const compact = (entry: Record<string, unknown>): Record<string, unknown> => {
132
- const kept: Record<string, unknown> = {}
133
- for (const [k, v] of Object.entries(entry)) if (v !== undefined) kept[k] = v
134
- return kept
135
- }
136
- for (const list of lists) {
137
- for (const f of list as Array<Record<string, any>>) {
138
- if (f?.tool === 'phpstan') out.phpstanErrors.push(compact({ path: f.path, line: f.line, message: f.message }) as CollectedLintFindings['phpstanErrors'][number])
139
- else if (f?.tool === 'phpcs') {
140
- if (f.path?.includes('pub/media') || f.rule?.includes('PubMedia')) out.pubMediaViolations.push(f)
141
- else out.phpcsViolations.push(compact({ path: f.path, line: f.line, column: f.column, rule: f.rule, message: f.message, severity: f.severity }) as CollectedLintFindings['phpcsViolations'][number])
142
- } else if (f && typeof f === 'object') {
143
- out.compat.push(compact({ tool: f.tool, path: f.path, line: f.line, rule: f.rule, message: f.message }) as CollectedLintFindings['compat'][number])
144
- }
145
- }
146
- }
147
- out.total = out.phpcsViolations.length + out.phpstanErrors.length + out.pubMediaViolations.length + out.compat.length
148
- return out
149
- }
150
-
151
- export function lintResultText(v: LintResultLike): string {
152
- if (v.ok) return 'audit lint passed'
153
- const phpcs = v.lint?.phpcs?.violations ?? []
154
- const phpstan = v.lint?.phpstan?.errors ?? []
155
- const pubMedia = v.lint?.pubMediaGuard?.violations ?? []
156
- const compat = (v.lint as Record<string, any> | undefined)?.compat?.findings ?? []
157
- const total = v.summary?.findingCount ?? phpcs.length + phpstan.length + pubMedia.length + compat.length
158
- const bits: string[] = [`audit lint failed — ${total} finding(s) (phpcs ${phpcs.length}, phpstan ${phpstan.length}, pubMedia ${pubMedia.length}, compat ${compat.length})`]
159
- const top = [...phpcs.map(x => ({ ...x, tool: 'phpcs' })), ...phpstan.map(x => ({ ...x, tool: 'phpstan' })), ...pubMedia.map(x => ({ ...x, tool: 'pubMedia' })), ...compat.map((x: Record<string, any>) => ({ ...x, tool: x.tool ?? 'compat' }))].slice(0, 5)
160
- for (const f of top) bits.push(`- [${f.tool}] ${f.path ?? (f.message ?? '?').toString().slice(0, 120)}${f.path !== undefined ? `:${f.line ?? '?'}` : ''}${f.rule !== undefined ? ` ${f.rule}` : ''}`)
161
- if (v.exitCode !== undefined) bits.push(`exit ${v.exitCode}`)
162
- const diag = (v.diagnostics ?? '').split('\n')[0]?.trim()
163
- if (total === 0 && diag !== '' && diag !== undefined) bits.push(diag.slice(0, 200))
164
- return bits.join('\n')
165
- }
166
- // Govard 1.67 auto timeout: 90s-30m framework-aware (15m floor for wordpress/magento2 → 22.5m auto)
167
- // Keep kill timeout above the largest auto value so the outer watchdog doesn't cancel a valid auto run.
168
- const DEFAULT_TIMEOUT=900_000
169
-
170
- function run(cmd:string, args:string[], cwd:string, timeoutMs:number):Promise<{code:number|null, stdout:string, stderr:string, timedOut:boolean}>{
171
- return new Promise((resolvePromise)=>{
172
- const child=spawn(cmd, args, {cwd})
173
- let stdout='', stderr=''
174
- let timedOut=false
175
- const timer=setTimeout(()=>{ timedOut=true; child.kill('SIGKILL') }, timeoutMs)
176
- child.stdout?.on('data',(c:Buffer)=> stdout+=c.toString())
177
- child.stderr?.on('data',(c:Buffer)=> stderr+=c.toString())
178
- child.on('error',(err:Error)=>{ clearTimeout(timer); resolvePromise({code:null, stdout, stderr: err.message, timedOut:false}) })
179
- child.on('close',(code)=>{ clearTimeout(timer); resolvePromise({code, stdout, stderr, timedOut}) })
180
- })
181
- }
182
-
183
- export function apply(ctx:Context, config:{rootPath?:string, timeoutMs?:number, defaultBase?:string, allowXdebug?:boolean}={}):void{
184
- const configuredRoot=config.rootPath
185
- const defaultTimeout=config.timeoutMs ?? DEFAULT_TIMEOUT
186
- const defaultBase=config.defaultBase
187
- // Review worktrees disable xdebug via .govard.local.yml, but govard's lint
188
- // guard probes the base .govard.yml only — so the mount opts out explicitly.
189
- const allowXdebug=config.allowXdebug ?? false
190
- ctx.effect(()=>ctx.tools.register(defineTool({
191
- name:'govard_audit_lint',
192
- 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. scope "diff" requires a base ref: pass base, or rely on the wired defaultBase (MR base_sha) when present.',
193
- parameters:{
194
- worktreePath:{type:'string'},
195
- checks:{type:'array', items:{type:'string'}},
196
- mode:{type:'string', enum:['auto','project','module_in_project','standalone']},
197
- phpVersions:{type:'array', items:{type:'string'}},
198
- noLintResultCache:{type:'boolean'},
199
- timeoutMs:{type:'number'},
200
- // New in 1.67/2.7: framework-aware timeout and native lint provider
201
- timeout:{type:'string', description:'Govard --timeout (e.g. auto, 300s, 15m, 0 for no timeout). Default auto.'},
202
- lintProvider:{type:'string', description:'--lint-provider (govard or external). Default govard.'},
203
- scope:{type:'string', enum:['project','diff']},
204
- base:{type:'string', description:'--base ref for diff scope'},
205
- },
206
- output:{
207
- schema:{
208
- type:'object', additionalProperties:false,
209
- properties:{
210
- ok:{type:'boolean', required:true},
211
- exitCode:{type:'number', required:true},
212
- timedOut:{type:'boolean', required:true},
213
- worktreePath:{type:'string', required:true},
214
- lint:{type:'object', required:true, additionalProperties:true},
215
- summary:{type:'object', required:true, additionalProperties:true},
216
- rawJson:{type:'object', required:true, additionalProperties:true},
217
- errors:{type:'array', items:{type:'object', additionalProperties:true}},
218
- diagnostics:{type:'string'},
219
- sessionId:{type:'string'},
220
- runId:{type:'string'},
221
- }
222
- },
223
- render:(_a,v:LintResultLike)=>[{type:'text', text: lintResultText(v)}],
224
- },
225
- async execute(args, exec){
226
- const rawPath=(args as {worktreePath?:string}).worktreePath
227
- const root=workspaceRootFor(configuredRoot, exec)
228
- const worktreePath= rawPath ? resolve(root, rawPath) : root
229
- const checkPath= rawPath ?? ''
230
- if(checkPath!=='' && !(await isInsideRoot(root, checkPath))) return {text:`Path "${checkPath}" escapes the workspace root.`, truncated:false} as never
231
- const timeoutMs=(args as {timeoutMs?:number}).timeoutMs ?? defaultTimeout
232
- 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
233
-
234
- const a = args as {checks?:string[]; mode?:string; scope?:string; base?:string; phpVersions?:string[]; noLintResultCache?:boolean; timeout?:string; lintProvider?:string; allowXdebug?:boolean}
235
- const scope = a.scope
236
- const base = resolveLintBase(a.base, defaultBase)
237
- if (scope === 'diff' && base === undefined) {
238
- return {text:'scope "diff" requires a base ref (govard --base): pass base explicitly (e.g. origin/master or the MR base_sha) or mount this tool with defaultBase.', truncated:false} as never
239
- }
240
- const cliArgs = buildAuditCliArgs({ ...a, base, allowXdebug: a.allowXdebug ?? allowXdebug })
241
- const result=await run('govard', cliArgs, worktreePath, timeoutMs)
242
- if(result.timedOut){
243
- 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
244
- }
245
- if(result.code===null && result.stderr.includes('ENOENT')){
246
- return {ok:false, exitCode:127, timedOut:false, worktreePath, lint:{phpcs:{violations:[]}, phpstan:{errors:[]}, pubMediaGuard:{violations:[]}}, summary:{status:null, phpVersions:[], matrixComplete:false, findingCount:0, truncated:false}, rawJson:{}, errors:[{code:'govard_not_found', message:'govard binary not found'}], diagnostics:result.stderr.slice(0,4000)} as never
247
- }
248
- const cleaned=cleanJson(result.stdout)
249
- let parsed:any=null
250
- try{ parsed=JSON.parse(cleaned) }catch{ parsed=null }
251
- if(!parsed){
252
- const fallback=cleaned.split('\n').slice(0,-1).join('\n').trimEnd()
253
- try{ parsed=JSON.parse(fallback)}catch{}
254
- if(!parsed){
255
- const tryStderr=cleanJson(result.stdout+result.stderr)
256
- try{ parsed=JSON.parse(tryStderr)}catch{}
257
- }
258
- }
259
- if(!parsed){
260
- return {ok:false, exitCode: result.code ?? 1, timedOut:false, worktreePath, lint:{phpcs:{violations:[]}, phpstan:{errors:[]}, pubMediaGuard:{violations:[]}}, summary:{status:null, phpVersions:[], matrixComplete:false, findingCount:0, truncated:false}, rawJson:{}, errors:[{code:'parse_error', message:'stdout not JSON'}], diagnostics:(cleaned+result.stderr).slice(0,4000)} as never
261
- }
262
- const collected = collectLintFindings(parsed)
263
- const phpcsViolations = collected.phpcsViolations
264
- const phpstanErrors = collected.phpstanErrors
265
- const pubMediaViolations = collected.pubMediaViolations
266
- const status=parsed.status ?? (result.code===0?'passed':'failed')
267
- const phpVersions=parsed.php_versions ?? parsed.phpVersions ?? []
268
- const findingCount=collected.total
269
- const sessionId = parsed.session_id ?? parsed.sessionId ?? undefined
270
- const runId = parsed.run_id ?? parsed.runId ?? undefined
271
- return {
272
- ok: result.code===0,
273
- exitCode: result.code ?? 0,
274
- timedOut:false,
275
- worktreePath,
276
- ...(sessionId ? { sessionId: String(sessionId) } : {}),
277
- ...(runId ? { runId: String(runId) } : {}),
278
- rawJson: parsed as Record<string, unknown>,
279
- summary:{status, phpVersions, matrixComplete:true, findingCount, truncated: findingCount>100},
280
- lint:{phpcs:{violations:phpcsViolations}, phpstan:{errors:phpstanErrors}, pubMediaGuard:{violations:pubMediaViolations}, compat:{findings:collected.compat}},
281
- errors:[],
282
- diagnostics: result.stderr.slice(0,4000),
283
- } as never
284
- }
285
- })))
286
- }
@@ -1,114 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import { existsSync } from 'node:fs'
3
- import { join } from 'node:path'
4
- import type { Context } from '@deepseek-ai/cordis'
5
- import z from '@deepseek-ai/schemastery'
6
- import { defineTool } from '@deepseek-ai/dsh-tools'
7
-
8
- export const name = 'maestro-govard-tool'
9
- export const inject = ['tools']
10
-
11
- export interface Config {
12
- rootPath?: string
13
- timeoutMs?: number
14
- }
15
-
16
- export const Config: z<Config> = z.object({
17
- rootPath: z.string(),
18
- timeoutMs: z.number().min(1_000).default(300_000),
19
- })
20
-
21
- interface RunResult {
22
- code: number | null
23
- stdout: string
24
- stderr: string
25
- }
26
-
27
- function run(command: string, args: string[], cwd: string, timeoutMs: number): Promise<RunResult> {
28
- return new Promise((promiseResolve, promiseReject) => {
29
- const child = spawn(command, args, { cwd })
30
- let stdout = ''
31
- let stderr = ''
32
- const timer = setTimeout(() => {
33
- child.kill('SIGKILL')
34
- promiseReject(new Error(`"${command} ${args.join(' ')}" timed out after ${timeoutMs}ms`))
35
- }, timeoutMs)
36
- child.stdout?.on('data', (chunk: Buffer) => { stdout += chunk.toString() })
37
- child.stderr?.on('data', (chunk: Buffer) => { stderr += chunk.toString() })
38
- child.on('error', (err) => {
39
- clearTimeout(timer)
40
- promiseReject(err)
41
- })
42
- child.on('close', (code) => {
43
- clearTimeout(timer)
44
- promiseResolve({ code, stdout, stderr })
45
- })
46
- })
47
- }
48
-
49
- function textResult(result: RunResult, successText: string): { text: string } {
50
- if (result.code !== 0) {
51
- throw new Error(`Exit ${result.code}: ${result.stderr || result.stdout}`)
52
- }
53
- return { text: result.stdout.length > 0 ? result.stdout : successText }
54
- }
55
-
56
- export function apply(ctx: Context, config: Config): void {
57
- const rootPath = config.rootPath ?? process.cwd()
58
- const timeoutMs = config.timeoutMs ?? 300_000
59
-
60
- async function ensureInitialized(): Promise<void> {
61
- if (existsSync(join(rootPath, '.govard.yml'))) return
62
- const result = await run('govard', ['init', '--framework', 'magento2', '-y'], rootPath, timeoutMs)
63
- if (result.code !== 0) throw new Error(`govard init failed: ${result.stderr || result.stdout}`)
64
- }
65
-
66
- ctx.tools.register(defineTool({
67
- name: 'govard_env_up',
68
- description: 'Bring up the local Magento 2 Govard environment for the current worktree, initializing it first if needed.',
69
- parameters: {},
70
- output: {
71
- schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string', required: true } } },
72
- render: (_args, value) => [{ type: 'text', text: value.text }],
73
- },
74
- async execute() {
75
- await ensureInitialized()
76
- const result = await run('govard', ['up'], rootPath, timeoutMs)
77
- return textResult(result, 'Environment is up.')
78
- },
79
- }))
80
-
81
- ctx.tools.register(defineTool({
82
- name: 'govard_shell',
83
- description: 'Run one non-interactive command inside the Govard-managed container (e.g. a test suite) and return its output.',
84
- parameters: {
85
- command: { type: 'string', required: true, description: 'Shell command to run inside the container. PHPUnit in a Magento root: always scope it — "vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist --filter <Module>" or "vendor/bin/phpunit --no-coverage --bootstrap dev/tests/unit/framework/bootstrap.php app/code/<Vendor>/<Module>/Test/Unit". The root ships no phpunit.xml so the bare binary prints usage and exits 1; never run the full suite bare (core fixture conflicts), and capture status via ${PIPESTATUS[0]} when piping through tail.' },
86
- },
87
- output: {
88
- schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string', required: true } } },
89
- render: (_args, value) => [{ type: 'text', text: value.text }],
90
- },
91
- async execute(args) {
92
- const result = await run('govard', ['shell', '-c', args.command], rootPath, timeoutMs)
93
- return textResult(result, '(no output)')
94
- },
95
- }))
96
-
97
- ctx.tools.register(defineTool({
98
- name: 'govard_env_down',
99
- description: 'Tear down the local Govard environment for the current worktree, removing volumes.',
100
- parameters: {},
101
- output: {
102
- schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string', required: true } } },
103
- render: (_args, value) => [{ type: 'text', text: value.text }],
104
- },
105
- async execute() {
106
- const result = await run('govard', ['down', '-v'], rootPath, timeoutMs)
107
- return textResult(result, 'Environment is down.')
108
- },
109
- }))
110
-
111
- ctx.effect(() => async () => {
112
- await run('govard', ['down', '-v'], rootPath, timeoutMs).catch(() => undefined)
113
- }, 'govard-tool teardown')
114
- }
@@ -1,108 +0,0 @@
1
- import { basename, dirname, resolve, sep } from 'node:path'
2
- import { mkdir, readFile, realpath, writeFile } 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-workspace-tool'
8
- export const inject = ['tools']
9
-
10
- export interface Config {
11
- rootPath?: string
12
- }
13
-
14
- export const Config: z<Config> = z.object({
15
- rootPath: z.string(),
16
- })
17
-
18
- async function resolveInRoot(rootPath: string, requestedPath: string): Promise<string | undefined> {
19
- const absoluteRoot = resolve(rootPath)
20
- const target = resolve(absoluteRoot, requestedPath)
21
- if (target !== absoluteRoot && !target.startsWith(absoluteRoot + sep)) return undefined
22
-
23
- let realRoot: string
24
- try {
25
- realRoot = await realpath(absoluteRoot)
26
- } catch {
27
- return undefined
28
- }
29
-
30
- let existing = target
31
- const pending: string[] = []
32
- while (true) {
33
- let real: string
34
- try {
35
- real = await realpath(existing)
36
- } catch (err) {
37
- if ((err as NodeJS.ErrnoException).code !== 'ENOENT') return undefined
38
- const parent = dirname(existing)
39
- if (parent === existing) return undefined
40
- pending.unshift(basename(existing))
41
- existing = parent
42
- continue
43
- }
44
- const realTarget = pending.length > 0 ? resolve(real, ...pending) : real
45
- if (realTarget !== realRoot && !realTarget.startsWith(realRoot + sep)) return undefined
46
- return target
47
- }
48
- }
49
-
50
- /**
51
- * Structural view of the tool run-context fields this tool needs. Mirrors the
52
- * `@deepseek-ai/dsh-tool-fs` session-cwd semantics: without an explicit pinned
53
- * root, relative paths resolve against the CALLING agent's per-session
54
- * workspace (`exec.agent.session.header.cwd`), so subagents whose session
55
- * lives in a checkout or review worktree read their own tree instead of the
56
- * harness host's `process.cwd()`.
57
- */
58
- interface SessionCwdSource {
59
- agent?: { session?: { header?: { cwd?: string } } }
60
- }
61
-
62
- function workspaceRootFor(configuredRoot: string | undefined, exec: unknown): string {
63
- if (configuredRoot !== undefined) return configuredRoot
64
- const cwd = (exec as SessionCwdSource | undefined)?.agent?.session?.header?.cwd
65
- return typeof cwd === 'string' && cwd !== '' ? cwd : process.cwd()
66
- }
67
-
68
- export function apply(ctx: Context, config: Config = {}): void {
69
- const configuredRoot = config.rootPath
70
-
71
- ctx.effect(() => ctx.tools.register(defineTool({
72
- name: 'maestro_read_file',
73
- description: 'Read a text file relative to the current workspace root.',
74
- parameters: {
75
- path: { type: 'string', required: true, description: 'Path relative to the workspace root.' },
76
- },
77
- output: {
78
- schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string', required: true } } },
79
- render: (_args, value) => [{ type: 'text', text: value.text }],
80
- },
81
- async execute(args, exec) {
82
- const target = await resolveInRoot(workspaceRootFor(configuredRoot, exec), args.path)
83
- if (target === undefined) return { isError: true, text: `Path "${args.path}" escapes the workspace root.` } as never
84
- const text = await readFile(target, 'utf-8')
85
- return { text }
86
- },
87
- })))
88
-
89
- ctx.effect(() => ctx.tools.register(defineTool({
90
- name: 'maestro_write_file',
91
- description: 'Write a text file relative to the current workspace root, creating parent directories as needed.',
92
- parameters: {
93
- path: { type: 'string', required: true, description: 'Path relative to the workspace root.' },
94
- content: { type: 'string', required: true, description: 'File content to write.' },
95
- },
96
- output: {
97
- schema: { type: 'object', additionalProperties: false, properties: { written: { type: 'boolean', required: true } } },
98
- render: (args) => [{ type: 'text', text: `Wrote ${args.path}` }],
99
- },
100
- async execute(args, exec) {
101
- const target = await resolveInRoot(workspaceRootFor(configuredRoot, exec), args.path)
102
- if (target === undefined) return { isError: true, text: `Path "${args.path}" escapes the workspace root.` } as never
103
- await mkdir(dirname(target), { recursive: true })
104
- await writeFile(target, args.content, 'utf-8')
105
- return { written: true }
106
- },
107
- })))
108
- }