agentme 0.36.0 → 0.37.1
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/.filedist-package.yml +1 -1
- package/.xdrs/agentme/bdrs/index.md +2 -1
- package/.xdrs/agentme/bdrs/operations/{401-epic-feature-story-planning.md → 401-plan-epic-feature-story.md} +2 -2
- package/.xdrs/agentme/bdrs/operations/402-digital-product-roles.md +15 -19
- package/.xdrs/agentme/bdrs/operations/403-product-team-composition.md +3 -0
- package/.xdrs/agentme/bdrs/operations/404-team-roles-and-specialists.md +61 -82
- package/.xdrs/agentme/bdrs/operations/405-digital-product-roles-raci.md +131 -0
- package/.xdrs/agentme/bdrs/principles/002-okr-framework.md +1 -1
- package/.xdrs/agentme/bdrs/principles/003-product-delivery-value-flow.md +3 -2
- package/.xdrs/agentme/edrs/application/101-javascript-project-tooling.md +1 -1
- package/.xdrs/agentme/edrs/application/102-golang-project-tooling.md +1 -1
- package/.xdrs/agentme/edrs/application/103-python-project-tooling.md +1 -1
- package/.xdrs/agentme/edrs/application/127-external-system-adapter-skills.md +2 -0
- package/.xdrs/agentme/edrs/application/skills/{251-azure-devops-connector → azure-devops-connector}/SKILL.md +40 -9
- package/.xdrs/agentme/edrs/application/skills/{251-azure-devops-connector → azure-devops-connector}/SKILL.test.md +2 -2
- package/.xdrs/agentme/edrs/application/skills/{051-create-golang-project → create-golang-project}/SKILL.md +55 -2
- package/.xdrs/agentme/edrs/application/skills/create-golang-project/SKILL.test.md +72 -0
- package/.xdrs/agentme/edrs/application/skills/{050-create-javascript-project → create-javascript-project}/SKILL.md +57 -25
- package/.xdrs/agentme/edrs/application/skills/create-javascript-project/SKILL.test.md +72 -0
- package/.xdrs/agentme/edrs/application/skills/{052-create-python-project → create-python-project}/SKILL.md +36 -2
- package/.xdrs/agentme/edrs/application/skills/create-python-project/SKILL.test.md +79 -0
- package/.xdrs/agentme/edrs/application/skills/{250-github-connector → github-connector}/SKILL.md +39 -8
- package/.xdrs/agentme/edrs/application/skills/{250-github-connector → github-connector}/SKILL.test.md +2 -2
- package/.xdrs/agentme/edrs/application/skills/{200-run-skill-tests → run-skill-tests}/SKILL.md +39 -5
- package/.xdrs/agentme/edrs/application/skills/{200-run-skill-tests → run-skill-tests}/SKILL.test.md +8 -8
- package/.xdrs/agentme/edrs/application/skills/{010-select-relevant-xdrs → select-relevant-xdrs}/SKILL.md +36 -2
- package/.xdrs/agentme/edrs/application/skills/select-relevant-xdrs/SKILL.test.md +73 -0
- package/.xdrs/agentme/edrs/governance/502-contributing-guide-requirements.md +1 -1
- package/.xdrs/agentme/edrs/index.md +16 -11
- package/.xdrs/agentme/edrs/operations/402-dora-metrics-framework.md +82 -0
- package/.xdrs/agentme/edrs/operations/403-dora-deployment-frequency.md +37 -0
- package/.xdrs/agentme/edrs/operations/404-dora-lead-time-for-changes.md +41 -0
- package/.xdrs/agentme/edrs/operations/405-dora-change-failure-rate.md +42 -0
- package/.xdrs/agentme/edrs/operations/406-dora-time-to-restore-service.md +42 -0
- package/.xdrs/agentme/edrs/platform/301-monorepo-structure.md +1 -1
- package/.xdrs/agentme/edrs/platform/skills/{053-monorepo-setup → monorepo-setup}/SKILL.md +50 -16
- package/.xdrs/agentme/edrs/platform/skills/monorepo-setup/SKILL.test.md +80 -0
- package/.xdrs/agentme/edrs/principles/012-continuous-xdr-enrichment.md +1 -1
- package/.xdrs/agentme/edrs/principles/017-skill-testing.md +4 -4
- package/.xdrs/agentme/edrs/principles/articles/001-continuous-xdr-improvement.md +1 -1
- package/.xdrs/agentme/edrs/principles/skills/{150-refine-plan-mode → refine-plan-mode}/SKILL.md +43 -17
- package/.xdrs/agentme/edrs/principles/skills/{150-refine-plan-mode → refine-plan-mode}/SKILL.test.md +3 -3
- package/.xdrs/agentme/edrs/principles/skills/{151-refine-user-story → refine-user-story}/SKILL.md +42 -15
- package/.xdrs/agentme/edrs/principles/skills/{151-refine-user-story → refine-user-story}/SKILL.test.md +18 -18
- package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/Makefile +1 -1
- package/.xdrs/agentme/edrs/principles/skills/resolve-pr-comments/SKILL.md +662 -0
- package/.xdrs/agentme/edrs/principles/skills/resolve-pr-comments/SKILL.test.md +165 -0
- package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-azure-devops.js +3 -3
- package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-azure-devops.test.js +5 -5
- package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-github.js +3 -3
- package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-github.test.js +4 -4
- package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/update-section.js +84 -3
- package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/update-section.test.js +125 -1
- package/package.json +2 -2
- package/.xdrs/agentme/edrs/principles/skills/400-pr-owner-assistant/SKILL.md +0 -633
- package/.xdrs/agentme/edrs/principles/skills/400-pr-owner-assistant/SKILL.test.md +0 -174
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: resolve-pr-comments
|
|
3
|
+
skill-version: "4.0.0"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Test Scenarios
|
|
7
|
+
|
|
8
|
+
### Scenario 1: End-to-end GitHub PR review, happy path with guided mode and a Phase 6 edit
|
|
9
|
+
|
|
10
|
+
**Trigger / Input**
|
|
11
|
+
|
|
12
|
+
"Let's review this PR: https://github.com/acme/widgets/pull/482" (run from a clean, up to
|
|
13
|
+
date local clone of `acme/widgets`, which has 2 open comments).
|
|
14
|
+
|
|
15
|
+
**Expected Behaviour**
|
|
16
|
+
|
|
17
|
+
The skill: (1) parses the URL host as `github.com` and activates `github-connector`; (2)
|
|
18
|
+
Phase 1 fetches PR #482 metadata and every comment, then confirms the local repo matches
|
|
19
|
+
`acme/widgets` and offers to check out the PR branch; (3) Phase 2 calls `init` to write
|
|
20
|
+
`.tmp/review-pr-482.md`'s header before analysing anything, then computes each comment's
|
|
21
|
+
fields and calls `append-section` to write its section immediately, one comment at a time --
|
|
22
|
+
rendering each section's `source` field as a clickable markdown link to the matching file in
|
|
23
|
+
the local checkout (never a provider URL), `source-lines` with the commented line(s) plus
|
|
24
|
+
padding, `diff-hunk-raw`/`author-raw`/`comment-url` verbatim from the connector,
|
|
25
|
+
`possible-user-intention` (an under-20-word, silently generated read of the author's likely
|
|
26
|
+
concern), and `possible-follow-ups` (2-4 candidate next actions); (4) Phase 3 renders the
|
|
27
|
+
2-row summary table and asks the literal numbered 4-way automation-level question -- the
|
|
28
|
+
human picks option 3 (guide all), so both comments use the guided flow; (5) Phase 4 walks
|
|
29
|
+
each comment one at a time, rendering its full focus card before asking the human to choose
|
|
30
|
+
an action, then drafts the reply/fix text, persists it as `pending-reply: drafted`, and shows
|
|
31
|
+
one FYI outcome line -- no confirmation question, nothing sent yet; (6) Phase 6 gathers both
|
|
32
|
+
now-drafted comments, renders a numbered consolidated preview table (`#`, `id`, title,
|
|
33
|
+
action, resolve-on-apply) followed by each row's exact draft text, and the human uses a
|
|
34
|
+
free-text instruction naming comment 2's `#` to change its reply text before applying both
|
|
35
|
+
together in a single apply-all batch confirmation -- resolving comment 1's thread as
|
|
36
|
+
requested -- then reports the final summary by action taken and send status.
|
|
37
|
+
|
|
38
|
+
**Simulated Human Responses**
|
|
39
|
+
1. "Yes, check out the PR branch."
|
|
40
|
+
2. (Phase 3) "3. I want to guide all comments solutions."
|
|
41
|
+
3. "For comment 1: confirm criticality, action = reply. Yes, resolve the thread once
|
|
42
|
+
applied." (accepts the drafted text unedited)
|
|
43
|
+
4. "For comment 2: confirm criticality, action = fix." (drafted text left as-is at Phase 4)
|
|
44
|
+
5. (Phase 6) "For comment 2, reply with 'Renamed per your suggestion, thanks!' instead, then
|
|
45
|
+
apply all now."
|
|
46
|
+
|
|
47
|
+
**Assertions**
|
|
48
|
+
|
|
49
|
+
- [ ] Skill activates `github-connector` because the URL host is `github.com`.
|
|
50
|
+
- [ ] Skill calls `init` to write the tracking file's header before analysing any comment,
|
|
51
|
+
then calls `append-section` once per comment, immediately, one at a time -- never
|
|
52
|
+
accumulating every section in memory until the end.
|
|
53
|
+
- [ ] Skill renders each section's `source` field as a markdown link to the matching local
|
|
54
|
+
file, relative to the tracking file's own location -- never a link to the provider's
|
|
55
|
+
website.
|
|
56
|
+
- [ ] Skill's Phase 3 renders a summary table (`#`, title, author, automation-suggestion,
|
|
57
|
+
criticality) and asks the literal numbered 4-way automation-level question before
|
|
58
|
+
Phase 4 asks anything else.
|
|
59
|
+
- [ ] Skill asks about exactly one comment at a time in Phase 4, posting each comment's full
|
|
60
|
+
focus card as its own chat message before the action question.
|
|
61
|
+
- [ ] Skill shows no confirmation question in Phase 4 -- only a one-line FYI outcome after
|
|
62
|
+
each draft is persisted, for both comments.
|
|
63
|
+
- [ ] Skill's Phase 6 renders a numbered consolidated preview table (`#`, `id`, title,
|
|
64
|
+
action, resolve-on-apply) followed by each row's exact draft text below the table.
|
|
65
|
+
- [ ] Skill accepts a Phase 6 free-text instruction naming comment 2's preview `#` to change
|
|
66
|
+
its reply text before applying, persisting the change immediately.
|
|
67
|
+
- [ ] Skill applies both comments' drafts together in a single apply-all batch confirmation
|
|
68
|
+
in Phase 6, resolving comment 1's thread as requested.
|
|
69
|
+
- [ ] Skill suffixes both comment 1's reply (accepted as drafted) and comment 2's reply
|
|
70
|
+
(changed at Phase 6) with the identical `(resolve-pr-comments - guided)` literal --
|
|
71
|
+
no distinction between the two cases.
|
|
72
|
+
- [ ] Skill records the section's original comment text under `comment-raw` and any existing
|
|
73
|
+
thread replies under `replies-raw`.
|
|
74
|
+
- [ ] Skill populates `possible-user-intention` with an under-20-word inference silently,
|
|
75
|
+
with no dedicated confirmation prompt of its own.
|
|
76
|
+
|
|
77
|
+
### Scenario 2: Phase 3's "something else" free text drives a mixed fully-auto/guided run
|
|
78
|
+
|
|
79
|
+
**Trigger / Input**
|
|
80
|
+
|
|
81
|
+
Phase 2 populates 3 open comments: comment A (a simple, repetitive rename request with a
|
|
82
|
+
clean `suggested-fix`, classified `automation-suggestion: fully-auto`), comment B (a vague
|
|
83
|
+
architectural question, classified `automation-suggestion: guided`), and comment C (a minor
|
|
84
|
+
style nitpick, classified `automation-suggestion: fully-auto`, not mentioned in the human's
|
|
85
|
+
free text below). At Phase 3, the human answers option 4 with "automate everything, but
|
|
86
|
+
guide comment B".
|
|
87
|
+
|
|
88
|
+
**Expected Behaviour**
|
|
89
|
+
|
|
90
|
+
Phase 4 reaches comment A and, matching the free text's "automate everything", skips its
|
|
91
|
+
focus card and action question entirely: before starting, it shows one starting banner line
|
|
92
|
+
(`Starting comment A of 3 (fix): "<short title>".`), then auto-chooses `fix`, applies the
|
|
93
|
+
`suggested-fix` verbatim, defaults `resolve-on-apply` from `can_resolve` with no ask, drafts
|
|
94
|
+
and persists `pending-reply: drafted` immediately, and shows one brief outcome line
|
|
95
|
+
(`Auto-handled comment A (fix): ...`). The drafted text carries the
|
|
96
|
+
`(resolve-pr-comments - fully-auto)` suffix unconditionally. Comment B, explicitly named for
|
|
97
|
+
guidance, still gets its full focus card and action question exactly as documented, with the
|
|
98
|
+
human choosing `reply` and editing the drafted text, earning the
|
|
99
|
+
`(resolve-pr-comments - guided)` suffix. Comment C, not explicitly addressed by the free
|
|
100
|
+
text, follows its own `automation-suggestion` (`fully-auto`) and is handled the same
|
|
101
|
+
no-question way as comment A, each with its own starting banner and outcome line. Phase 6
|
|
102
|
+
later gathers all three comments together and requires the same explicit apply confirmation
|
|
103
|
+
for all of them -- no comment's fully-auto origin grants it a bypass of that gate.
|
|
104
|
+
|
|
105
|
+
**Simulated Human Responses**
|
|
106
|
+
1. (Phase 3) "4. Automate everything, but guide comment B."
|
|
107
|
+
2. "For comment B: action = reply. Use this reply text instead: 'We considered that
|
|
108
|
+
approach; here's why we didn't take it...'" (replaces the drafted text with free text)
|
|
109
|
+
3. (Phase 6) "Apply all now."
|
|
110
|
+
|
|
111
|
+
**Assertions**
|
|
112
|
+
|
|
113
|
+
- [ ] Skill's Phase 3 renders the summary table and asks the literal numbered 4-way
|
|
114
|
+
automation-level question before Phase 4 begins.
|
|
115
|
+
- [ ] Skill interprets the free-text option-4 answer against the numbered table, applying the
|
|
116
|
+
no-question path to comments A and C and the guided path to comment B by name.
|
|
117
|
+
- [ ] Skill never shows comment A's or comment C's focus card or an action question for
|
|
118
|
+
either, auto-choosing and applying each one's action with no human confirmation.
|
|
119
|
+
- [ ] Skill shows one starting banner line before each of comment A's and comment C's work
|
|
120
|
+
begins, and one brief outcome line after, suffixing both with
|
|
121
|
+
`(resolve-pr-comments - fully-auto)` unconditionally.
|
|
122
|
+
- [ ] Skill shows comment B's full focus card and action question, unaffected by comments A
|
|
123
|
+
and C's no-question handling.
|
|
124
|
+
- [ ] Skill suffixes comment B's reply -- replaced with human-supplied free text -- with
|
|
125
|
+
`(resolve-pr-comments - guided)`.
|
|
126
|
+
- [ ] Skill's Phase 6 requires the same explicit apply confirmation for all three comments --
|
|
127
|
+
being drafted via the no-question path grants no bypass of Phase 6's gate.
|
|
128
|
+
|
|
129
|
+
### Scenario 3: Phase 2 writes the tracking file incrementally, surviving a mid-run interruption
|
|
130
|
+
|
|
131
|
+
**Trigger / Input**
|
|
132
|
+
|
|
133
|
+
A PR has 6 open comments. The session runs Phase 2 against a brand-new tracking file, then is
|
|
134
|
+
interrupted (e.g. context compaction or a cancelled session) immediately after the 4th
|
|
135
|
+
comment's section has been written, before the 5th comment's fields are computed.
|
|
136
|
+
|
|
137
|
+
**Expected Behaviour**
|
|
138
|
+
|
|
139
|
+
Phase 2 first calls `init` to durably write `.tmp/review-pr-<N>.md`'s header (PR link, raw
|
|
140
|
+
summary, auto-generated summary) before analysing any single comment. It then computes
|
|
141
|
+
comment 1's fields and calls `append-section` to write its section immediately, repeating
|
|
142
|
+
this one-comment-at-a-time for comments 2, 3, and 4 -- never holding more than one comment's
|
|
143
|
+
computed-but-unwritten section in memory at a time. When the interruption happens right
|
|
144
|
+
after comment 4's `append-section` call returns, comments 1-4 are already durably present on
|
|
145
|
+
disk. A fresh invocation re-reads the existing file, finds comments 1-4 already recorded (and
|
|
146
|
+
does not recompute or rewrite them), and resumes by computing and appending comments 5 and 6
|
|
147
|
+
the same incremental way.
|
|
148
|
+
|
|
149
|
+
**Simulated Human Responses**
|
|
150
|
+
(none -- Phase 2 asks nothing; the interruption and resume are session-level events, not
|
|
151
|
+
human decisions)
|
|
152
|
+
|
|
153
|
+
**Assertions**
|
|
154
|
+
|
|
155
|
+
- [ ] Skill calls `init` once, before computing any comment's fields, to write the tracking
|
|
156
|
+
file's header (PR link, raw summary, auto-generated summary).
|
|
157
|
+
- [ ] Skill calls `append-section` once per comment, immediately after that comment's fields
|
|
158
|
+
are computed, rather than batching multiple comments' sections into one write.
|
|
159
|
+
- [ ] Skill's on-disk tracking file already contains comments 1 through 4's full sections at
|
|
160
|
+
the moment of interruption, before comment 5 is ever analysed.
|
|
161
|
+
- [ ] Skill's fresh invocation after the interruption re-reads the existing file and does not
|
|
162
|
+
recompute or rewrite comments 1-4's already-written sections.
|
|
163
|
+
- [ ] Skill's fresh invocation computes and appends comments 5 and 6 the same
|
|
164
|
+
one-comment-at-a-time way, completing the file.
|
|
165
|
+
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Applies pr-
|
|
6
|
-
* `az rest` commands documented in
|
|
5
|
+
* Applies resolve-pr-comments tracking-file drafts to a live Azure DevOps PR, using the
|
|
6
|
+
* `az rest` commands documented in azure-devops-connector's "Writing data" section.
|
|
7
7
|
* Delegates every tracking-file read/write to the sibling `update-section.js` script so
|
|
8
8
|
* there is exactly one place that understands the file format.
|
|
9
9
|
*
|
|
10
10
|
* Every write is verified by an independent read-back before the tracking file is ever
|
|
11
11
|
* marked `pending-reply: applied` -- a zero exit code from `az rest` alone is never treated
|
|
12
|
-
* as proof a write persisted (see
|
|
12
|
+
* as proof a write persisted (see azure-devops-connector's Known Issues: a POST/PATCH
|
|
13
13
|
* can exit 0 without the change actually landing on the server).
|
|
14
14
|
*
|
|
15
15
|
* Usage:
|
|
@@ -75,8 +75,8 @@ resolve-on-apply: false
|
|
|
75
75
|
pending-reply: drafted
|
|
76
76
|
reply-draft: |
|
|
77
77
|
Agreed -- good catch. Renamed the parameter to a dedicated codeowners-app service
|
|
78
|
-
connection. Leaving this thread open until it is created. (pr-
|
|
79
|
-
|
|
78
|
+
connection. Leaving this thread open until it is created. (resolve-pr-comments -
|
|
79
|
+
guided)
|
|
80
80
|
|
|
81
81
|
### Split run() into smaller functions
|
|
82
82
|
id: thread-comment/200.1
|
|
@@ -90,8 +90,8 @@ action: fix
|
|
|
90
90
|
resolve-on-apply: true
|
|
91
91
|
pending-reply: drafted
|
|
92
92
|
reply-draft: |
|
|
93
|
-
Fixed -- split run() into named single-purpose functions. (pr-
|
|
94
|
-
|
|
93
|
+
Fixed -- split run() into named single-purpose functions. (resolve-pr-comments -
|
|
94
|
+
guided)
|
|
95
95
|
|
|
96
96
|
### Already handled comment, should never be touched
|
|
97
97
|
id: thread-comment/300.1
|
|
@@ -208,7 +208,7 @@ test('REGRESSION: does not mark status resolved when PATCH exits 0 but status ne
|
|
|
208
208
|
|
|
209
209
|
test('skips re-posting when the exact reply is already present on the live thread, but still marks applied', () => {
|
|
210
210
|
const alreadyPostedText =
|
|
211
|
-
'Agreed -- good catch. Renamed the parameter to a dedicated codeowners-app service connection. Leaving this thread open until it is created. (pr-
|
|
211
|
+
'Agreed -- good catch. Renamed the parameter to a dedicated codeowners-app service connection. Leaving this thread open until it is created. (resolve-pr-comments - guided)';
|
|
212
212
|
const { file, env } = setup({
|
|
213
213
|
threads: {
|
|
214
214
|
100: {
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Applies pr-
|
|
6
|
-
* commands documented in
|
|
5
|
+
* Applies resolve-pr-comments tracking-file drafts to a live GitHub PR, using the `gh`
|
|
6
|
+
* commands documented in github-connector's "Writing data" section. Delegates every
|
|
7
7
|
* tracking-file read/write to the sibling `update-section.js` script so there is exactly
|
|
8
8
|
* one place that understands the file format. Mirrors post-replies-azure-devops.js's
|
|
9
9
|
* interface and verify-after-write discipline for parity across both connectors.
|
|
10
10
|
*
|
|
11
11
|
* Every write is confirmed by an independent read-back before the tracking file is ever
|
|
12
12
|
* marked `pending-reply: applied` -- not because `gh` is known to exit 0 without persisting
|
|
13
|
-
* (unlike `az rest`, see
|
|
13
|
+
* (unlike `az rest`, see azure-devops-connector's Known Issues), but so both scripts
|
|
14
14
|
* make the same guarantee and a GraphQL mutation's rarer partial-failure shape is still
|
|
15
15
|
* caught.
|
|
16
16
|
*
|
|
@@ -115,7 +115,7 @@ action: fix
|
|
|
115
115
|
resolve-on-apply: true
|
|
116
116
|
pending-reply: drafted
|
|
117
117
|
reply-draft: |
|
|
118
|
-
Fixed -- renamed per your suggestion, thanks! (pr-
|
|
118
|
+
Fixed -- renamed per your suggestion, thanks! (resolve-pr-comments - guided)
|
|
119
119
|
|
|
120
120
|
### General question about the release plan
|
|
121
121
|
id: issue-comment/555
|
|
@@ -129,8 +129,8 @@ action: reply
|
|
|
129
129
|
resolve-on-apply: false
|
|
130
130
|
pending-reply: drafted
|
|
131
131
|
reply-draft: |
|
|
132
|
-
Targeting the next minor release once CI is green. (pr-
|
|
133
|
-
|
|
132
|
+
Targeting the next minor release once CI is green. (resolve-pr-comments -
|
|
133
|
+
guided)
|
|
134
134
|
|
|
135
135
|
### Already handled comment, should never be touched
|
|
136
136
|
id: review-comment/77777
|
|
@@ -235,7 +235,7 @@ test('REGRESSION: does not mark status resolved when the mutation exits 0 but is
|
|
|
235
235
|
test('skips re-posting when the exact reply is already present, but still marks applied', () => {
|
|
236
236
|
const { file, env } = setup({
|
|
237
237
|
issueComments: [],
|
|
238
|
-
reviewComments: [{ id: 1, body: 'Fixed -- renamed per your suggestion, thanks! (pr-
|
|
238
|
+
reviewComments: [{ id: 1, body: 'Fixed -- renamed per your suggestion, thanks! (resolve-pr-comments - guided)', in_reply_to_id: 91234 }],
|
|
239
239
|
threads: { 91234: { nodeId: 'PRT_1', isResolved: false } },
|
|
240
240
|
// If the script incorrectly tries to POST again, this makes it fail loudly.
|
|
241
241
|
failPost: ['91234'],
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Safe read/update helper for a pr-
|
|
5
|
+
* Safe read/update helper for a resolve-pr-comments tracking file
|
|
6
6
|
* (.tmp/review-pr-<N>.md), keyed by each section's stable `id:` value
|
|
7
7
|
* instead of its title text. See SKILL.md's "Editing the tracking file" note.
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
|
+
* update-section.js init <file> <pr-number> <pr-link> <auto-summary...> (raw PR summary from stdin)
|
|
11
|
+
* update-section.js append-section <file> (full "### ..." section from stdin)
|
|
10
12
|
* update-section.js list <file>
|
|
11
13
|
* update-section.js get <file> <id> <field>
|
|
12
14
|
* update-section.js set <file> <id> <field> <value...>
|
|
@@ -28,17 +30,20 @@ const SCALAR_FIELDS = [
|
|
|
28
30
|
'possible-user-intention',
|
|
29
31
|
'suggested-fix-assessment',
|
|
30
32
|
'criticality',
|
|
33
|
+
'automation-suggestion',
|
|
31
34
|
'action',
|
|
32
35
|
'resolve-on-apply',
|
|
33
36
|
'pending-reply',
|
|
34
37
|
];
|
|
35
38
|
const BLOCK_FIELDS = ['source-lines', 'diff-hunk-raw', 'suggested-fix', 'comment-raw', 'reply-draft'];
|
|
36
|
-
const LIST_FIELDS = ['replies-raw', 'possible-follow-ups'];
|
|
39
|
+
const LIST_FIELDS = ['replies-raw', 'possible-follow-ups', 'similar-to'];
|
|
37
40
|
const ALL_FIELDS = [...SCALAR_FIELDS, ...BLOCK_FIELDS, ...LIST_FIELDS];
|
|
38
41
|
const NEXT_FIELD_RE = new RegExp(`^(?:${ALL_FIELDS.join('|')}):`);
|
|
39
42
|
|
|
40
43
|
function usage() {
|
|
41
44
|
return `Usage:
|
|
45
|
+
update-section.js init <file> <pr-number> <pr-link> <auto-summary...> (raw PR summary from stdin)
|
|
46
|
+
update-section.js append-section <file> (full "### ..." section from stdin)
|
|
42
47
|
update-section.js list <file>
|
|
43
48
|
update-section.js get <file> <id> <field>
|
|
44
49
|
update-section.js set <file> <id> <field> <value...>
|
|
@@ -142,6 +147,59 @@ function main(argv) {
|
|
|
142
147
|
return;
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
if (cmd === 'init') {
|
|
151
|
+
const [prNumber, prLink, ...summaryParts] = rest;
|
|
152
|
+
if (!prNumber || !prLink || summaryParts.length === 0) {
|
|
153
|
+
console.error(usage());
|
|
154
|
+
process.exit(1);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (fs.existsSync(file)) {
|
|
158
|
+
throw new Error(`refusing to overwrite existing file: ${file}`);
|
|
159
|
+
}
|
|
160
|
+
const rawSummary = readStdin().replace(/\n$/, '');
|
|
161
|
+
const autoSummary = summaryParts.join(' ');
|
|
162
|
+
fs.writeFileSync(
|
|
163
|
+
file,
|
|
164
|
+
`# PR #${prNumber}\n\n${prLink}\n\n${rawSummary}\n\n**Summary**: ${autoSummary}\n`,
|
|
165
|
+
);
|
|
166
|
+
console.log('OK');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (cmd === 'append-section') {
|
|
171
|
+
if (!fs.existsSync(file)) {
|
|
172
|
+
throw new Error(`file does not exist: ${file}`);
|
|
173
|
+
}
|
|
174
|
+
const stdin = readStdin().replace(/\n$/, '');
|
|
175
|
+
if (!stdin.startsWith('### ')) {
|
|
176
|
+
throw new Error('stdin must be a full section starting with "### "');
|
|
177
|
+
}
|
|
178
|
+
const stdinLines = stdin.split('\n');
|
|
179
|
+
const idLine = stdinLines.find((l) => /^id:\s*/.test(l));
|
|
180
|
+
if (!idLine) {
|
|
181
|
+
throw new Error('stdin section is missing an "id:" line');
|
|
182
|
+
}
|
|
183
|
+
const newId = idLine.replace(/^id:\s*/, '').trim();
|
|
184
|
+
const original = fs.readFileSync(file, 'utf8');
|
|
185
|
+
const hadTrailingNewline = original.endsWith('\n');
|
|
186
|
+
const lines = original.split('\n');
|
|
187
|
+
let duplicate = true;
|
|
188
|
+
try {
|
|
189
|
+
findSectionById(lines, newId);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (!/^no section found/.test(err.message)) throw err;
|
|
192
|
+
duplicate = false;
|
|
193
|
+
}
|
|
194
|
+
if (duplicate) {
|
|
195
|
+
throw new Error(`a section with id "${newId}" already exists in ${file}`);
|
|
196
|
+
}
|
|
197
|
+
const separator = lines[lines.length - 1] === '' ? [] : [''];
|
|
198
|
+
writeBack(file, [...lines, ...separator, ...stdinLines], hadTrailingNewline);
|
|
199
|
+
console.log('OK');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
145
203
|
const original = fs.readFileSync(file, 'utf8');
|
|
146
204
|
const hadTrailingNewline = original.endsWith('\n');
|
|
147
205
|
const lines = original.split('\n');
|
|
@@ -151,7 +209,16 @@ function main(argv) {
|
|
|
151
209
|
for (let s = 0; s < idx.length; s++) {
|
|
152
210
|
const start = idx[s];
|
|
153
211
|
const end = s + 1 < idx.length ? idx[s + 1] : lines.length;
|
|
154
|
-
const row = {
|
|
212
|
+
const row = {
|
|
213
|
+
id: '',
|
|
214
|
+
'author-raw': '',
|
|
215
|
+
status: '',
|
|
216
|
+
criticality: '',
|
|
217
|
+
'automation-suggestion': '',
|
|
218
|
+
action: '',
|
|
219
|
+
'pending-reply': '',
|
|
220
|
+
'resolve-on-apply': '',
|
|
221
|
+
};
|
|
155
222
|
for (const key of Object.keys(row)) {
|
|
156
223
|
try {
|
|
157
224
|
const f = findField(lines, start, end, key);
|
|
@@ -160,13 +227,27 @@ function main(argv) {
|
|
|
160
227
|
// field absent in a malformed section -- leave blank, never fail `list`
|
|
161
228
|
}
|
|
162
229
|
}
|
|
230
|
+
let similarTo = '';
|
|
231
|
+
try {
|
|
232
|
+
const f = findField(lines, start, end, 'similar-to');
|
|
233
|
+
similarTo = lines
|
|
234
|
+
.slice(f.contentStart, f.contentEnd)
|
|
235
|
+
.map((l) => l.replace(/^ {2}- /, ''))
|
|
236
|
+
.join(', ');
|
|
237
|
+
} catch {
|
|
238
|
+
// field absent in a malformed section -- leave blank, never fail `list`
|
|
239
|
+
}
|
|
163
240
|
// Bullets avoid relying on column/tab alignment, which breaks once field values vary in width.
|
|
164
241
|
console.log(`- id: ${row.id}`);
|
|
165
242
|
console.log(` title: ${lines[start].slice(4).trim()}`);
|
|
243
|
+
console.log(` author-raw: ${row['author-raw']}`);
|
|
166
244
|
console.log(` status: ${row.status}`);
|
|
245
|
+
console.log(` criticality: ${row.criticality}`);
|
|
246
|
+
console.log(` automation-suggestion: ${row['automation-suggestion']}`);
|
|
167
247
|
console.log(` action: ${row.action}`);
|
|
168
248
|
console.log(` pending-reply: ${row['pending-reply']}`);
|
|
169
249
|
console.log(` resolve-on-apply: ${row['resolve-on-apply']}`);
|
|
250
|
+
console.log(` similar-to: ${similarTo}`);
|
|
170
251
|
}
|
|
171
252
|
return;
|
|
172
253
|
}
|
|
@@ -33,20 +33,25 @@ reply-draft: |
|
|
|
33
33
|
id: review-comment/222
|
|
34
34
|
status: open
|
|
35
35
|
source: [lib/src/foo.ts:10-10](../lib/src/foo.ts#L10-L10)
|
|
36
|
+
author-raw: octocat
|
|
36
37
|
suggested-fix: |
|
|
37
38
|
const value = compute();
|
|
38
39
|
suggested-fix-assessment: accept-as-is
|
|
40
|
+
criticality: medium
|
|
41
|
+
automation-suggestion: fully-auto
|
|
39
42
|
comment-raw: |
|
|
40
43
|
Shall we rename this?
|
|
41
44
|
replies-raw:
|
|
42
45
|
possible-follow-ups:
|
|
43
46
|
- "fix: rename for consistency"
|
|
44
47
|
- "mark won't-fix: keep current naming"
|
|
48
|
+
similar-to:
|
|
49
|
+
- review-comment/333
|
|
45
50
|
action: fix
|
|
46
51
|
resolve-on-apply: true
|
|
47
52
|
pending-reply: drafted
|
|
48
53
|
reply-draft: |
|
|
49
|
-
Renamed as suggested. (pr-
|
|
54
|
+
Renamed as suggested. (resolve-pr-comments - guided)
|
|
50
55
|
`;
|
|
51
56
|
|
|
52
57
|
function writeFixture(content = FIXTURE) {
|
|
@@ -55,6 +60,10 @@ function writeFixture(content = FIXTURE) {
|
|
|
55
60
|
return file;
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
function tmpFilePath(name = 'review-pr-999.md') {
|
|
64
|
+
return path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'update-section-test-')), name);
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
function run(args, input) {
|
|
59
68
|
try {
|
|
60
69
|
const stdout = execFileSync('node', [SCRIPT, ...args], { input, encoding: 'utf8' });
|
|
@@ -73,8 +82,29 @@ test('list renders one bullet block per section, not tab-aligned columns', () =>
|
|
|
73
82
|
assert.match(stdout, /^ {2}title: Contributor offers to help$/m);
|
|
74
83
|
assert.match(stdout, /^ {2}status: open$/m);
|
|
75
84
|
assert.match(stdout, /- id: review-comment\/222/);
|
|
85
|
+
assert.match(stdout, /^ {2}author-raw: octocat$/m);
|
|
86
|
+
assert.match(stdout, /^ {2}criticality: medium$/m);
|
|
87
|
+
assert.match(stdout, /^ {2}automation-suggestion: fully-auto$/m);
|
|
76
88
|
assert.match(stdout, /^ {2}action: fix$/m);
|
|
77
89
|
assert.match(stdout, /^ {2}pending-reply: drafted$/m);
|
|
90
|
+
assert.match(stdout, /^ {2}similar-to: review-comment\/333$/m);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('list --json renders one JSON object per section with the same fields', () => {
|
|
94
|
+
const file = writeFixture();
|
|
95
|
+
const { status, stdout } = run(['list', file, '--json']);
|
|
96
|
+
assert.equal(status, 0);
|
|
97
|
+
const rows = JSON.parse(stdout);
|
|
98
|
+
assert.equal(rows.length, 2);
|
|
99
|
+
assert.equal(rows[0].id, 'issue-comment/111');
|
|
100
|
+
assert.equal(rows[0].title, 'Contributor offers to help');
|
|
101
|
+
assert.equal(rows[1].id, 'review-comment/222');
|
|
102
|
+
assert.equal(rows[1]['author-raw'], 'octocat');
|
|
103
|
+
assert.equal(rows[1].criticality, 'medium');
|
|
104
|
+
assert.equal(rows[1]['automation-suggestion'], 'fully-auto');
|
|
105
|
+
assert.equal(rows[1].action, 'fix');
|
|
106
|
+
assert.equal(rows[1]['pending-reply'], 'drafted');
|
|
107
|
+
assert.equal(rows[1]['similar-to'], 'review-comment/333');
|
|
78
108
|
});
|
|
79
109
|
|
|
80
110
|
test('get returns a scalar field value', () => {
|
|
@@ -197,3 +227,97 @@ test('set-block on a scalar-only field fails and points to set', () => {
|
|
|
197
227
|
assert.notEqual(status, 0);
|
|
198
228
|
assert.match(stderr, /use set \(scalar\)/);
|
|
199
229
|
});
|
|
230
|
+
|
|
231
|
+
test('get/set support the automation-suggestion scalar field', () => {
|
|
232
|
+
const file = writeFixture();
|
|
233
|
+
const { stdout: before } = run(['get', file, 'review-comment/222', 'automation-suggestion']);
|
|
234
|
+
assert.equal(before.trim(), 'fully-auto');
|
|
235
|
+
const { status } = run(['set', file, 'review-comment/222', 'automation-suggestion', 'guided']);
|
|
236
|
+
assert.equal(status, 0);
|
|
237
|
+
const { stdout: after } = run(['get', file, 'review-comment/222', 'automation-suggestion']);
|
|
238
|
+
assert.equal(after.trim(), 'guided');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('get/set-list support the similar-to list field', () => {
|
|
242
|
+
const file = writeFixture();
|
|
243
|
+
const { stdout: before } = run(['get', file, 'review-comment/222', 'similar-to']);
|
|
244
|
+
assert.equal(before, 'review-comment/333\n');
|
|
245
|
+
const { status } = run(
|
|
246
|
+
['set-list', file, 'review-comment/222', 'similar-to'],
|
|
247
|
+
'review-comment/333\nreview-comment/444\n',
|
|
248
|
+
);
|
|
249
|
+
assert.equal(status, 0);
|
|
250
|
+
const { stdout: after } = run(['get', file, 'review-comment/222', 'similar-to']);
|
|
251
|
+
assert.equal(after, 'review-comment/333\nreview-comment/444\n');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('init creates a new tracking file with the PR header and raw summary', () => {
|
|
255
|
+
const file = tmpFilePath();
|
|
256
|
+
const { status, stdout } = run(
|
|
257
|
+
['init', file, '999', 'https://github.com/acme/widget/pull/999', 'Adds', 'retry', 'logic'],
|
|
258
|
+
'Raw PR body text.\n\nSecond paragraph.\n',
|
|
259
|
+
);
|
|
260
|
+
assert.equal(status, 0);
|
|
261
|
+
assert.equal(stdout.trim(), 'OK');
|
|
262
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
263
|
+
assert.equal(
|
|
264
|
+
content,
|
|
265
|
+
'# PR #999\n\nhttps://github.com/acme/widget/pull/999\n\nRaw PR body text.\n\nSecond paragraph.\n\n**Summary**: Adds retry logic\n',
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('init refuses to overwrite an existing file', () => {
|
|
270
|
+
const file = writeFixture();
|
|
271
|
+
const { status, stderr } = run(
|
|
272
|
+
['init', file, '999', 'https://example.com/pr/999', 'Some', 'summary'],
|
|
273
|
+
'Raw body.\n',
|
|
274
|
+
);
|
|
275
|
+
assert.notEqual(status, 0);
|
|
276
|
+
assert.match(stderr, /refusing to overwrite existing file/);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('append-section adds a new section after the last one, separated by exactly one blank line', () => {
|
|
280
|
+
const file = writeFixture();
|
|
281
|
+
const newSection =
|
|
282
|
+
'### New finding\nid: issue-comment/777\nstatus: open\nsource: (PR conversation)\ncomment-raw: |\n A brand new comment.\nreplies-raw:\npossible-follow-ups:\naction:\nresolve-on-apply: false\npending-reply: none\nreply-draft: |\n';
|
|
283
|
+
const { status } = run(['append-section', file], newSection);
|
|
284
|
+
assert.equal(status, 0);
|
|
285
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
286
|
+
assert.match(
|
|
287
|
+
content,
|
|
288
|
+
/\(resolve-pr-comments - guided\)\n\n### New finding\nid: issue-comment\/777/,
|
|
289
|
+
);
|
|
290
|
+
const { stdout } = run(['list', file]);
|
|
291
|
+
assert.match(stdout, /- id: issue-comment\/777/);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('append-section fails when the id already exists', () => {
|
|
295
|
+
const file = writeFixture();
|
|
296
|
+
const { status, stderr } = run(
|
|
297
|
+
['append-section', file],
|
|
298
|
+
'### Duplicate\nid: issue-comment/111\nstatus: open\n',
|
|
299
|
+
);
|
|
300
|
+
assert.notEqual(status, 0);
|
|
301
|
+
assert.match(stderr, /a section with id "issue-comment\/111" already exists/);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('append-section fails when the file does not exist', () => {
|
|
305
|
+
const file = tmpFilePath();
|
|
306
|
+
const { status, stderr } = run(['append-section', file], '### X\nid: a/1\n');
|
|
307
|
+
assert.notEqual(status, 0);
|
|
308
|
+
assert.match(stderr, /file does not exist/);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('append-section fails when stdin is not a full section', () => {
|
|
312
|
+
const file = writeFixture();
|
|
313
|
+
const { status, stderr } = run(['append-section', file], 'not a section\nid: a/1\n');
|
|
314
|
+
assert.notEqual(status, 0);
|
|
315
|
+
assert.match(stderr, /must be a full section/);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('append-section fails when stdin has no id: line', () => {
|
|
319
|
+
const file = writeFixture();
|
|
320
|
+
const { status, stderr } = run(['append-section', file], '### No id here\nstatus: open\n');
|
|
321
|
+
assert.notEqual(status, 0);
|
|
322
|
+
assert.match(stderr, /missing an "id:" line/);
|
|
323
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"filedist": "^0.39.0"
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
"url": "https://github.com/flaviostutz/agentme.git"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"xdrs-core": "^0.
|
|
21
|
+
"xdrs-core": "^0.49.0"
|
|
22
22
|
}
|
|
23
23
|
}
|