agentme 0.36.0 → 0.37.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 (56) hide show
  1. package/.filedist-package.yml +1 -1
  2. package/.xdrs/agentme/bdrs/index.md +2 -1
  3. package/.xdrs/agentme/bdrs/operations/{401-epic-feature-story-planning.md → 401-plan-epic-feature-story.md} +2 -2
  4. package/.xdrs/agentme/bdrs/operations/402-digital-product-roles.md +15 -19
  5. package/.xdrs/agentme/bdrs/operations/403-product-team-composition.md +3 -0
  6. package/.xdrs/agentme/bdrs/operations/404-team-roles-and-specialists.md +61 -82
  7. package/.xdrs/agentme/bdrs/operations/405-digital-product-roles-raci.md +131 -0
  8. package/.xdrs/agentme/bdrs/principles/002-okr-framework.md +1 -1
  9. package/.xdrs/agentme/bdrs/principles/003-product-delivery-value-flow.md +3 -2
  10. package/.xdrs/agentme/edrs/application/101-javascript-project-tooling.md +1 -1
  11. package/.xdrs/agentme/edrs/application/102-golang-project-tooling.md +1 -1
  12. package/.xdrs/agentme/edrs/application/103-python-project-tooling.md +1 -1
  13. package/.xdrs/agentme/edrs/application/127-external-system-adapter-skills.md +2 -0
  14. package/.xdrs/agentme/edrs/application/skills/{251-azure-devops-connector → azure-devops-connector}/SKILL.md +6 -9
  15. package/.xdrs/agentme/edrs/application/skills/{251-azure-devops-connector → azure-devops-connector}/SKILL.test.md +1 -1
  16. package/.xdrs/agentme/edrs/application/skills/{051-create-golang-project → create-golang-project}/SKILL.md +1 -1
  17. package/.xdrs/agentme/edrs/application/skills/create-golang-project/SKILL.test.md +72 -0
  18. package/.xdrs/agentme/edrs/application/skills/{050-create-javascript-project → create-javascript-project}/SKILL.md +23 -24
  19. package/.xdrs/agentme/edrs/application/skills/create-javascript-project/SKILL.test.md +72 -0
  20. package/.xdrs/agentme/edrs/application/skills/{052-create-python-project → create-python-project}/SKILL.md +1 -1
  21. package/.xdrs/agentme/edrs/application/skills/create-python-project/SKILL.test.md +79 -0
  22. package/.xdrs/agentme/edrs/application/skills/{250-github-connector → github-connector}/SKILL.md +5 -8
  23. package/.xdrs/agentme/edrs/application/skills/{250-github-connector → github-connector}/SKILL.test.md +1 -1
  24. package/.xdrs/agentme/edrs/application/skills/{200-run-skill-tests → run-skill-tests}/SKILL.md +4 -4
  25. package/.xdrs/agentme/edrs/application/skills/{200-run-skill-tests → run-skill-tests}/SKILL.test.md +7 -7
  26. package/.xdrs/agentme/edrs/application/skills/{010-select-relevant-xdrs → select-relevant-xdrs}/SKILL.md +1 -1
  27. package/.xdrs/agentme/edrs/application/skills/select-relevant-xdrs/SKILL.test.md +73 -0
  28. package/.xdrs/agentme/edrs/governance/502-contributing-guide-requirements.md +1 -1
  29. package/.xdrs/agentme/edrs/index.md +16 -11
  30. package/.xdrs/agentme/edrs/operations/402-dora-metrics-framework.md +82 -0
  31. package/.xdrs/agentme/edrs/operations/403-dora-deployment-frequency.md +37 -0
  32. package/.xdrs/agentme/edrs/operations/404-dora-lead-time-for-changes.md +41 -0
  33. package/.xdrs/agentme/edrs/operations/405-dora-change-failure-rate.md +42 -0
  34. package/.xdrs/agentme/edrs/operations/406-dora-time-to-restore-service.md +42 -0
  35. package/.xdrs/agentme/edrs/platform/301-monorepo-structure.md +1 -1
  36. package/.xdrs/agentme/edrs/platform/skills/{053-monorepo-setup → monorepo-setup}/SKILL.md +15 -15
  37. package/.xdrs/agentme/edrs/platform/skills/monorepo-setup/SKILL.test.md +80 -0
  38. package/.xdrs/agentme/edrs/principles/012-continuous-xdr-enrichment.md +1 -1
  39. package/.xdrs/agentme/edrs/principles/017-skill-testing.md +4 -4
  40. package/.xdrs/agentme/edrs/principles/articles/001-continuous-xdr-improvement.md +1 -1
  41. package/.xdrs/agentme/edrs/principles/skills/{150-refine-plan-mode → refine-plan-mode}/SKILL.md +2 -8
  42. package/.xdrs/agentme/edrs/principles/skills/{150-refine-plan-mode → refine-plan-mode}/SKILL.test.md +2 -2
  43. package/.xdrs/agentme/edrs/principles/skills/{151-refine-user-story → refine-user-story}/SKILL.md +4 -11
  44. package/.xdrs/agentme/edrs/principles/skills/{151-refine-user-story → refine-user-story}/SKILL.test.md +17 -17
  45. package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/Makefile +1 -1
  46. package/.xdrs/agentme/edrs/principles/skills/resolve-pr-comments/SKILL.md +662 -0
  47. package/.xdrs/agentme/edrs/principles/skills/resolve-pr-comments/SKILL.test.md +165 -0
  48. package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-azure-devops.js +3 -3
  49. package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-azure-devops.test.js +5 -5
  50. package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-github.js +3 -3
  51. package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/post-replies-github.test.js +4 -4
  52. package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/update-section.js +84 -3
  53. package/.xdrs/agentme/edrs/principles/skills/{400-pr-owner-assistant → resolve-pr-comments}/scripts/update-section.test.js +108 -1
  54. package/package.json +2 -2
  55. package/.xdrs/agentme/edrs/principles/skills/400-pr-owner-assistant/SKILL.md +0 -633
  56. 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-owner-assistant tracking-file drafts to a live Azure DevOps PR, using the
