duoops 0.1.7 → 0.2.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 (64) hide show
  1. package/README.md +151 -63
  2. package/data/aws_machine_power_profiles.json +54 -0
  3. package/data/cpu_physical_specs.json +105 -0
  4. package/data/cpu_power_profiles.json +275 -0
  5. package/data/gcp_machine_power_profiles.json +1802 -0
  6. package/data/runtime-pue-mappings.json +183 -0
  7. package/dist/commands/ask.d.ts +3 -0
  8. package/dist/commands/ask.js +9 -3
  9. package/dist/commands/autofix-ci.d.ts +13 -0
  10. package/dist/commands/autofix-ci.js +114 -0
  11. package/dist/commands/autofix.d.ts +5 -0
  12. package/dist/commands/autofix.js +11 -0
  13. package/dist/commands/config.d.ts +10 -0
  14. package/dist/commands/config.js +47 -0
  15. package/dist/commands/init.js +50 -27
  16. package/dist/commands/mcp/deploy.d.ts +13 -0
  17. package/dist/commands/mcp/deploy.js +139 -0
  18. package/dist/commands/measure/calculate.js +2 -2
  19. package/dist/commands/portal.js +428 -11
  20. package/dist/lib/ai/agent.d.ts +6 -2
  21. package/dist/lib/ai/agent.js +51 -57
  22. package/dist/lib/ai/tools/editing.js +28 -13
  23. package/dist/lib/ai/tools/gitlab.d.ts +4 -0
  24. package/dist/lib/ai/tools/gitlab.js +166 -11
  25. package/dist/lib/ai/tools/measure.js +7 -3
  26. package/dist/lib/ai/tools/types.d.ts +3 -0
  27. package/dist/lib/ai/tools/types.js +1 -0
  28. package/dist/lib/config.d.ts +10 -0
  29. package/dist/lib/gcloud.d.ts +7 -0
  30. package/dist/lib/gcloud.js +105 -0
  31. package/dist/lib/gitlab/pipelines-service.d.ts +23 -0
  32. package/dist/lib/gitlab/pipelines-service.js +146 -0
  33. package/dist/lib/gitlab/runner-service.d.ts +11 -0
  34. package/dist/lib/gitlab/runner-service.js +15 -0
  35. package/dist/lib/portal/settings.d.ts +3 -0
  36. package/dist/lib/portal/settings.js +48 -0
  37. package/dist/lib/scaffold.d.ts +5 -0
  38. package/dist/lib/scaffold.js +32 -0
  39. package/dist/portal/assets/HomeDashboard-DlkwSyKx.js +1 -0
  40. package/dist/portal/assets/JobDetailsDrawer-7kXXMSH8.js +1 -0
  41. package/dist/portal/assets/JobsDashboard-D4pNc9TM.js +1 -0
  42. package/dist/portal/assets/MetricsDashboard-BcgzvzBz.js +1 -0
  43. package/dist/portal/assets/PipelinesDashboard-BNrSM9GB.js +1 -0
  44. package/dist/portal/assets/allPaths-CXDKahbk.js +1 -0
  45. package/dist/portal/assets/allPathsLoader-BF5PAx2c.js +2 -0
  46. package/dist/portal/assets/cache-YerT0Slh.js +6 -0
  47. package/dist/portal/assets/core-Cz8f3oSB.js +19 -0
  48. package/dist/portal/assets/{index-B6bzT1Vv.js → index-B9sNUqEC.js} +1 -1
  49. package/dist/portal/assets/index-BWa_E8Y7.css +1 -0
  50. package/dist/portal/assets/index-Bp4RqK05.js +1 -0
  51. package/dist/portal/assets/index-DW6Qp0d6.js +64 -0
  52. package/dist/portal/assets/index-Uc4Xhv31.js +1 -0
  53. package/dist/portal/assets/progressBar-C4SmnGeZ.js +1 -0
  54. package/dist/portal/assets/splitPathsBySizeLoader-C-T9_API.js +1 -0
  55. package/dist/portal/index.html +2 -2
  56. package/oclif.manifest.json +282 -93
  57. package/package.json +2 -1
  58. package/templates/.gitlab/duo/flows/duoops.yaml +114 -0
  59. package/templates/agents/agent.yml +45 -0
  60. package/templates/duoops-autofix-component.yml +52 -0
  61. package/templates/flows/flow.yml +283 -0
  62. package/dist/portal/assets/MetricsDashboard-DIsoz4Sl.js +0 -71
  63. package/dist/portal/assets/index-BP8FwWqA.css +0 -1
  64. package/dist/portal/assets/index-DkVG3jel.js +0 -70
