engineering-memory 1.11.29 → 1.11.31

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.
@@ -0,0 +1,135 @@
1
+ import * as z from 'zod/v4';
2
+ import { endpoints } from '../config.js';
3
+ import { concludedReview } from './merge-request-sync.js';
4
+ import { copies, format } from './texts.js';
5
+ const text = z.string().min(1);
6
+ const commentWording = z.strictObject({
7
+ round: text,
8
+ passed: text,
9
+ skipped: text,
10
+ exempted: text,
11
+ findingsOf: text,
12
+ rule: text,
13
+ open: text,
14
+ fixed: text,
15
+ rejected: text,
16
+ });
17
+ export const commentReviewSchema = z.object({
18
+ delivery: z.object({
19
+ id: z.string(),
20
+ taskId: z.string(),
21
+ externalTaskId: z.string(),
22
+ workItemKey: z.string().nullish(),
23
+ reviewState: z.string().nullish(),
24
+ reviewChangedAt: z.string().nullish(),
25
+ reviewExemptionReason: z.string().nullish(),
26
+ reviewCommentPostedAt: z.string().nullish(),
27
+ reviewRequest: z
28
+ .object({
29
+ provider: z.string(),
30
+ host: z.string(),
31
+ repository: z.string(),
32
+ number: z.number().int().positive(),
33
+ url: z.string(),
34
+ state: z.string(),
35
+ })
36
+ .nullish(),
37
+ }),
38
+ rounds: z.array(z.object({
39
+ id: z.string(),
40
+ number: z.number().int(),
41
+ state: z.string(),
42
+ commentPostedAt: z.string().nullish(),
43
+ })),
44
+ totalRounds: z.number().int().optional(),
45
+ findings: z.array(z.object({
46
+ position: z.number().int(),
47
+ path: z.string(),
48
+ line: z.number().int().nullish(),
49
+ ruleKey: z.string().nullish(),
50
+ description: z.string(),
51
+ resolution: z.string(),
52
+ rejectionReason: z.string().nullish(),
53
+ fixedCommit: z.string().nullish(),
54
+ })),
55
+ });
56
+ export function dueComment(review, language) {
57
+ const { delivery, findings } = review;
58
+ const { copy, language: shown } = copies(commentWording, 'reviewComment', language)[0];
59
+ const task = delivery.workItemKey ?? delivery.externalTaskId;
60
+ const where = (finding) => '`' + (finding.path + (finding.line ? `:${finding.line}` : '')).replace(/`/g, '') + '`';
61
+ const latest = review.rounds.find((round) => round.state === 'submitted');
62
+ if (delivery.reviewState === 'findings_open' &&
63
+ latest?.commentPostedAt === null &&
64
+ findings.length) {
65
+ const marker = `<!-- engineering-memory review-round ${latest.id} -->`;
66
+ const lines = findings.map((finding) => `${finding.position}. ${where(finding)}` +
67
+ (finding.ruleKey
68
+ ? ` (${format(copy.rule, shown, { rule: oneLine(finding.ruleKey) })})`
69
+ : '') +
70
+ `: ${oneLine(finding.description)}`);
71
+ return {
72
+ kind: 'round',
73
+ roundId: latest.id,
74
+ marker,
75
+ body: [
76
+ marker,
77
+ `**${format(copy.round, shown, { task, round: latest.number, count: findings.length })}**`,
78
+ '',
79
+ ...lines,
80
+ ].join('\n'),
81
+ };
82
+ }
83
+ const outcome = delivery.reviewState;
84
+ if (!outcome ||
85
+ !concludedReview.includes(outcome) ||
86
+ !delivery.reviewChangedAt ||
87
+ delivery.reviewCommentPostedAt !== null ||
88
+ !['draft', 'ready'].includes(delivery.reviewRequest?.state ?? ''))
89
+ return null;
90
+ const marker = `<!-- engineering-memory review-conclusion ${delivery.id} ${delivery.reviewChangedAt} -->`;
91
+ const title = outcome === 'passed'
92
+ ? format(copy.passed, shown, { task, rounds: review.totalRounds ?? review.rounds.length })
93
+ : outcome === 'skipped'
94
+ ? format(copy.skipped, shown, { task })
95
+ : format(copy.exempted, shown, {
96
+ task,
97
+ reason: oneLine(delivery.reviewExemptionReason ?? ''),
98
+ });
99
+ const status = (finding) => finding.resolution === 'fixed'
100
+ ? format(copy.fixed, shown, { commit: (finding.fixedCommit ?? '').slice(0, 12) })
101
+ : finding.resolution === 'rejected'
102
+ ? format(copy.rejected, shown, { reason: oneLine(finding.rejectionReason ?? '') })
103
+ : copy.open;
104
+ return {
105
+ kind: 'conclusion',
106
+ concludedAt: delivery.reviewChangedAt,
107
+ marker,
108
+ body: [
109
+ marker,
110
+ `**${title}**`,
111
+ ...(findings.length && latest
112
+ ? [
113
+ '',
114
+ format(copy.findingsOf, shown, { round: latest.number }),
115
+ ...findings.map((finding) => `${finding.position}. ${where(finding)}: ${status(finding)}`),
116
+ ]
117
+ : []),
118
+ ].join('\n'),
119
+ };
120
+ }
121
+ export async function markComment(client, projectId, taskId, comment) {
122
+ await client
123
+ .request(comment.kind === 'round'
124
+ ? endpoints.reviewRoundComment(projectId, comment.roundId)
125
+ : endpoints.reviewComment(projectId, taskId), {
126
+ method: 'POST',
127
+ ...(comment.kind === 'conclusion' ? { body: { concludedAt: comment.concludedAt } } : {}),
128
+ })
129
+ .catch(() => undefined);
130
+ }
131
+ function oneLine(value) {
132
+ const line = value.replace(/\s+/g, ' ').trim();
133
+ return (line.length > 1500 ? `${line.slice(0, 1499)}…` : line).replace(/@/g, '@​');
134
+ }
135
+ //# sourceMappingURL=review-comment.js.map
@@ -304,12 +304,14 @@ When the user later says a pull request became ready, was merged or was closed,
304
304
 
305
305
  A project can review code before a pull request is ready: `project.review_policy` reads its setting — none, optional or required — and, only when the user asks to change it, `change: true` asks them in one native form; only an owner or maintainer can change it. Under optional the delivery question offers a pull request reviewed first, which opens as a draft, and one without review; under required only the reviewed one. A user's own instruction for a ready pull request under required becomes the reviewed one, and the tool says so; relay it. When the tool says the setting changed while the question was open, tell the user and call it again.
306
306
 
307
- After a reviewed pull request is pushed and the worktree released, open it as a draft (`task.open_review_request`, or your own tools and `task.review_request` with `draft`), then call `review.start` in a clone of the project. It fetches the pushed branch without checking anything out and returns the review packet. Give the packet to a fresh subagent that did not write the change; only when your host cannot start one, review it yourself under the same rules. The reviewer reads the change only through the read-only Git commands the packet lists: it never checks out, switches branches, edits, commits, pushes or changes a file. It judges the change against the packet's records (more with `review.rules`) and says whether each earlier finding still holds. Each finding names the file by its path in the repository, the line in the reviewed commit when there is one, the key of the record it breaks when one applies, and what is wrong and why in plain words. Record the result with `review.submit`, an empty list when there are no findings. Describe a problem without the value itself: e-mail addresses, IP addresses and user folders are masked, and a description that still looks like it holds a secret or personal data is refused.
307
+ After a reviewed pull request is pushed and the worktree released, open it as a draft (`task.open_review_request`, or your own tools and `task.review_request` with `draft`), then call `review.start` in a clone of the project. It fetches the pushed branch without checking anything out and returns the review packet. Give the packet to a fresh subagent that did not write the change; only when your host cannot start one, review it yourself under the same rules. The reviewer reads the change only through the read-only Git commands the packet lists: it never checks out, switches branches, edits, commits, pushes or changes a file. It judges the change against the packet's records (more with `review.rules`) and says whether each earlier finding still holds; from the second round on it starts from `sinceLastRound`, what changed since the previous round. Each finding names the file by its path in the repository, the line in the reviewed commit when there is one, the key of the record it breaks when one applies, and what is wrong and why in plain words. Record the result with `review.submit`, an empty list when there are no findings. Describe a problem without the value itself: e-mail addresses, IP addresses and user folders are masked, and a description that still looks like it holds a secret or personal data is refused.
308
308
 
309
309
  Show the findings to the user and ask whether to fix them. A fix is a write task: `task.branch` with `reviewFixOf` set to the reviewed task's id, then `session.bootstrap` with the same `reviewFixOf`; it continues the pull request's branch, and its delivery offers commit or commit and push onto that branch — never a second pull request. After its push the open findings are marked fixed; ask whether to review again (`review.start`) or end the review (`review.conclude` with passed). A finding the user disagrees with is rejected with `review.reject_finding`, where the user writes the reason in the form; never write it for them. `review.conclude` also skips a review where the setting is not required, and exempts one under required, which only an owner or maintainer can do and only with the reason the user writes. Every one of these asks the user in a native form first.
310
310
 
311
311
  Once a review has ended, call `review.make_ready`: on GitLab, with the user's own token saved, it removes Draft and records the pull request ready; anywhere else follow its `nextAction` (for example `gh pr ready`) and record the state with `task.review_request`. It also makes ready, when the user asks, a pull request that has no code review. Never mark a draft ready while its review has not ended. `session.entry` lists as `awaitingReviews` the deliveries that wait for code review; mention them in one line and start nothing on your own. When the user asks, `review.queue` and `review.get` read them, and every member except readers can continue any review where it stopped.
312
312
 
313
+ The review is written on the pull request as comments: the findings of each round and the conclusion. On GitLab the bridge writes them with the user's saved token; when a result of `review.submit`, `review.reject_finding`, `review.conclude` or `review.make_ready` carries `comment.body`, post that body unchanged as a comment with your own tools (on GitHub, `gh pr comment` with the body in a file) — it is recorded as handed over and not handed over again. When you record a pull request with `task.review_request`, pass `headCommit` and `targetBranch` when you can read them (on GitHub, `gh pr view --json headRefOid,baseRefName`): a review that had passed waits again when its pull request gets a new commit or a new target branch, and the answer says so; tell the user in one line, and do not pass it again without a round — `review.start` runs the next one, and skipping or exempting still asks the user. An `awaitingReviews` entry shows `reopened`, `readyBeforeReview` and `pullRequestChange`: a make-ready or make-draft that a member computer with a GitLab token does in the background, with its last failure. Mention a failure in one line; on GitHub, when the answer asks for a draft again, make it a draft with your own tools (`gh pr ready --undo`) and record it with `task.review_request`.
314
+
313
315
  ## Code source and memory applicability
314
316
 
315
317
  The bridge sends the checkout's committed Git commit and tree at bootstrap. Task and session
@@ -454,3 +456,24 @@ roles stay as they are; change an aimed role separately with work_item.setup_wor
454
456
  bound to the counts it showed: when they change, the question is asked again, and a change at the
455
457
  moment of writing moves nothing. It is critical under the task mode. An empty source needs no
456
458
  question. Keep the same requestKey to resume and honor deferral as in the flows above.
459
+
460
+ ## Jira connection
461
+
462
+ When the user asks to connect Jira, to link the project to a Jira project or to match people, start
463
+ with `jira.status`: it reads the bound project's link and the organization's connections and names
464
+ the next step. `jira.connect` returns a one-time link: give it to the user as it is and never open it
465
+ yourself; they open it on this computer and approve access in Atlassian. Call `jira.connect` again
466
+ without `restart` to learn how it ended, and while it still waits, wait for the user; `restart: true`
467
+ only discards a link the user no longer wants. When Jira is not configured on the server, say that
468
+ the person who runs the server must add its settings, and do not retry. A connection that needs
469
+ reconnecting is renewed with `jira.connect`.
470
+
471
+ `jira.link_project`, `jira.unlink_project`, `jira.disconnect` and `jira.map_people` take a
472
+ `requestKey`: reuse it to retry or continue the same request, and use a new one when the user asks
473
+ again later. Every choice here (the site, the Jira project, replacing or pausing a link,
474
+ disconnecting, who each person is) is asked of the user natively in every task mode; never answer it
475
+ yourself. Pass a Jira project key the user names as `jiraProjectKey`. Match a person only from the
476
+ user's answer and never guess a handle; a typed e-mail address is refused, so ask for the part before
477
+ `@`. The access only reads Jira and Jira issues are not brought in yet, so do not promise writing to
478
+ Jira or importing issues. After a disconnect the user can also remove the app on Atlassian's side, at
479
+ id.atlassian.com → Connected apps, as the tool says.