6
- * `az rest` commands documented in 251-azure-devops-connector's "Writing data" section.
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 251-azure-devops-connector's Known Issues: a POST/PATCH
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-owner-assistant - with user
79
- guidance)
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-owner-assistant - with
94
- defaults)
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-owner-assistant - with user guidance)';
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-owner-assistant tracking-file drafts to a live GitHub PR, using the `gh`
6
- * commands documented in 250-github-connector's "Writing data" section. Delegates every
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 251-azure-devops-connector's Known Issues), but so both scripts
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-owner-assistant skill - using defaults)
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-owner-assistant skill - using
133
- defaults)
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-owner-assistant skill - using defaults)', in_reply_to_id: 91234 }],
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-owner-assistant tracking file
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 = { id: '', status: '', action: '', 'pending-reply': '', 'resolve-on-apply': '' };
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-owner-assistant skill - using defaults)
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,12 @@ 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);
78
91
  });
79
92
 
80
93
  test('get returns a scalar field value', () => {
@@ -197,3 +210,97 @@ test('set-block on a scalar-only field fails and points to set', () => {
197
210
  assert.notEqual(status, 0);
198
211
  assert.match(stderr, /use set \(scalar\)/);
199
212
  });
213
+
214
+ test('get/set support the automation-suggestion scalar field', () => {
215
+ const file = writeFixture();
216
+ const { stdout: before } = run(['get', file, 'review-comment/222', 'automation-suggestion']);
217
+ assert.equal(before.trim(), 'fully-auto');
218
+ const { status } = run(['set', file, 'review-comment/222', 'automation-suggestion', 'guided']);
219
+ assert.equal(status, 0);
220
+ const { stdout: after } = run(['get', file, 'review-comment/222', 'automation-suggestion']);
221
+ assert.equal(after.trim(), 'guided');
222
+ });
223
+
224
+ test('get/set-list support the similar-to list field', () => {
225
+ const file = writeFixture();
226
+ const { stdout: before } = run(['get', file, 'review-comment/222', 'similar-to']);
227
+ assert.equal(before, 'review-comment/333\n');
228
+ const { status } = run(
229
+ ['set-list', file, 'review-comment/222', 'similar-to'],
230
+ 'review-comment/333\nreview-comment/444\n',
231
+ );
232
+ assert.equal(status, 0);
233
+ const { stdout: after } = run(['get', file, 'review-comment/222', 'similar-to']);
234
+ assert.equal(after, 'review-comment/333\nreview-comment/444\n');
235
+ });
236
+
237
+ test('init creates a new tracking file with the PR header and raw summary', () => {
238
+ const file = tmpFilePath();
239
+ const { status, stdout } = run(
240
+ ['init', file, '999', 'https://github.com/acme/widget/pull/999', 'Adds', 'retry', 'logic'],
241
+ 'Raw PR body text.\n\nSecond paragraph.\n',
242
+ );
243
+ assert.equal(status, 0);
244
+ assert.equal(stdout.trim(), 'OK');
245
+ const content = fs.readFileSync(file, 'utf8');
246
+ assert.equal(
247
+ content,
248
+ '# 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',
249
+ );
250
+ });
251
+
252
+ test('init refuses to overwrite an existing file', () => {
253
+ const file = writeFixture();
254
+ const { status, stderr } = run(
255
+ ['init', file, '999', 'https://example.com/pr/999', 'Some', 'summary'],
256
+ 'Raw body.\n',
257
+ );
258
+ assert.notEqual(status, 0);
259
+ assert.match(stderr, /refusing to overwrite existing file/);
260
+ });
261
+
262
+ test('append-section adds a new section after the last one, separated by exactly one blank line', () => {
263
+ const file = writeFixture();
264
+ const newSection =
265
+ '### 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';
266
+ const { status } = run(['append-section', file], newSection);
267
+ assert.equal(status, 0);
268
+ const content = fs.readFileSync(file, 'utf8');
269
+ assert.match(
270
+ content,
271
+ /\(resolve-pr-comments - guided\)\n\n### New finding\nid: issue-comment\/777/,
272
+ );
273
+ const { stdout } = run(['list', file]);
274
+ assert.match(stdout, /- id: issue-comment\/777/);
275
+ });
276
+
277
+ test('append-section fails when the id already exists', () => {
278
+ const file = writeFixture();
279
+ const { status, stderr } = run(
280
+ ['append-section', file],
281
+ '### Duplicate\nid: issue-comment/111\nstatus: open\n',
282
+ );
283
+ assert.notEqual(status, 0);
284
+ assert.match(stderr, /a section with id "issue-comment\/111" already exists/);
285
+ });
286
+
287
+ test('append-section fails when the file does not exist', () => {
288
+ const file = tmpFilePath();
289
+ const { status, stderr } = run(['append-section', file], '### X\nid: a/1\n');
290
+ assert.notEqual(status, 0);
291
+ assert.match(stderr, /file does not exist/);
292
+ });
293
+
294
+ test('append-section fails when stdin is not a full section', () => {
295
+ const file = writeFixture();
296
+ const { status, stderr } = run(['append-section', file], 'not a section\nid: a/1\n');
297
+ assert.notEqual(status, 0);
298
+ assert.match(stderr, /must be a full section/);
299
+ });
300
+
301
+ test('append-section fails when stdin has no id: line', () => {
302
+ const file = writeFixture();
303
+ const { status, stderr } = run(['append-section', file], '### No id here\nstatus: open\n');
304
+ assert.notEqual(status, 0);
305
+ assert.match(stderr, /missing an "id:" line/);
306
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentme",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
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.47.0"
21
+ "xdrs-core": "^0.49.0"
22
22
  }
23
23
  }