@ddtcorex/dsh-maestro-review 0.11.1 → 0.12.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 (85) hide show
  1. package/README.md +2 -3
  2. package/lib/client.js +10 -1
  3. package/lib/config-store.d.ts +0 -8
  4. package/lib/config-store.d.ts.map +1 -1
  5. package/lib/config-store.js +0 -3
  6. package/lib/config-store.js.map +1 -1
  7. package/lib/hyva-theme-inspect-tool.js.map +1 -1
  8. package/lib/layout-xml-tool.d.ts.map +1 -1
  9. package/lib/layout-xml-tool.js +0 -1
  10. package/lib/layout-xml-tool.js.map +1 -1
  11. package/lib/module-check-tool.d.ts.map +1 -1
  12. package/lib/module-check-tool.js +0 -1
  13. package/lib/module-check-tool.js.map +1 -1
  14. package/lib/notify.d.ts +0 -1
  15. package/lib/notify.d.ts.map +1 -1
  16. package/lib/notify.js +0 -3
  17. package/lib/notify.js.map +1 -1
  18. package/lib/orchestrator.d.ts +1 -9
  19. package/lib/orchestrator.d.ts.map +1 -1
  20. package/lib/orchestrator.js +1 -14
  21. package/lib/orchestrator.js.map +1 -1
  22. package/lib/phtml-escape-scan-tool.d.ts.map +1 -1
  23. package/lib/phtml-escape-scan-tool.js +0 -4
  24. package/lib/phtml-escape-scan-tool.js.map +1 -1
  25. package/lib/providers/gitlab.d.ts +0 -2
  26. package/lib/providers/gitlab.d.ts.map +1 -1
  27. package/lib/providers/gitlab.js +0 -34
  28. package/lib/providers/gitlab.js.map +1 -1
  29. package/lib/scope-split-tool.d.ts.map +1 -1
  30. package/lib/scope-split-tool.js +0 -1
  31. package/lib/scope-split-tool.js.map +1 -1
  32. package/lib/settings-rpc.d.ts.map +1 -1
  33. package/lib/settings-rpc.js.map +1 -1
  34. package/lib/types/client/BrandMark.d.ts +0 -3
  35. package/lib/types/client/BrandMark.d.ts.map +1 -1
  36. package/lib/types/client/review/styles.d.ts +1 -1
  37. package/lib/types/client/review/styles.d.ts.map +1 -1
  38. package/package.json +3 -4
  39. package/presets/maestro-auditor.patch.yml +6 -6
  40. package/presets/maestro-coder/agent.cordis.yml +5 -8
  41. package/presets/maestro-reviewer.patch.yml +6 -6
  42. package/skills/dsh-native-tools/SKILL.md +0 -3
  43. package/src/client/BrandMark.tsx +1 -1
  44. package/src/client/review/styles.ts +10 -1
  45. package/src/host/config-store.ts +0 -4
  46. package/src/host/hyva-theme-inspect-tool.ts +1 -1
  47. package/src/host/layout-xml-tool.ts +1 -2
  48. package/src/host/module-check-tool.ts +1 -2
  49. package/src/host/notify.ts +0 -4
  50. package/src/host/orchestrator.ts +1 -17
  51. package/src/host/phtml-escape-scan-tool.ts +0 -5
  52. package/src/host/providers/gitlab.ts +0 -35
  53. package/src/host/scope-split-tool.ts +0 -1
  54. package/src/host/settings-rpc.ts +0 -1
  55. package/client/settings-card.tsx +0 -4
  56. package/lib/git-worktree-tool.d.ts +0 -11
  57. package/lib/git-worktree-tool.d.ts.map +0 -1
  58. package/lib/git-worktree-tool.js +0 -162
  59. package/lib/git-worktree-tool.js.map +0 -1
  60. package/lib/plan-track-tool.d.ts +0 -11
  61. package/lib/plan-track-tool.d.ts.map +0 -1
  62. package/lib/plan-track-tool.js +0 -143
  63. package/lib/plan-track-tool.js.map +0 -1
  64. package/lib/providers/github.stub.d.ts +0 -3
  65. package/lib/providers/github.stub.d.ts.map +0 -1
  66. package/lib/providers/github.stub.js +0 -10
  67. package/lib/providers/github.stub.js.map +0 -1
  68. package/lib/providers/interface.d.ts +0 -12
  69. package/lib/providers/interface.d.ts.map +0 -1
  70. package/lib/providers/interface.js +0 -2
  71. package/lib/providers/interface.js.map +0 -1
  72. package/lib/tdd-evidence-tool.d.ts +0 -11
  73. package/lib/tdd-evidence-tool.d.ts.map +0 -1
  74. package/lib/tdd-evidence-tool.js +0 -114
  75. package/lib/tdd-evidence-tool.js.map +0 -1
  76. package/lib/webhook-secret.d.ts +0 -31
  77. package/lib/webhook-secret.d.ts.map +0 -1
  78. package/lib/webhook-secret.js +0 -38
  79. package/lib/webhook-secret.js.map +0 -1
  80. package/src/host/git-worktree-tool.ts +0 -130
  81. package/src/host/plan-track-tool.ts +0 -118
  82. package/src/host/providers/github.stub.ts +0 -11
  83. package/src/host/providers/interface.ts +0 -2
  84. package/src/host/tdd-evidence-tool.ts +0 -85
  85. package/src/host/webhook-secret.ts +0 -43