@@ -0,0 +1 @@
1
+ import{c as m,P as R,x as d,y as P,z as E,j as e,C as S,E as N,e as p}from"./index-DW6Qp0d6.js";const x=n=>{const{animate:r=!0,className:t,intent:l,stripes:i=!0,value:a,...o}=n,u=m(R,d(l),{[E]:!r,[P]:!i},t),s=a==null?void 0:100*N(a,0,1),c=s==null?void 0:s+"%";return e.jsx("div",{...o,"aria-valuemax":100,"aria-valuemin":0,"aria-valuenow":s==null?void 0:Math.round(s),className:u,role:"progressbar",children:e.jsx("div",{className:S,style:{width:c}})})};x.displayName=`${p}.ProgressBar`;export{x as P};
@@ -0,0 +1 @@
1
+ import{a6 as s,v as _,a5 as e}from"./index-DW6Qp0d6.js";const p=async(o,i)=>{const r=s(o);let t;return i===_.STANDARD?t=await e(()=>import("./index-Bp4RqK05.js").then(a=>a.I),[]):t=await e(()=>import("./index-Uc4Xhv31.js").then(a=>a.I),[]),t[r]};export{p as splitPathsBySizeLoader};
@@ -15,8 +15,8 @@
15
15
  <meta property="og:description" content="Explainable and Sustainable CI on GitLab with AI-powered insights." />
16
16
 
17
17
  <meta name="robots" content="noindex, nofollow" />
18
- <script type="module" crossorigin src="/assets/index-DkVG3jel.js"></script>
19
- <link rel="stylesheet" crossorigin href="/assets/index-BP8FwWqA.css">
18
+ <script type="module" crossorigin src="/assets/index-DW6Qp0d6.js"></script>
19
+ <link rel="stylesheet" crossorigin href="/assets/index-BWa_E8Y7.css">
20
20
  </head>
21
21
  <body>
22
22
  <div id="root"></div>
@@ -50,7 +50,16 @@
50
50
  }
51
51
  },
52
52
  "description": "Ask questions about your CI/CD pipelines, logs, and sustainability",
53
- "flags": {},
53
+ "flags": {
54
+ "project": {
55
+ "char": "P",
56
+ "description": "GitLab Project ID to contextually use",
57
+ "name": "project",
58
+ "hasDynamicHelp": false,
59
+ "multiple": false,
60
+ "type": "option"
61
+ }
62
+ },
54
63
  "hasDynamicHelp": false,
55
64
  "hiddenAliases": [],
56
65
  "id": "ask",
@@ -66,6 +75,117 @@
66
75
  "ask.js"
67
76
  ]
68
77
  },
