@link-assistant/hive-mind 2.7.4 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.hi.md +22 -0
- package/README.md +24 -0
- package/README.ru.md +21 -0
- package/README.zh.md +20 -0
- package/package.json +3 -2
- package/src/fix.ci-cd.lib.mjs +480 -0
- package/src/fix.mjs +242 -0
- package/src/locales/en.lino +6 -1
- package/src/locales/hi.lino +6 -1
- package/src/locales/ru.lino +6 -1
- package/src/locales/zh.lino +6 -1
- package/src/task.issue-creation.lib.mjs +31 -5
- package/src/telegram-bot.mjs +10 -85
- package/src/telegram-fix-command.lib.mjs +164 -0
- package/src/telegram-ui-messages.lib.mjs +11 -1
- package/src/telegram.config.lib.mjs +96 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 2.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8d02cf6: feat(fix): add `/fix --ci-cd <repository>` command (#1733)
|
|
8
|
+
|
|
9
|
+
`/fix --ci-cd` automatically generates and solves a CI/CD remediation issue for
|
|
10
|
+
a target repository:
|
|
11
|
+
|
|
12
|
+
- detects the repository's languages via the GitHub `/languages` API and selects
|
|
13
|
+
the matching CI/CD templates, ordered by detected language (byte-weighted);
|
|
14
|
+
- inspects the latest default-branch commit and lists its CI/CD runs in the issue
|
|
15
|
+
(falling back to the most recent default-branch runs when a release/tag commit
|
|
16
|
+
has none of its own);
|
|
17
|
+
- creates the remediation issue reusing the title and description of the standard
|
|
18
|
+
prompt (web-capture#139), with its retired case-study paragraph permanently
|
|
19
|
+
replaced by `--development-log` and the paragraphs supplied by `--deep-analysis`
|
|
20
|
+
omitted when that option is enabled. The issue is created as type `Bug`
|
|
21
|
+
(best-effort, since issue types are org-scoped) because that is what makes the
|
|
22
|
+
deep-analysis omission valid, and links to `docs/CI-CD-BEST-PRACTICES.md`;
|
|
23
|
+
- hands the issue off to `/solve --development-log --deep-analysis --auto-merge`,
|
|
24
|
+
forwarding every option `/fix` does not consume itself (e.g. `--tool`,
|
|
25
|
+
`--model`, `--think`);
|
|
26
|
+
- supports `--dry-run` (preview the issue) and `--no-solve` (create only).
|
|
27
|
+
|
|
28
|
+
The command is also available from the Telegram bot as `/fix <repository>`
|
|
29
|
+
(`--ci-cd` is implied), toggleable with `--no-fix` / `TELEGRAM_FIX=false` and
|
|
30
|
+
documented in all four locales.
|
|
31
|
+
|
|
32
|
+
Also adds the PHP template
|
|
33
|
+
(`link-foundation/php-ai-driven-development-pipeline-template`) to
|
|
34
|
+
`docs/CI-CD-BEST-PRACTICES.md` and documents the new "Automatic CI/CD
|
|
35
|
+
Remediation" flow in all four languages (en/zh/hi/ru).
|
|
36
|
+
|
|
3
37
|
## 2.7.4
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
package/README.hi.md
CHANGED
|
@@ -532,6 +532,28 @@ message पर `/merge` से reply भी कर सकते हैं जि
|
|
|
532
532
|
mergeable होने तक wait करता है। Merge-conflict skips अभी भी `--auto-resolve` के
|
|
533
533
|
साथ काम करते हैं।
|
|
534
534
|
|
|
535
|
+
#### `/fix` - CI/CD स्वतः ठीक करें
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
/fix <github-repository-url> --ci-cd [options]
|
|
539
|
+
|
|
540
|
+
Examples:
|
|
541
|
+
/fix https://github.com/owner/repo --ci-cd
|
|
542
|
+
/fix owner/repo --ci-cd --tool codex --model gpt-5.5 --think max
|
|
543
|
+
/fix owner/repo --ci-cd --dry-run
|
|
544
|
+
/fix owner/repo --ci-cd --no-solve
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
`/fix --ci-cd` लक्ष्य रिपॉज़िटरी की भाषाओं का पता लगाता है, डिफ़ॉल्ट ब्रांच के नवीनतम कमिट और उसके CI/CD रनों
|
|
548
|
+
की जाँच करता है, और स्वचालित रूप से एक CI/CD सुधार issue बनाता है (मानक प्रॉम्प्ट और
|
|
549
|
+
[`docs/CI-CD-BEST-PRACTICES.md`](docs/CI-CD-BEST-PRACTICES.md) के टेम्पलेट्स का उपयोग करके, जिन्हें पहचानी
|
|
550
|
+
गई भाषाओं के अनुसार क्रमबद्ध किया जाता है)। इसके बाद यह issue को
|
|
551
|
+
`/solve --development-log --deep-analysis --auto-merge` को सौंप देता है। हर वह विकल्प जिसे `/fix` स्वयं
|
|
552
|
+
उपयोग नहीं करता (जैसे `--tool`, `--model`, `--think`) `/solve` को अग्रेषित कर दिया जाता है। issue बनाए बिना
|
|
553
|
+
उसका पूर्वावलोकन करने के लिए `--dry-run` का उपयोग करें, या `/solve` शुरू किए बिना केवल issue बनाने के लिए
|
|
554
|
+
`--no-solve` का उपयोग करें। विवरण के लिए
|
|
555
|
+
[स्वचालित CI/CD सुधार](docs/CI-CD-BEST-PRACTICES.md#automatic-cicd-remediation) देखें।
|
|
556
|
+
|
|
535
557
|
#### `/limits` - उपयोग सीमाएँ दिखाएँ
|
|
536
558
|
|
|
537
559
|
```
|
package/README.md
CHANGED
|
@@ -551,6 +551,30 @@ request link, such as a previous `/codex ...issues/123` command.
|
|
|
551
551
|
If a target PR is not finished yet, `/merge` waits for it to become mergeable
|
|
552
552
|
before merging. Merge-conflict skips still work with `--auto-resolve`.
|
|
553
553
|
|
|
554
|
+
#### `/fix` - Auto-Remediate CI/CD
|
|
555
|
+
|
|
556
|
+
```
|
|
557
|
+
/fix <github-repository-url> --ci-cd [options]
|
|
558
|
+
|
|
559
|
+
Examples:
|
|
560
|
+
/fix https://github.com/owner/repo --ci-cd
|
|
561
|
+
/fix owner/repo --ci-cd --tool codex --model gpt-5.5 --think max
|
|
562
|
+
/fix owner/repo --ci-cd --dry-run
|
|
563
|
+
/fix owner/repo --ci-cd --no-solve
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
`/fix --ci-cd` detects the target repository's languages, inspects the latest
|
|
567
|
+
default-branch commit and its CI/CD runs, and automatically creates a CI/CD
|
|
568
|
+
remediation issue (using the standard prompt and the templates from
|
|
569
|
+
[`docs/CI-CD-BEST-PRACTICES.md`](docs/CI-CD-BEST-PRACTICES.md), sorted by the
|
|
570
|
+
detected languages). It then hands the issue off to
|
|
571
|
+
`/solve --development-log --deep-analysis --auto-merge`. Every option `/fix`
|
|
572
|
+
does not consume itself (e.g. `--tool`, `--model`, `--think`) is forwarded to
|
|
573
|
+
`/solve`. Use `--dry-run` to preview the issue without creating it, or
|
|
574
|
+
`--no-solve` to create the issue without starting `/solve`. See
|
|
575
|
+
[Automatic CI/CD Remediation](docs/CI-CD-BEST-PRACTICES.md#automatic-cicd-remediation)
|
|
576
|
+
for details.
|
|
577
|
+
|
|
554
578
|
#### `/limits` - Show Usage Limits
|
|
555
579
|
|
|
556
580
|
```
|
package/README.ru.md
CHANGED
|
@@ -533,6 +533,27 @@ pull request, например на предыдущую команду `/codex
|
|
|
533
533
|
выполняет слияние. Пропуск конфликтов слияния по-прежнему работает с
|
|
534
534
|
`--auto-resolve`.
|
|
535
535
|
|
|
536
|
+
#### `/fix` — Автоматическое исправление CI/CD
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
/fix <github-repository-url> --ci-cd [options]
|
|
540
|
+
|
|
541
|
+
Examples:
|
|
542
|
+
/fix https://github.com/owner/repo --ci-cd
|
|
543
|
+
/fix owner/repo --ci-cd --tool codex --model gpt-5.5 --think max
|
|
544
|
+
/fix owner/repo --ci-cd --dry-run
|
|
545
|
+
/fix owner/repo --ci-cd --no-solve
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
`/fix --ci-cd` определяет языки целевого репозитория, анализирует последний коммит ветки по умолчанию и его
|
|
549
|
+
запуски CI/CD и автоматически создаёт issue для исправления CI/CD (используя стандартный промпт и шаблоны из
|
|
550
|
+
[`docs/CI-CD-BEST-PRACTICES.md`](docs/CI-CD-BEST-PRACTICES.md), отсортированные по обнаруженным языкам). Затем
|
|
551
|
+
он передаёт issue в `/solve --development-log --deep-analysis --auto-merge`. Любая опция, которую `/fix` не
|
|
552
|
+
использует сам (например, `--tool`, `--model`, `--think`), передаётся в `/solve`. Используйте `--dry-run`,
|
|
553
|
+
чтобы предварительно просмотреть issue без его создания, или `--no-solve`, чтобы создать issue без запуска
|
|
554
|
+
`/solve`. Подробнее см.
|
|
555
|
+
[Автоматическое исправление CI/CD](docs/CI-CD-BEST-PRACTICES.md#automatic-cicd-remediation).
|
|
556
|
+
|
|
536
557
|
#### `/limits` — Показать лимиты использования
|
|
537
558
|
|
|
538
559
|
```
|
package/README.zh.md
CHANGED
|
@@ -528,6 +528,26 @@ Examples:
|
|
|
528
528
|
如果目标 PR 尚未完成,`/merge` 会等待它变为可合并后再合并。合并冲突跳过仍可配合
|
|
529
529
|
`--auto-resolve` 使用。
|
|
530
530
|
|
|
531
|
+
#### `/fix` - 自动修复 CI/CD
|
|
532
|
+
|
|
533
|
+
```
|
|
534
|
+
/fix <github-repository-url> --ci-cd [options]
|
|
535
|
+
|
|
536
|
+
Examples:
|
|
537
|
+
/fix https://github.com/owner/repo --ci-cd
|
|
538
|
+
/fix owner/repo --ci-cd --tool codex --model gpt-5.5 --think max
|
|
539
|
+
/fix owner/repo --ci-cd --dry-run
|
|
540
|
+
/fix owner/repo --ci-cd --no-solve
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
`/fix --ci-cd` 会检测目标仓库使用的语言,检查默认分支的最新提交及其 CI/CD 运行情况,并自动创建一个 CI/CD
|
|
544
|
+
修复 issue(使用标准提示词,以及来自
|
|
545
|
+
[`docs/CI-CD-BEST-PRACTICES.md`](docs/CI-CD-BEST-PRACTICES.md) 的模板,按检测到的语言排序)。随后它会将该
|
|
546
|
+
issue 交给 `/solve --development-log --deep-analysis --auto-merge` 处理。所有 `/fix`
|
|
547
|
+
自身不消费的选项(例如 `--tool`、`--model`、`--think`)都会转发给 `/solve`。使用 `--dry-run` 可在不创建
|
|
548
|
+
issue 的情况下预览,使用 `--no-solve` 可只创建 issue 而不启动 `/solve`。详见
|
|
549
|
+
[自动 CI/CD 修复](docs/CI-CD-BEST-PRACTICES.md#automatic-cicd-remediation)。
|
|
550
|
+
|
|
531
551
|
#### `/limits` - 显示用量限制
|
|
532
552
|
|
|
533
553
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"hive": "./src/hive.mjs",
|
|
9
9
|
"solve": "./src/solve.mjs",
|
|
10
10
|
"task": "./src/task.mjs",
|
|
11
|
+
"fix": "./src/fix.mjs",
|
|
11
12
|
"hive-cleanup": "./src/cleanup.mjs",
|
|
12
13
|
"review": "./src/review.mjs",
|
|
13
14
|
"configure-claude": "./src/configure-claude.mjs",
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
"changeset": "changeset",
|
|
29
30
|
"changeset:version": "changeset version",
|
|
30
31
|
"changeset:publish": "npm run build:pre && changeset publish",
|
|
31
|
-
"build:pre": "chmod +x src/hive.mjs && chmod +x src/solve.mjs && chmod +x src/configure-claude.mjs && chmod +x src/hive-screens.mjs",
|
|
32
|
+
"build:pre": "chmod +x src/hive.mjs && chmod +x src/solve.mjs && chmod +x src/fix.mjs && chmod +x src/configure-claude.mjs && chmod +x src/hive-screens.mjs",
|
|
32
33
|
"prepare": "husky"
|
|
33
34
|
},
|
|
34
35
|
"repository": {
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the `/fix --ci-cd` command (issue #1733).
|
|
3
|
+
*
|
|
4
|
+
* `/fix --ci-cd <repository>` automatically:
|
|
5
|
+
* 1. detects the languages used in the target repository,
|
|
6
|
+
* 2. inspects the latest default-branch commit and its CI/CD runs,
|
|
7
|
+
* 3. creates a remediation issue (mirroring the `/task` issue-creation flow)
|
|
8
|
+
* that links the language-appropriate CI/CD pipeline templates and the
|
|
9
|
+
* CI/CD best-practices guide, and
|
|
10
|
+
* 4. hands the issue off to
|
|
11
|
+
* `/solve --development-log --deep-analysis --auto-merge`, forwarding every
|
|
12
|
+
* option that `/fix` itself does not consume (e.g. --tool, --model,
|
|
13
|
+
* --think).
|
|
14
|
+
*
|
|
15
|
+
* The issue title and body are taken from the standard prompt in
|
|
16
|
+
* https://github.com/link-assistant/web-capture/issues/139, omitting the
|
|
17
|
+
* its retired case-study paragraph in favor of `--development-log` and omitting
|
|
18
|
+
* paragraphs that `--deep-analysis` already injects into the AI prompt (issue
|
|
19
|
+
* #1733) — see `buildStandardPromptParagraphs` below.
|
|
20
|
+
*
|
|
21
|
+
* Everything that does not touch the network or the filesystem lives here so it
|
|
22
|
+
* can be unit-tested without GitHub access.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { KEEP_WORKING_PROMPT } from './solve.keep-working.detect.lib.mjs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Canonical mapping from GitHub Linguist language names to the
|
|
29
|
+
* link-foundation AI-driven-development pipeline templates.
|
|
30
|
+
*
|
|
31
|
+
* Order in this array is the stable tie-breaker when two languages contribute
|
|
32
|
+
* an equal number of bytes. The PHP template was added per issue #1733.
|
|
33
|
+
*/
|
|
34
|
+
export const CI_CD_TEMPLATES = Object.freeze([
|
|
35
|
+
{
|
|
36
|
+
key: 'javascript',
|
|
37
|
+
label: 'JavaScript / TypeScript',
|
|
38
|
+
languages: ['JavaScript', 'TypeScript'],
|
|
39
|
+
repo: 'link-foundation/js-ai-driven-development-pipeline-template',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'rust',
|
|
43
|
+
label: 'Rust',
|
|
44
|
+
languages: ['Rust'],
|
|
45
|
+
repo: 'link-foundation/rust-ai-driven-development-pipeline-template',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: 'python',
|
|
49
|
+
label: 'Python',
|
|
50
|
+
languages: ['Python'],
|
|
51
|
+
repo: 'link-foundation/python-ai-driven-development-pipeline-template',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: 'go',
|
|
55
|
+
label: 'Go',
|
|
56
|
+
languages: ['Go'],
|
|
57
|
+
repo: 'link-foundation/go-ai-driven-development-pipeline-template',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: 'csharp',
|
|
61
|
+
label: 'C#',
|
|
62
|
+
languages: ['C#'],
|
|
63
|
+
repo: 'link-foundation/csharp-ai-driven-development-pipeline-template',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: 'java',
|
|
67
|
+
label: 'Java',
|
|
68
|
+
languages: ['Java'],
|
|
69
|
+
repo: 'link-foundation/java-ai-driven-development-pipeline-template',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: 'php',
|
|
73
|
+
label: 'PHP',
|
|
74
|
+
languages: ['PHP'],
|
|
75
|
+
repo: 'link-foundation/php-ai-driven-development-pipeline-template',
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
export const CI_CD_BEST_PRACTICES_URL = 'https://github.com/link-assistant/hive-mind/blob/main/docs/CI-CD-BEST-PRACTICES.md';
|
|
80
|
+
|
|
81
|
+
/** Build a browser URL for a `owner/repo` slug. */
|
|
82
|
+
export function templateUrl(repo) {
|
|
83
|
+
return `https://github.com/${repo}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parse a `/fix` repository argument into a normalized descriptor.
|
|
88
|
+
* Returns null when the value is not a GitHub repository URL/shorthand.
|
|
89
|
+
*
|
|
90
|
+
* Self-contained on purpose: keeping this module free of the heavy
|
|
91
|
+
* `github.lib.mjs` import chain lets the pure helpers be unit-tested without
|
|
92
|
+
* network access. Accepts:
|
|
93
|
+
* - https://github.com/owner/repo (with optional .git / trailing slash)
|
|
94
|
+
* - github.com/owner/repo
|
|
95
|
+
* - owner/repo shorthand
|
|
96
|
+
* Rejects anything that points deeper than a repository (issues, pulls, …),
|
|
97
|
+
* contains whitespace, or is otherwise malformed.
|
|
98
|
+
*/
|
|
99
|
+
export function parseFixRepository(value) {
|
|
100
|
+
const candidate = String(value || '')
|
|
101
|
+
.trim()
|
|
102
|
+
.replace(/^[<([{]+/, '')
|
|
103
|
+
.replace(/[>\])}.,;:]+$/, '');
|
|
104
|
+
if (!candidate || /\s/.test(candidate)) return null;
|
|
105
|
+
|
|
106
|
+
// Normalize away an optional protocol, then require either a github.com host
|
|
107
|
+
// or a bare `owner/repo` shorthand. Any other host is rejected.
|
|
108
|
+
let withoutProtocol = candidate.replace(/^https?:\/\//i, '');
|
|
109
|
+
const hadProtocol = withoutProtocol !== candidate;
|
|
110
|
+
|
|
111
|
+
let pathPart;
|
|
112
|
+
if (/^github\.com\//i.test(withoutProtocol)) {
|
|
113
|
+
pathPart = withoutProtocol.replace(/^github\.com\//i, '');
|
|
114
|
+
} else if (!hadProtocol && !withoutProtocol.includes('.com/') && !/[^/]+\.[^/]+\//.test(withoutProtocol)) {
|
|
115
|
+
// Bare shorthand like `owner/repo`.
|
|
116
|
+
pathPart = withoutProtocol;
|
|
117
|
+
} else {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pathPart = pathPart.replace(/\.git$/i, '').replace(/\/+$/, '');
|
|
122
|
+
|
|
123
|
+
const segments = pathPart.split('/').filter(Boolean);
|
|
124
|
+
if (segments.length !== 2) return null;
|
|
125
|
+
|
|
126
|
+
const [owner, repo] = segments;
|
|
127
|
+
if (!/^[A-Za-z0-9._-]+$/.test(owner) || !/^[A-Za-z0-9._-]+$/.test(repo)) return null;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
owner,
|
|
131
|
+
repo,
|
|
132
|
+
fullName: `${owner}/${repo}`,
|
|
133
|
+
url: `https://github.com/${owner}/${repo}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Normalize the GitHub `/languages` response (a `{ "JavaScript": bytes }` map)
|
|
139
|
+
* or an array of names into a byte-sorted array of `{ name, bytes }`.
|
|
140
|
+
*/
|
|
141
|
+
export function normalizeLanguages(input) {
|
|
142
|
+
let entries = [];
|
|
143
|
+
if (Array.isArray(input)) {
|
|
144
|
+
entries = input.map(name => [String(name), 0]);
|
|
145
|
+
} else if (input && typeof input === 'object') {
|
|
146
|
+
entries = Object.entries(input).map(([name, bytes]) => [String(name), Number(bytes) || 0]);
|
|
147
|
+
}
|
|
148
|
+
return entries
|
|
149
|
+
.filter(([name]) => name)
|
|
150
|
+
.map(([name, bytes]) => ({ name, bytes }))
|
|
151
|
+
.sort((a, b) => b.bytes - a.bytes || a.name.localeCompare(b.name));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Map detected languages to CI/CD templates, sorted so that the templates for
|
|
156
|
+
* the most-used languages come first (issue #1733: "links to CI/CD templates
|
|
157
|
+
* should be sorted by detected languages in the target repository").
|
|
158
|
+
*
|
|
159
|
+
* Returns:
|
|
160
|
+
* - sortedTemplates: matched templates ordered by aggregate detected bytes
|
|
161
|
+
* - unmatchedLanguages: detected languages with no template (informational)
|
|
162
|
+
*/
|
|
163
|
+
export function mapLanguagesToTemplates(languages) {
|
|
164
|
+
const normalized = normalizeLanguages(languages);
|
|
165
|
+
|
|
166
|
+
const templateByLanguage = new Map();
|
|
167
|
+
for (const template of CI_CD_TEMPLATES) {
|
|
168
|
+
for (const language of template.languages) {
|
|
169
|
+
templateByLanguage.set(language.toLowerCase(), template);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const aggregate = new Map(); // template.key -> { template, bytes, languages: [] }
|
|
174
|
+
const unmatchedLanguages = [];
|
|
175
|
+
|
|
176
|
+
for (const { name, bytes } of normalized) {
|
|
177
|
+
const template = templateByLanguage.get(name.toLowerCase());
|
|
178
|
+
if (!template) {
|
|
179
|
+
unmatchedLanguages.push(name);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const existing = aggregate.get(template.key) || { template, bytes: 0, languages: [] };
|
|
183
|
+
existing.bytes += bytes;
|
|
184
|
+
existing.languages.push(name);
|
|
185
|
+
aggregate.set(template.key, existing);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const templateOrder = new Map(CI_CD_TEMPLATES.map((template, index) => [template.key, index]));
|
|
189
|
+
const sortedTemplates = [...aggregate.values()].sort((a, b) => b.bytes - a.bytes || templateOrder.get(a.template.key) - templateOrder.get(b.template.key));
|
|
190
|
+
|
|
191
|
+
return { sortedTemplates, unmatchedLanguages };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Title of the auto-generated remediation issue, taken exactly from the
|
|
196
|
+
* standard template issue https://github.com/link-assistant/web-capture/issues/139
|
|
197
|
+
* (issue #1733: "use title and description exactly"). The issue is created in
|
|
198
|
+
* the target repository itself, so it carries no repository suffix.
|
|
199
|
+
*/
|
|
200
|
+
export const CI_CD_ISSUE_TITLE = 'Check for all false positives, false negatives, warnings and errors in CI/CD and fix them all';
|
|
201
|
+
|
|
202
|
+
export function buildCiCdIssueTitle() {
|
|
203
|
+
return CI_CD_ISSUE_TITLE;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function shortSha(sha) {
|
|
207
|
+
return String(sha || '').slice(0, 7);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Render the detected-languages section. */
|
|
211
|
+
export function buildLanguagesSection(languages) {
|
|
212
|
+
const normalized = normalizeLanguages(languages);
|
|
213
|
+
if (normalized.length === 0) {
|
|
214
|
+
return 'No languages were reported by the GitHub Linguist API for this repository.';
|
|
215
|
+
}
|
|
216
|
+
const total = normalized.reduce((sum, { bytes }) => sum + bytes, 0) || 1;
|
|
217
|
+
const lines = normalized.map(({ name, bytes }) => {
|
|
218
|
+
const percent = ((bytes / total) * 100).toFixed(1);
|
|
219
|
+
return `- **${name}** — ${percent}%`;
|
|
220
|
+
});
|
|
221
|
+
return lines.join('\n');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Render the recommended-templates section, sorted by detected languages. */
|
|
225
|
+
export function buildTemplatesSection(languages) {
|
|
226
|
+
const { sortedTemplates, unmatchedLanguages } = mapLanguagesToTemplates(languages);
|
|
227
|
+
const lines = [];
|
|
228
|
+
|
|
229
|
+
if (sortedTemplates.length === 0) {
|
|
230
|
+
lines.push('No language-specific template matched the detected languages. Review all templates and apply the closest match:');
|
|
231
|
+
lines.push('');
|
|
232
|
+
for (const template of CI_CD_TEMPLATES) {
|
|
233
|
+
lines.push(`- ${template.label}: [${template.repo}](${templateUrl(template.repo)})`);
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
lines.push('Apply the best practices from these templates, in priority order (most-used language first):');
|
|
237
|
+
lines.push('');
|
|
238
|
+
sortedTemplates.forEach((entry, index) => {
|
|
239
|
+
const detected = entry.languages.join(', ');
|
|
240
|
+
lines.push(`${index + 1}. **${entry.template.label}** — [${entry.template.repo}](${templateUrl(entry.template.repo)}) _(detected: ${detected})_`);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (unmatchedLanguages.length > 0) {
|
|
245
|
+
lines.push('');
|
|
246
|
+
lines.push(`Other detected languages without a dedicated template: ${unmatchedLanguages.join(', ')}.`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Render the CI/CD runs section from the GitHub Actions API payload. */
|
|
253
|
+
export function buildRunsSection(runs, { emptyMessage } = {}) {
|
|
254
|
+
const list = Array.isArray(runs) ? runs : [];
|
|
255
|
+
if (list.length === 0) {
|
|
256
|
+
return emptyMessage || 'No CI/CD runs were found for the latest default-branch commit.';
|
|
257
|
+
}
|
|
258
|
+
const header = '| Workflow | Status | Conclusion | Run |\n| --- | --- | --- | --- |';
|
|
259
|
+
const rows = list.map(run => {
|
|
260
|
+
const name = run.name || run.workflowName || 'unknown';
|
|
261
|
+
const status = run.status || 'unknown';
|
|
262
|
+
const conclusion = run.conclusion || (status === 'completed' ? 'unknown' : 'in_progress');
|
|
263
|
+
const url = run.html_url || run.url || '';
|
|
264
|
+
const runLabel = url ? `[run](${url})` : '—';
|
|
265
|
+
return `| ${name} | ${status} | ${conclusion} | ${runLabel} |`;
|
|
266
|
+
});
|
|
267
|
+
return [header, ...rows].join('\n');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Count the runs that did not pass (failure/cancelled/timed_out/etc.). */
|
|
271
|
+
export function summarizeRunFailures(runs) {
|
|
272
|
+
const list = Array.isArray(runs) ? runs : [];
|
|
273
|
+
const passing = new Set(['success', 'neutral', 'skipped']);
|
|
274
|
+
const failing = list.filter(run => {
|
|
275
|
+
const conclusion = (run.conclusion || '').toLowerCase();
|
|
276
|
+
return run.status === 'completed' && conclusion && !passing.has(conclusion);
|
|
277
|
+
});
|
|
278
|
+
return { total: list.length, failing: failing.length };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The `/solve` options that `/fix` always turns on. `--development-log`
|
|
283
|
+
* replaces the retired collection paragraph; `--deep-analysis` provides the
|
|
284
|
+
* remaining instructions omitted from the generated issue body (issue #1733).
|
|
285
|
+
*/
|
|
286
|
+
export const SOLVE_OPTION_DEVELOPMENT_LOG = '--development-log';
|
|
287
|
+
export const SOLVE_OPTION_DEEP_ANALYSIS = '--deep-analysis';
|
|
288
|
+
export const FIX_FORWARDED_SOLVE_OPTIONS = Object.freeze([SOLVE_OPTION_DEVELOPMENT_LOG, SOLVE_OPTION_DEEP_ANALYSIS]);
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Paragraphs of the standard prompt, quoted from
|
|
292
|
+
* https://github.com/link-assistant/web-capture/issues/139.
|
|
293
|
+
*
|
|
294
|
+
* `providedBy` lists the `/solve` options that already inject an equivalent
|
|
295
|
+
* instruction into the AI prompt (see `buildDeepAnalysisPrompt`). A paragraph
|
|
296
|
+
* is dropped from the issue body when every option that provides it is passed
|
|
297
|
+
* to `/solve`.
|
|
298
|
+
*
|
|
299
|
+
* The deep-analysis wording below is the "bug" variant, which `/solve` emits
|
|
300
|
+
* only when the issue type is Bug — `/fix` therefore creates the issue with
|
|
301
|
+
* that type (see CI_CD_ISSUE_TYPE).
|
|
302
|
+
*
|
|
303
|
+
* The old case-study instruction from the upstream template is intentionally
|
|
304
|
+
* not represented here. `--development-log` is its replacement; generated
|
|
305
|
+
* issues must never offer or restore the superseded folder convention, even
|
|
306
|
+
* when callers request an otherwise unabridged prompt (PR #1929 feedback).
|
|
307
|
+
*/
|
|
308
|
+
export const DEBUG_OUTPUT_PARAGRAPH = 'If there is not enough data to find actual root cause, add debug output and verbose mode if not present, that will allow us to find root cause on next iteration.';
|
|
309
|
+
export const REPORT_UPSTREAM_PARAGRAPH = 'If issue related to any other repository/project, where we can report issues on GitHub, please do so. Each issue must contain reproducible examples, workarounds and suggestions for fix the issue in code. Also double check to fully apply requirements to entire codebase, so if we have issue in multiple places, it should be fixed in all them.';
|
|
310
|
+
|
|
311
|
+
/** Build the ordered, tagged paragraphs of the standard prompt. */
|
|
312
|
+
export function buildStandardPromptParagraphs({ templatesSorted } = {}) {
|
|
313
|
+
const templateLinks = (templatesSorted && templatesSorted.length > 0 ? templatesSorted.map(entry => entry.template.repo) : CI_CD_TEMPLATES.map(template => template.repo)).map(repo => `- ${templateUrl(repo)}`).join('\n');
|
|
314
|
+
|
|
315
|
+
return [
|
|
316
|
+
{
|
|
317
|
+
providedBy: [],
|
|
318
|
+
text: `Use all the best practices from CI/CD templates (check full file tree to compare for all GitHub workflow and CI/CD scripts file), if the same issue is found in template report issue also in templates:\n\n${templateLinks}`,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
providedBy: [],
|
|
322
|
+
text: "We should compare all files, so we don't have more CI/CD errors in the future and reuse all the best practices from these templates.",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
providedBy: [SOLVE_OPTION_DEEP_ANALYSIS],
|
|
326
|
+
text: DEBUG_OUTPUT_PARAGRAPH,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
providedBy: [SOLVE_OPTION_DEEP_ANALYSIS],
|
|
330
|
+
text: REPORT_UPSTREAM_PARAGRAPH,
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
providedBy: [],
|
|
334
|
+
text: `Follow the CI/CD best practices collected in [${CI_CD_BEST_PRACTICES_URL}](${CI_CD_BEST_PRACTICES_URL}).`,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
// Quoted verbatim from the template; identical to the reinforcement
|
|
338
|
+
// prompt /solve --keep-working-... reuses, so share the single constant.
|
|
339
|
+
providedBy: [],
|
|
340
|
+
text: KEEP_WORKING_PROMPT,
|
|
341
|
+
},
|
|
342
|
+
];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The standard remediation prompt, quoted from web-capture#139 with the
|
|
347
|
+
* paragraphs that `omittedOptions` already provide removed.
|
|
348
|
+
*/
|
|
349
|
+
export function buildStandardPrompt({ templatesSorted, omittedOptions = FIX_FORWARDED_SOLVE_OPTIONS } = {}) {
|
|
350
|
+
const omitted = new Set(omittedOptions || []);
|
|
351
|
+
return buildStandardPromptParagraphs({ templatesSorted })
|
|
352
|
+
.filter(paragraph => paragraph.providedBy.length === 0 || !paragraph.providedBy.every(option => omitted.has(option)))
|
|
353
|
+
.map(paragraph => paragraph.text)
|
|
354
|
+
.join('\n\n');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Issue type and label of the generated issue.
|
|
359
|
+
*
|
|
360
|
+
* `/solve --deep-analysis` only emits the root-cause / debug-output /
|
|
361
|
+
* report-upstream instructions — the paragraphs this body omits — when the
|
|
362
|
+
* issue type is Bug (see `isBugIssueType` in development-log.lib.mjs). Creating
|
|
363
|
+
* the issue as a Bug is therefore what makes the omission lossless.
|
|
364
|
+
*/
|
|
365
|
+
export const CI_CD_ISSUE_TYPE = 'Bug';
|
|
366
|
+
export const CI_CD_ISSUE_LABELS = Object.freeze(['bug']);
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Build the full Markdown body of the auto-generated remediation issue.
|
|
370
|
+
*
|
|
371
|
+
* The body mirrors the template issue's own description: the CI/CD runs of the
|
|
372
|
+
* latest default-branch commit first, then the standard prompt. The data `/fix`
|
|
373
|
+
* collected to build it (commit, languages, template ranking) follows as a
|
|
374
|
+
* collapsed context block so it stays available without displacing the prompt.
|
|
375
|
+
*/
|
|
376
|
+
export function buildCiCdIssueBody({ repository, defaultBranch, commit, runs, languages, runsSource = 'commit', omittedOptions = FIX_FORWARDED_SOLVE_OPTIONS }) {
|
|
377
|
+
const { sortedTemplates } = mapLanguagesToTemplates(languages);
|
|
378
|
+
const { total, failing } = summarizeRunFailures(runs);
|
|
379
|
+
|
|
380
|
+
const commitLine = commit?.sha ? `\`${shortSha(commit.sha)}\`${commit.url ? ` ([commit](${commit.url}))` : ''}${commit.message ? ` — ${String(commit.message).split('\n')[0]}` : ''}` : 'unknown';
|
|
381
|
+
|
|
382
|
+
// When the exact latest commit produced no runs (common for release/tag
|
|
383
|
+
// commits), `/fix` falls back to the most recent runs on the default branch
|
|
384
|
+
// so the issue stays actionable. Label the source honestly.
|
|
385
|
+
const runsHeading = runsSource === 'branch' ? `Recent CI/CD runs on \`${defaultBranch || 'default branch'}\`` : 'Latest default-branch CI/CD runs';
|
|
386
|
+
const runsEmptyMessage = runsSource === 'branch' ? `No recent CI/CD runs were found on \`${defaultBranch || 'the default branch'}\`.` : 'No CI/CD runs were found for the latest default-branch commit.';
|
|
387
|
+
|
|
388
|
+
const sections = [`### ${runsHeading}`, '', buildRunsSection(runs, { emptyMessage: runsEmptyMessage }), '', buildStandardPrompt({ templatesSorted: sortedTemplates, omittedOptions }), '', '---', '', '<details>', '<summary>Context collected by <code>/fix --ci-cd</code></summary>', '', `- **Repository:** [${repository?.fullName}](${repository?.url})`, `- **Default branch:** \`${defaultBranch || 'unknown'}\``, `- **Latest commit:** ${commitLine}`, `- **CI/CD runs found:** ${total} (${failing} not passing)`, '', '**Detected languages**', '', buildLanguagesSection(languages), '', '**Recommended CI/CD templates**', '', buildTemplatesSection(languages), '', '</details>'];
|
|
389
|
+
|
|
390
|
+
return sections.join('\n');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Flags that `/fix` consumes itself and must NOT be forwarded to `/solve`.
|
|
395
|
+
* Boolean flags only — they never take a value.
|
|
396
|
+
*/
|
|
397
|
+
export const FIX_OWNED_BOOLEAN_FLAGS = Object.freeze(['--ci-cd', '--dry-run', '--no-solve', '--solve', '--no-auto-solve', '--help', '-h', '--version']);
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Partition raw CLI args into the options `/fix` consumes and the passthrough
|
|
401
|
+
* args forwarded to `/solve`. Unknown flags (and their values) are preserved in
|
|
402
|
+
* order so that `--tool`, `--model`, `--think`, etc. reach `/solve` untouched.
|
|
403
|
+
*/
|
|
404
|
+
export function partitionFixArgs(rawArgs) {
|
|
405
|
+
const args = Array.isArray(rawArgs) ? rawArgs : [];
|
|
406
|
+
const result = {
|
|
407
|
+
repository: null,
|
|
408
|
+
repositoryRaw: null,
|
|
409
|
+
ciCd: false,
|
|
410
|
+
dryRun: false,
|
|
411
|
+
runSolve: true,
|
|
412
|
+
help: false,
|
|
413
|
+
version: false,
|
|
414
|
+
passthrough: [],
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
for (const arg of args) {
|
|
418
|
+
if (arg === '--ci-cd') {
|
|
419
|
+
result.ciCd = true;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (arg === '--dry-run') {
|
|
423
|
+
result.dryRun = true;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (arg === '--no-solve' || arg === '--no-auto-solve') {
|
|
427
|
+
result.runSolve = false;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (arg === '--solve') {
|
|
431
|
+
result.runSolve = true;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (arg === '--help' || arg === '-h') {
|
|
435
|
+
result.help = true;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (arg === '--version') {
|
|
439
|
+
result.version = true;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
// First bare GitHub repository argument becomes the target.
|
|
443
|
+
if (!result.repository && !arg.startsWith('-')) {
|
|
444
|
+
const repository = parseFixRepository(arg);
|
|
445
|
+
if (repository) {
|
|
446
|
+
result.repository = repository;
|
|
447
|
+
result.repositoryRaw = arg;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
result.passthrough.push(arg);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* The options `/fix` always turns on when handing the issue to `/solve`
|
|
459
|
+
* (issue #1733: "do similar to what `/solve --development-log --deep-analysis
|
|
460
|
+
* --auto-merge`"). `--development-log` replaces the superseded case-study
|
|
461
|
+
* workflow, while `--deep-analysis` re-injects the prompt paragraphs
|
|
462
|
+
* `buildCiCdIssueBody` conditionally omits from the issue text.
|
|
463
|
+
*/
|
|
464
|
+
export const FIX_SOLVE_OPTIONS = Object.freeze([SOLVE_OPTION_DEVELOPMENT_LOG, SOLVE_OPTION_DEEP_ANALYSIS, '--auto-merge']);
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Build the argv passed to `solve.mjs`: the created issue URL, the options
|
|
468
|
+
* `/fix` always enables, and every forwarded option. An option the caller
|
|
469
|
+
* already passed through is not duplicated.
|
|
470
|
+
*/
|
|
471
|
+
export function buildSolveArgs({ issueUrl, passthrough = [] }) {
|
|
472
|
+
const args = [issueUrl];
|
|
473
|
+
for (const option of FIX_SOLVE_OPTIONS) {
|
|
474
|
+
if (!passthrough.includes(option)) {
|
|
475
|
+
args.push(option);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
args.push(...passthrough);
|
|
479
|
+
return args;
|
|
480
|
+
}
|