@@ -1,118 +0,0 @@
1
- import { join, resolve, sep } from 'node:path'
2
- import { readFile, writeFile, mkdir, realpath } from 'node:fs/promises'
3
- import type { Context } from '@deepseek-ai/cordis'
4
- import { defineTool } from '@deepseek-ai/dsh-tools'
5
- import z from '@deepseek-ai/schemastery'
6
-
7
- export const name='maestro-plan-track-tool'
8
- export const inject=['tools']
9
- export const Config:z<{rootPath?:string}> = z.object({rootPath:z.string()})
10
- interface SC{agent?:{session?:{header?:{cwd?:string}}}}
11
- function workspaceRootFor(c:string|undefined,e:unknown):string{ if(c!==undefined) return c; const cwd=(e as SC|undefined)?.agent?.session?.header?.cwd; return typeof cwd==='string'&&cwd!==''?cwd:process.cwd()}
12
- async function isInsideRoot(r:string,t:string):Promise<boolean>{
13
- const ar=resolve(r); const rs=resolve(ar,t)
14
- if(rs!==ar && !rs.startsWith(ar+sep)) return false
15
- try{ const rr=await realpath(ar); const rt=await realpath(rs); return rt===rr||rt.startsWith(rr+sep)}catch{
16
- try{ const rr=await realpath(ar); const parent=resolve(rs,'..'); const rp=await realpath(parent); return rp===rr||rp.startsWith(rr+sep)}catch{ return rs===ar || rs.startsWith(ar+sep) }
17
- }
18
- }
19
-
20
- type Task={id:string, title:string, status:'pending'|'in_progress'|'done'|'blocked', line:number}
21
- function parseTasks(md:string):Task[]{
22
- const lines=md.split('\n')
23
- const tasks:Task[]=[]
24
- for(let i=0;i<lines.length;i++){
25
- const m=/^###\s+(Task\s*\d+):?\s*(.*)/.exec(lines[i].trim())
26
- if(!m) continue
27
- const id=m[1].trim()
28
- let title=m[2].trim()
29
- let status:Task['status']='pending'
30
- if(title.includes('in_progress')) status='in_progress'
31
- else if(title.toLowerCase().includes('done') || title.includes('✅')) status='done'
32
- else if(title.toLowerCase().includes('blocked')) status='blocked'
33
- for(let j=i+1;j<Math.min(i+4, lines.length);j++){
34
- if(lines[j].toLowerCase().includes('in_progress')) status='in_progress'
35
- if(lines[j].toLowerCase().includes('status: done')) status='done'
36
- }
37
- tasks.push({id, title, status, line:i})
38
- }
39
- return tasks
40
- }
41
-
42
- export function apply(ctx:Context, config:{rootPath?:string}={}):void{
43
- const configuredRoot=config.rootPath
44
- ctx.effect(()=>ctx.tools.register(defineTool({
45
- name:'maestro_plan_track',
46
- description:'Track cross-repo plan: init/update/complete tasks with dtodo sync and oneTaskOneCommit guard.',
47
- parameters:{
48
- op:{type:'string', enum:['init','update','complete'], required:true},
49
- planPath:{type:'string', required:true},
50
- taskId:{type:'string'},
51
- status:{type:'string', enum:['pending','in_progress','done','blocked']},
52
- note:{type:'string'},
53
- },
54
- output:{
55
- schema:{
56
- type:'object', additionalProperties:true,
57
- properties:{
58
- ok:{type:'boolean', required:true},
59
- plan:{type:'object', required:true, additionalProperties:true},
60
- discipline:{type:'object', additionalProperties:true},
61
- }
62
- },
63
- render:(_a,v:{ok:boolean})=>[{type:'text', text: v.ok? 'ok' : 'violation'}],
64
- },
65
- async execute(args, exec){
66
- const op=(args as {op:string}).op
67
- const rawPath=(args as {planPath:string}).planPath
68
- const root=workspaceRootFor(configuredRoot, exec)
69
- if(!(await isInsideRoot(root, rawPath))) return {text:`Path "${rawPath}" escapes the workspace root.`, truncated:false} as never
70
- const planPath=resolve(root, rawPath)
71
- let md=''
72
- try{ md=await readFile(planPath,'utf-8') }catch{
73
- if(op==='init') return {text:`plan not found ${rawPath}`, truncated:false} as never
74
- md=''
75
- }
76
- const tasks=parseTasks(md)
77
- const lines=md.split('\n')
78
-
79
- if(op==='init'){
80
- const nextTask=tasks.find(t=>t.status==='pending' || t.status==='in_progress') || tasks[0] || null
81
- const dtodoPayload=tasks.map(t=>({content:`${t.id}: ${t.title}`, status: t.status}))
82
- return {ok:true, plan:{path: rawPath, tasks, nextTask, dtodoPayload}, discipline:{violation:null}} as never
83
- }
84
- if(op==='update'){
85
- const taskId=(args as {taskId?:string}).taskId
86
- const status=(args as {status?:string}).status as Task['status'] | undefined
87
- if(!taskId) return {text:'taskId required', truncated:false} as never
88
- const idx=tasks.findIndex(t=>t.id===taskId)
89
- if(idx<0) return {text:`task ${taskId} not found`, truncated:false} as never
90
- if(status==='in_progress'){
91
- const other=tasks.find(t=>t.id!==taskId && t.status==='in_progress')
92
- if(other){
93
- return {ok:false, plan:{path: rawPath, tasks, nextTask: tasks.find(t=>t.status==='in_progress')||null, dtodoPayload:[]}, discipline:{violation:'oneTaskOneCommit: only one task may be in_progress at a time'}} as never
94
- }
95
- }
96
- const lineIdx=tasks[idx].line
97
- if(status){
98
- const orig=lines[lineIdx]
99
- if(orig.includes('—')) lines[lineIdx]=orig.replace(/—.*$/, `— ${status}`)
100
- else lines[lineIdx]=`${orig} — ${status}`
101
- await mkdir(join(planPath,'..'),{recursive:true})
102
- await writeFile(planPath, lines.join('\n'), 'utf-8')
103
- tasks[idx].status=status
104
- }
105
- const nextTask=tasks.find(t=>t.status==='in_progress') || tasks.find(t=>t.status==='pending') || null
106
- return {ok:true, plan:{path: rawPath, tasks, nextTask, dtodoPayload: tasks.map(t=>({content:`${t.id}: ${t.title}`, status:t.status}))}, discipline:{violation:null}} as never
107
- }
108
- if(op==='complete'){
109
- const pending=tasks.filter(t=>t.status!=='done')
110
- if(pending.length>0){
111
- return {ok:false, plan:{path: rawPath, tasks, nextTask: pending[0], dtodoPayload:[]}, discipline:{violation:`pending tasks remain: ${pending.map(p=>p.id).join(',')}`}} as never
112
- }
113
- return {ok:true, plan:{path: rawPath, tasks, nextTask:null, dtodoPayload:[]}, discipline:{violation:null}} as never
114
- }
115
- return {text:`unknown op ${op}`, truncated:false} as never
116
- }
117
- }), 'plan track'))
118
- }
@@ -1,11 +0,0 @@
1
- import type { ReviewProvider, ReviewRequest } from './interface.js'
2
-
3
- export const githubProvider: ReviewProvider = {
4
- id: 'github',
5
- async intake(_req: Request): Promise<ReviewRequest> {
6
- throw new Error('github not implemented')
7
- },
8
- async postFindings(_findings: any[]): Promise<void> {
9
- throw new Error('not implemented')
10
- },
11
- }
@@ -1,2 +0,0 @@
1
- export interface ReviewRequest { provider: string; projectPath: string; mrId: string; profile: 'magento2'|'generic'|'laravel'|'custom'; }
2
- export interface ReviewProvider { id: string; intake(req: Request): Promise<ReviewRequest>; postFindings(f: any[]): Promise<void>; }
@@ -1,85 +0,0 @@
1
- import { resolve, sep } from 'node:path'
2
- import { readFile, stat, realpath } from 'node:fs/promises'
3
- import { execFile } from 'node:child_process'
4
- import { promisify } from 'node:util'
5
- import type { Context } from '@deepseek-ai/cordis'
6
- import { defineTool } from '@deepseek-ai/dsh-tools'
7
- import z from '@deepseek-ai/schemastery'
8
-
9
- export const name='maestro-tdd-evidence-tool'
10
- export const inject=['tools']
11
- export const Config:z<{rootPath?:string}> = z.object({rootPath:z.string()})
12
- interface SC{agent?:{session?:{header?:{cwd?:string}}}}
13
- function workspaceRootFor(c:string|undefined,e:unknown):string{ if(c!==undefined) return c; const cwd=(e as SC|undefined)?.agent?.session?.header?.cwd; return typeof cwd==='string'&&cwd!==''?cwd:process.cwd()}
14
- async function isInsideRoot(r:string,t:string):Promise<boolean>{
15
- const ar=resolve(r); const rs=resolve(ar,t)
16
- if(rs!==ar && !rs.startsWith(ar+sep)) return false
17
- try{ const rr=await realpath(ar); const rt=await realpath(rs); return rt===rr||rt.startsWith(rr+sep)}catch{
18
- try{ const rr=await realpath(ar); const parent=resolve(rs,'..'); const rp=await realpath(parent); return rp===rr||rp.startsWith(rr+sep)}catch{ return rs===ar || rs.startsWith(ar+sep) }
19
- }
20
- }
21
- const execFileAsync=promisify(execFile)
22
-
23
- export function apply(ctx:Context, config:{rootPath?:string}={}):void{
24
- const configuredRoot=config.rootPath
25
- // simple in-memory red evidence store per process
26
- const redEvidence=new Set<string>()
27
- ctx.effect(()=>ctx.tools.register(defineTool({
28
- name:'maestro_tdd_evidence',
29
- description:'Capture TDD evidence: red (failing test), green (passing), verify (pnpm verify). Enforces red-before-green.',
30
- parameters:{
31
- op:{type:'string', enum:['red','green','verify'], required:true},
32
- testFile:{type:'string'},
33
- evidence:{type:'string'},
34
- },
35
- output:{
36
- schema:{
37
- type:'object', additionalProperties:true,
38
- properties:{
39
- ok:{type:'boolean', required:true},
40
- phase:{type:'string'},
41
- error:{type:'string'},
42
- verify:{type:'object', additionalProperties:true},
43
- }
44
- },
45
- render:(_a,v:{ok:boolean, error?:string})=>[{type:'text', text: v.ok? 'ok' : (v.error ?? 'error')}],
46
- },
47
- async execute(args, exec){
48
- const op=(args as {op:string}).op
49
- const rawTest=(args as {testFile?:string}).testFile
50
- const root=workspaceRootFor(configuredRoot, exec)
51
- if(rawTest && !(await isInsideRoot(root, rawTest))) return {text:`Path "${rawTest}" escapes the workspace root.`, truncated:false} as never
52
- if(op==='red'){
53
- if(!rawTest) return {text:'testFile required for red', truncated:false} as never
54
- const abs=resolve(root, rawTest)
55
- try{ const s=await stat(abs); if(!s.isFile()) return {ok:false, phase:'red', error:'test file not found'} as never }catch{ return {ok:false, phase:'red', error:'test file not found'} as never }
56
- const content=await readFile(abs,'utf-8').catch(()=>'')
57
- // red evidence is that file contains failing expectation (throw, expect fail, etc.)
58
- redEvidence.add(rawTest)
59
- return {ok:true, phase:'red', testFile: rawTest, evidence: content.slice(0,500), redEvidenceSize: redEvidence.size} as never
60
- }
61
- if(op==='green'){
62
- if(!rawTest) return {text:'testFile required for green', truncated:false} as never
63
- if(!redEvidence.has(rawTest)){
64
- // also check file exists? If not in redEvidence, error must contain 'red'
65
- return {ok:false, phase:'green', error:'red evidence required before green — run op:red first'} as never
66
- }
67
- const abs=resolve(root, rawTest)
68
- try{ await stat(abs) }catch{ return {ok:false, phase:'green', error:'test file not found'} as never }
69
- return {ok:true, phase:'green', testFile: rawTest} as never
70
- }
71
- if(op==='verify'){
72
- // try to run pnpm verify -- if not available, return ok false but with verify object
73
- try{
74
- const {stdout}=await execFileAsync('pnpm', ['verify'], {cwd:root, timeout:30000}) as any
75
- return {ok:true, phase:'verify', verify:{passed:true, output: String(stdout).slice(0,2000)}} as never
76
- }catch(e:any){
77
- const out=(e.stdout ?? '') + (e.stderr ?? '') + String(e.message)
78
- const passed=out.includes('Tests') && !out.includes('FAIL')
79
- return {ok:passed, phase:'verify', verify:{passed, output: out.slice(0,2000)}, error: passed? undefined : 'verify failed'} as never
80
- }
81
- }
82
- return {text:`unknown op ${op}`, truncated:false} as never
83
- }
84
- }), 'tdd evidence'))
85
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * Webhook secret generation and the URL an operator pastes into GitLab.
3
- *
4
- * Parked at the meta root when the Maestro settings card lost its GitLab tab,
5
- * and never ported: `review` kept the secret FIELD in its own settings section
6
- * but had no way to produce a value for it, so the operator had to invent a
7
- * 32-character base64url string by hand. A pasted secret with a `+`, a `/` or
8
- * a `=` is a common outcome, and those characters are exactly what GitLab's
9
- * token comparison and a URL path segment cannot carry.
10
- */
11
-
12
- const BASE64URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
13
-
14
- /** The merge-request webhook path this plugin serves. */
15
- export const GITLAB_WEBHOOK_PATH = '/hooks/gitlab-mr'
16
-
17
- /**
18
- * Generate an opaque 256-bit secret suitable for GitLab's webhook token.
19
- *
20
- * Each byte contributes 6 bits (`byte & 0b00111111`), so the result is always
21
- * 32 base64url characters with no padding and no character outside the
22
- * alphabet — the two properties GitLab's comparison and a URL path segment
23
- * both need. The randomness source is injectable so the mapping is testable
24
- * without weakening the production path.
25
- */
26
- export function generateWebhookSecret(
27
- randomValues: (array: Uint8Array) => Uint8Array = globalThis.crypto.getRandomValues.bind(globalThis.crypto),
28
- ): string {
29
- const bytes = randomValues(new Uint8Array(32))
30
- return Array.from(bytes, byte => BASE64URL_ALPHABET[byte & 0b00111111]).join('')
31
- }
32
-
33
- /**
34
- * GitLab's merge-request webhook endpoint for the configured public hostname.
35
- *
36
- * A user pasting the value back out of the settings field usually brings a
37
- * scheme and a trailing slash with it, so both are stripped. An absent
38
- * hostname names itself rather than producing a URL that looks real.
39
- */
40
- export function gitlabWebhookUrl(hostname?: string): string {
41
- const authority = hostname?.trim().replace(/^https?:\/\//, '').replace(/\/+$/, '') || '<your-hostname>'
42
- return `https://${authority}${GITLAB_WEBHOOK_PATH}`
43
- }