78
+ "autofix-ci": {
79
+ "aliases": [],
80
+ "args": {
81
+ "project-id": {
82
+ "description": "GitLab project ID or path (falls back to DUOOPS_PROJECT_ID, CI_PROJECT_ID, or duoops init default)",
83
+ "name": "project-id",
84
+ "required": false
85
+ }
86
+ },
87
+ "description": "Headless autofix: analyze the latest failing pipeline and post results to an MR or stdout",
88
+ "examples": [
89
+ "<%= config.bin %> <%= command.id %>",
90
+ "<%= config.bin %> <%= command.id %> 12345 --mr 42",
91
+ "<%= config.bin %> <%= command.id %> my-group/my-project --pipeline 99999"
92
+ ],
93
+ "flags": {
94
+ "mr": {
95
+ "description": "Merge request IID to comment the analysis on",
96
+ "name": "mr",
97
+ "hasDynamicHelp": false,
98
+ "multiple": false,
99
+ "type": "option"
100
+ },
101
+ "pipeline": {
102
+ "description": "Pipeline ID to analyze (defaults to latest failed)",
103
+ "name": "pipeline",
104
+ "hasDynamicHelp": false,
105
+ "multiple": false,
106
+ "type": "option"
107
+ }
108
+ },
109
+ "hasDynamicHelp": false,
110
+ "hiddenAliases": [],
111
+ "id": "autofix-ci",
112
+ "pluginAlias": "duoops",
113
+ "pluginName": "duoops",
114
+ "pluginType": "core",
115
+ "strict": true,
116
+ "enableJsonFlag": false,
117
+ "isESM": true,
118
+ "relativePath": [
119
+ "dist",
120
+ "commands",
121
+ "autofix-ci.js"
122
+ ]
123
+ },
124
+ "autofix": {
125
+ "aliases": [],
126
+ "args": {},
127
+ "description": "Launch the DuoOps portal and pre-fill an autofix request to the agent",
128
+ "flags": {
129
+ "port": {
130
+ "char": "p",
131
+ "description": "Port to run the portal on",
132
+ "name": "port",
133
+ "default": 58327,
134
+ "hasDynamicHelp": false,
135
+ "multiple": false,
136
+ "type": "option"
137
+ }
138
+ },
139
+ "hasDynamicHelp": false,
140
+ "hiddenAliases": [],
141
+ "id": "autofix",
142
+ "pluginAlias": "duoops",
143
+ "pluginName": "duoops",
144
+ "pluginType": "core",
145
+ "strict": true,
146
+ "enableJsonFlag": false,
147
+ "isESM": true,
148
+ "relativePath": [
149
+ "dist",
150
+ "commands",
151
+ "autofix.js"
152
+ ]
153
+ },
154
+ "config": {
155
+ "aliases": [],
156
+ "args": {
157
+ "key": {
158
+ "description": "Config key (e.g., gitlabUrl, defaultProjectId)",
159
+ "name": "key",
160
+ "required": true
161
+ },
162
+ "value": {
163
+ "description": "Config value",
164
+ "name": "value",
165
+ "required": true
166
+ }
167
+ },
168
+ "description": "Set a configuration value directly",
169
+ "examples": [
170
+ "<%= config.bin %> <%= command.id %> gitlabUrl https://gitlab.com",
171
+ "<%= config.bin %> <%= command.id %> defaultProjectId 123456"
172
+ ],
173
+ "flags": {},
174
+ "hasDynamicHelp": false,
175
+ "hiddenAliases": [],
176
+ "id": "config",
177
+ "pluginAlias": "duoops",
178
+ "pluginName": "duoops",
179
+ "pluginType": "core",
180
+ "strict": true,
181
+ "enableJsonFlag": false,
182
+ "isESM": true,
183
+ "relativePath": [
184
+ "dist",
185
+ "commands",
186
+ "config.js"
187
+ ]
188
+ },
69
189
  "init": {
70
190
  "aliases": [],
71
191
  "args": {},
@@ -95,7 +215,7 @@
95
215
  "char": "p",
96
216
  "description": "Port to run the portal on",
97
217
  "name": "port",
98
- "default": 3000,
218
+ "default": 58327,
99
219
  "hasDynamicHelp": false,
100
220
  "multiple": false,
101
221
  "type": "option"
@@ -171,6 +291,165 @@
171
291
  "logs.js"
172
292
  ]
173
293
  },
294
+ "mcp:deploy": {
295
+ "aliases": [],
296
+ "args": {},
297
+ "description": "Deploy the DuoOps MCP server to Google Cloud Run",
298
+ "flags": {
299
+ "bq-dataset": {
300
+ "description": "BigQuery Dataset ID",
301
+ "name": "bq-dataset",
302
+ "hasDynamicHelp": false,
303
+ "multiple": false,
304
+ "type": "option"
305
+ },
306
+ "bq-table": {
307
+ "description": "BigQuery Table ID",
308
+ "name": "bq-table",
309
+ "hasDynamicHelp": false,
310
+ "multiple": false,
311
+ "type": "option"
312
+ },
313
+ "gcp-project": {
314
+ "char": "p",
315
+ "description": "Google Cloud Project ID",
316
+ "name": "gcp-project",
317
+ "hasDynamicHelp": false,
318
+ "multiple": false,
319
+ "type": "option"
320
+ },
321
+ "gitlab-url": {
322
+ "description": "GitLab instance URL",
323
+ "name": "gitlab-url",
324
+ "default": "https://gitlab.com",
325
+ "hasDynamicHelp": false,
326
+ "multiple": false,
327
+ "type": "option"
328
+ },
329
+ "region": {
330
+ "char": "r",
331
+ "description": "Cloud Run region",
332
+ "name": "region",
333
+ "default": "us-central1",
334
+ "hasDynamicHelp": false,
335
+ "multiple": false,
336
+ "type": "option"
337
+ },
338
+ "source": {
339
+ "description": "Path to MCP server source directory",
340
+ "name": "source",
341
+ "default": "",
342
+ "hasDynamicHelp": false,
343
+ "multiple": false,
344
+ "type": "option"
345
+ }
346
+ },
347
+ "hasDynamicHelp": false,
348
+ "hiddenAliases": [],
349
+ "id": "mcp:deploy",
350
+ "pluginAlias": "duoops",
351
+ "pluginName": "duoops",
352
+ "pluginType": "core",
353
+ "strict": true,
354
+ "enableJsonFlag": false,
355
+ "isESM": true,
356
+ "relativePath": [
357
+ "dist",
358
+ "commands",
359
+ "mcp",
360
+ "deploy.js"
361
+ ]
362
+ },
363
+ "pipelines:list": {
364
+ "aliases": [],
365
+ "args": {
366
+ "project": {
367
+ "description": "Project ID or path (e.g. group/project)",
368
+ "name": "project",
369
+ "required": false
370
+ }
371
+ },
372
+ "description": "List GitLab CI pipelines for a project",
373
+ "examples": [
374
+ "<%= config.bin %> <%= command.id %> group/my-project",
375
+ "<%= config.bin %> <%= command.id %> 123 --limit 20 --ref main"
376
+ ],
377
+ "flags": {
378
+ "limit": {
379
+ "char": "n",
380
+ "description": "Maximum number of pipelines to return",
381
+ "name": "limit",
382
+ "default": 10,
383
+ "hasDynamicHelp": false,
384
+ "multiple": false,
385
+ "type": "option"
386
+ },
387
+ "ref": {
388
+ "description": "Filter by branch or tag",
389
+ "name": "ref",
390
+ "hasDynamicHelp": false,
391
+ "multiple": false,
392
+ "type": "option"
393
+ },
394
+ "status": {
395
+ "description": "Filter by status (created, pending, running, success, failed, canceled, skipped, manual, scheduled)",
396
+ "name": "status",
397
+ "hasDynamicHelp": false,
398
+ "multiple": false,
399
+ "type": "option"
400
+ }
401
+ },
402
+ "hasDynamicHelp": false,
403
+ "hiddenAliases": [],
404
+ "id": "pipelines:list",
405
+ "pluginAlias": "duoops",
406
+ "pluginName": "duoops",
407
+ "pluginType": "core",
408
+ "strict": true,
409
+ "enableJsonFlag": false,
410
+ "isESM": true,
411
+ "relativePath": [
412
+ "dist",
413
+ "commands",
414
+ "pipelines",
415
+ "list.js"
416
+ ]
417
+ },
418
+ "pipelines:show": {
419
+ "aliases": [],
420
+ "args": {
421
+ "project": {
422
+ "description": "Project ID (e.g. 123456)",
423
+ "name": "project",
424
+ "required": true
425
+ },
426
+ "pipeline_id": {
427
+ "description": "Pipeline ID",
428
+ "name": "pipeline_id",
429
+ "required": true
430
+ }
431
+ },
432
+ "description": "Show pipeline details and jobs",
433
+ "examples": [
434
+ "<%= config.bin %> <%= command.id %> 12345 67890"
435
+ ],
436
+ "flags": {},
437
+ "hasDynamicHelp": false,
438
+ "hiddenAliases": [],
439
+ "id": "pipelines:show",
440
+ "pluginAlias": "duoops",
441
+ "pluginName": "duoops",
442
+ "pluginType": "core",
443
+ "strict": true,
444
+ "enableJsonFlag": false,
445
+ "isESM": true,
446
+ "relativePath": [
447
+ "dist",
448
+ "commands",
449
+ "pipelines",
450
+ "show.js"
451
+ ]
452
+ },
174
453
  "measure:calculate": {
175
454
  "aliases": [],
176
455
  "args": {},
@@ -320,96 +599,6 @@
320
599
  "seed.js"
321
600
  ]
322
601
  },
323
- "pipelines:list": {
324
- "aliases": [],
325
- "args": {
326
- "project": {
327
- "description": "Project ID or path (e.g. group/project)",
328
- "name": "project",
329
- "required": false
330
- }
331
- },
332
- "description": "List GitLab CI pipelines for a project",
333
- "examples": [
334
- "<%= config.bin %> <%= command.id %> group/my-project",
335
- "<%= config.bin %> <%= command.id %> 123 --limit 20 --ref main"
336
- ],
337
- "flags": {
338
- "limit": {
339
- "char": "n",
340
- "description": "Maximum number of pipelines to return",
341
- "name": "limit",
342
- "default": 10,
343
- "hasDynamicHelp": false,
344
- "multiple": false,
345
- "type": "option"
346
- },
347
- "ref": {
348
- "description": "Filter by branch or tag",
349
- "name": "ref",
350
- "hasDynamicHelp": false,
351
- "multiple": false,
352
- "type": "option"
353
- },
354
- "status": {
355
- "description": "Filter by status (created, pending, running, success, failed, canceled, skipped, manual, scheduled)",
356
- "name": "status",
357
- "hasDynamicHelp": false,
358
- "multiple": false,
359
- "type": "option"
360
- }
361
- },
362
- "hasDynamicHelp": false,
363
- "hiddenAliases": [],
364
- "id": "pipelines:list",
365
- "pluginAlias": "duoops",
366
- "pluginName": "duoops",
367
- "pluginType": "core",
368
- "strict": true,
369
- "enableJsonFlag": false,
370
- "isESM": true,
371
- "relativePath": [
372
- "dist",
373
- "commands",
374
- "pipelines",
375
- "list.js"
376
- ]
377
- },
378
- "pipelines:show": {
379
- "aliases": [],
380
- "args": {
381
- "project": {
382
- "description": "Project ID (e.g. 123456)",
383
- "name": "project",
384
- "required": true
385
- },
386
- "pipeline_id": {
387
- "description": "Pipeline ID",
388
- "name": "pipeline_id",
389
- "required": true
390
- }
391
- },
392
- "description": "Show pipeline details and jobs",
393
- "examples": [
394
- "<%= config.bin %> <%= command.id %> 12345 67890"
395
- ],
396
- "flags": {},
397
- "hasDynamicHelp": false,
398
- "hiddenAliases": [],
399
- "id": "pipelines:show",
400
- "pluginAlias": "duoops",
401
- "pluginName": "duoops",
402
- "pluginType": "core",
403
- "strict": true,
404
- "enableJsonFlag": false,
405
- "isESM": true,
406
- "relativePath": [
407
- "dist",
408
- "commands",
409
- "pipelines",
410
- "show.js"
411
- ]
412
- },
413
602
  "runner:logs": {
414
603
  "aliases": [],
415
604
  "args": {},
@@ -480,5 +669,5 @@
480
669
  ]
481
670
  }
482
671
  },
483
- "version": "0.1.7"
672
+ "version": "0.2.0"
484
673
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "duoops",
3
3
  "description": "Toolset for Explainable and Sustainable CI on Gitlab.",
4
- "version": "0.1.7",
4
+ "version": "0.2.0",
5
5
  "author": "Younes Laaroussi",
6
6
  "bin": {
7
7
  "duoops": "./bin/run.js"
@@ -61,6 +61,7 @@
61
61
  "files": [
62
62
  "./bin",
63
63
  "./dist",
64
+ "./data",
64
65
  "./oclif.manifest.json",
65
66
  "./templates"
66
67
  ],
@@ -0,0 +1,114 @@
1
+ image: node:22-slim
2
+ commands:
3
+ - echo "=== Installing DuoOps CLI ==="
4
+ - npm install -g duoops@latest
5
+
6
+ - echo "=== Installing glab CLI ==="
7
+ - apt-get update --quiet && apt-get install --yes curl wget gpg git python3 && rm --recursive --force /var/lib/apt/lists/*
8
+ - curl --silent --show-error --location "https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository" | bash
9
+ - apt-get install -y glab
10
+
11
+ - echo "=== Configuring glab ==="
12
+ - mkdir -p ~/.config/glab-cli
13
+ - |
14
+ cat > ~/.config/glab-cli/config.yml <<EOF
15
+ hosts:
16
+ $AI_FLOW_GITLAB_HOSTNAME:
17
+ token: $AI_FLOW_GITLAB_TOKEN
18
+ is_oauth2: "true"
19
+ client_id: "bypass"
20
+ oauth2_refresh_token: ""
21
+ oauth2_expiry_date: "01 Jan 50 00:00 UTC"
22
+ api_host: $AI_FLOW_GITLAB_HOSTNAME
23
+ user: DuoOps
24
+ check_update: "false"
25
+ git_protocol: https
26
+ EOF
27
+ - chmod 600 ~/.config/glab-cli/config.yml
28
+
29
+ - echo "=== Configuring git ==="
30
+ - git config --global user.email "duoops@gitlab.com"
31
+ - git config --global user.name "DuoOps Agent"
32
+ - git remote set-url origin https://gitlab-ci-token:$AI_FLOW_GITLAB_TOKEN@$AI_FLOW_GITLAB_HOSTNAME/$AI_FLOW_PROJECT_PATH.git
33
+
34
+ - echo "=== Configuring DuoOps ==="
35
+ - export GITLAB_TOKEN=$AI_FLOW_GITLAB_TOKEN
36
+ - export GITLAB_HOST=https://$AI_FLOW_GITLAB_HOSTNAME
37
+ - export DUOOPS_PROJECT_ID=$AI_FLOW_PROJECT_PATH
38
+
39
+ - echo "=== Running DuoOps Agent ==="
40
+ - |
41
+ INPUT="$AI_FLOW_INPUT"
42
+ CONTEXT="$AI_FLOW_CONTEXT"
43
+ EVENT="$AI_FLOW_EVENT"
44
+
45
+ # Extract MR IID from context if available
46
+ MR_IID=$(echo "$CONTEXT" | python3 -c "
47
+ import sys, json
48
+ try:
49
+ ctx = json.load(sys.stdin)
50
+ iid = ctx.get('merge_request', {}).get('iid', '')
51
+ if iid: print(iid)
52
+ else: print('')
53
+ except: print('')
54
+ " 2>/dev/null || echo "")
55
+
56
+ # Extract Issue IID from context if available
57
+ ISSUE_IID=$(echo "$CONTEXT" | python3 -c "
58
+ import sys, json
59
+ try:
60
+ ctx = json.load(sys.stdin)
61
+ iid = ctx.get('issue', {}).get('iid', '')
62
+ if iid: print(iid)
63
+ else: print('')
64
+ except: print('')
65
+ " 2>/dev/null || echo "")
66
+
67
+ echo "Event: $EVENT"
68
+ echo "MR IID: $MR_IID"
69
+ echo "Issue IID: $ISSUE_IID"
70
+ echo "Input: $INPUT"
71
+
72
+ # Route to the appropriate DuoOps command based on intent
73
+ if echo "$INPUT" | grep -qiE "fix|fail|broken|debug|pipeline|autofix|error|crash|ci"; then
74
+ echo ">>> Pipeline failure analysis mode"
75
+ if [ -n "$MR_IID" ]; then
76
+ OUTPUT=$(duoops autofix-ci "$AI_FLOW_PROJECT_PATH" --mr "$MR_IID" 2>&1)
77
+ echo "$OUTPUT"
78
+ echo "Analysis posted to MR !$MR_IID"
79
+ else
80
+ OUTPUT=$(duoops autofix-ci "$AI_FLOW_PROJECT_PATH" 2>&1)
81
+ fi
82
+
83
+ elif echo "$INPUT" | grep -qiE "carbon|emission|sustain|green|measure|energy|footprint|budget"; then
84
+ echo ">>> Sustainability analysis mode"
85
+ OUTPUT=$(duoops act "$AI_FLOW_PROJECT_PATH" --limit 20 2>&1)
86
+
87
+ else
88
+ echo ">>> General query mode"
89
+ OUTPUT=$(duoops ask "$INPUT" -P "$AI_FLOW_PROJECT_PATH" 2>&1)
90
+ fi
91
+
92
+ # Post results back if we haven't already (autofix-ci with --mr handles its own posting)
93
+ if echo "$INPUT" | grep -qiE "fix|fail|broken|debug|pipeline|autofix|error|crash|ci" && [ -n "$MR_IID" ]; then
94
+ echo "Results already posted to MR"
95
+ elif [ -n "$MR_IID" ]; then
96
+ glab mr note "$MR_IID" --message "## DuoOps Agent Response
97
+
98
+ $OUTPUT
99
+
100
+ ---
101
+ *Generated by [DuoOps](https://gitlab.com/youneslaaroussi/duoops) — Explainable & Sustainable CI*"
102
+ elif [ -n "$ISSUE_IID" ]; then
103
+ glab issue note "$ISSUE_IID" --message "## DuoOps Agent Response
104
+
105
+ $OUTPUT
106
+
107
+ ---
108
+ *Generated by [DuoOps](https://gitlab.com/youneslaaroussi/duoops) — Explainable & Sustainable CI*"
109
+ else
110
+ echo "=== DuoOps Output ==="
111
+ echo "$OUTPUT"
112
+ fi
113
+ variables:
114
+ - ADDITIONAL_INSTRUCTIONS
@@ -0,0 +1,45 @@
1
+ name: "DuoOps Advisor"
2
+ description: "AI-powered CI/CD troubleshooting and carbon sustainability agent. Analyzes pipeline failures, fetches job logs, proposes fixes, and provides carbon emission insights for sustainable CI practices."
3
+ public: true
4
+ system_prompt: |
5
+ You are DuoOps, an AI DevOps assistant specializing in CI/CD reliability and sustainability on GitLab.
6
+
7
+ Your capabilities:
8
+ - Analyze pipeline failures by fetching job logs and identifying root causes
9
+ - Propose concrete fixes with specific CI configuration changes
10
+ - Measure and optimize the carbon footprint of CI/CD pipelines
11
+ - Review merge requests for CI/CD best practices and sustainability
12
+
13
+ When analyzing pipeline failures:
14
+ 1. First use get_pipeline_failing_jobs or get_pipeline_errors to identify which jobs failed
15
+ 2. Use get_job_logs to fetch the full logs of failed jobs
16
+ 3. Identify the root cause from error messages and stack traces
17
+ 4. Propose specific remediation steps referencing jobs and stages
18
+ 5. Suggest CI configuration changes to prevent regressions
19
+
20
+ When reviewing sustainability:
21
+ - Look for redundant work (repeated npm install, uncached dependencies)
22
+ - Identify jobs that could run in parallel
23
+ - Suggest smaller runner machine types where appropriate
24
+ - Recommend caching strategies to reduce compute time and emissions
25
+
26
+ Always respond with clear Markdown formatting. Be concise and actionable.
27
+ When posting notes to merge requests or issues, structure your response with clear sections.
28
+ tools:
29
+ - get_job_logs
30
+ - get_pipeline_errors
31
+ - get_pipeline_failing_jobs
32
+ - get_merge_request
33
+ - list_merge_request_diffs
34
+ - create_merge_request_note
35
+ - create_issue_note
36
+ - get_issue
37
+ - get_repository_file
38
+ - read_file
39
+ - read_files
40
+ - list_dir
41
+ - find_files
42
+ - grep
43
+ - get_project
44
+ - list_commits
45
+ - get_commit_diff
@@ -0,0 +1,52 @@
1
+ spec:
2
+ inputs:
3
+ duoops_version:
4
+ description: "duoops npm package version"
5
+ type: string
6
+ default: "latest"
7
+
8
+ stage:
9
+ description: "GitLab CI stage to run in"
10
+ type: string
11
+ default: ".post"
12
+
13
+ tags:
14
+ description: "Runner tags"
15
+ type: array
16
+ default: []
17
+
18
+ ---
19
+
20
+ duoops-autofix-analysis:
21
+ stage: $[[ inputs.stage ]]
22
+ tags: $[[ inputs.tags ]]
23
+ image: node:22-slim
24
+ variables:
25
+ DUOOPS_VERSION: $[[ inputs.duoops_version ]]
26
+ before_script:
27
+ - |
28
+ if command -v duoops &> /dev/null; then
29
+ echo "duoops already installed"
30
+ else
31
+ apt-get update && apt-get install -y git
32
+ npm install -g duoops@${DUOOPS_VERSION}
33
+ fi
34
+ script:
35
+ - |
36
+ export GITLAB_TOKEN="${GITLAB_TOKEN:-$CI_JOB_TOKEN}"
37
+
38
+ MR_FLAG=""
39
+ if [ -n "$CI_MERGE_REQUEST_IID" ]; then
40
+ MR_FLAG="--mr $CI_MERGE_REQUEST_IID"
41
+ fi
42
+
43
+ duoops autofix-ci "$CI_PROJECT_ID" --pipeline "$CI_PIPELINE_ID" $MR_FLAG | tee autofix-report.md
44
+
45
+ echo ""
46
+ echo "=== DuoOps Autofix Complete ==="
47
+ artifacts:
48
+ paths:
49
+ - autofix-report.md
50
+ expire_in: 30 days
51
+ rules:
52
+ - when: on_failure