@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.35 → 0.1.37

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 (84) hide show
  1. package/bin/adl +2 -2
  2. package/package.json +1 -1
  3. package/packages/agent-hooks/deploy-profile.json +1 -1
  4. package/packages/agent-hooks/src/pre-tool-use/handler.ts +2 -2
  5. package/packages/agent-hooks/src/rules/impl/adl-build-run-role.ts +116 -0
  6. package/packages/agent-hooks/src/rules/impl/adl-git-user-override-block.ts +159 -0
  7. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +12 -6
  8. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +9 -9
  9. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +6 -5
  10. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +15 -14
  11. package/packages/agent-hooks/src/rules/impl/git-core.ts +4 -8
  12. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +1 -1
  13. package/packages/agent-hooks/src/rules/registry.ts +11 -2
  14. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +8 -4
  15. package/packages/agent-skills/assets/code-review/SKILL.md +84 -66
  16. package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +23 -2
  17. package/packages/agent-skills/assets/diagnosing-bugs/references/FEEDBACK-LOOP-TACTICS.md +1 -1
  18. package/packages/agent-skills/assets/explore-codebase/SKILL.md +3 -2
  19. package/packages/agent-skills/assets/finalize-feature/SKILL.md +63 -0
  20. package/packages/agent-skills/assets/fix-ci/SKILL.md +58 -0
  21. package/packages/agent-skills/assets/handoff/SKILL.md +1 -1
  22. package/packages/agent-skills/assets/implement-feature/SKILL.md +153 -0
  23. package/packages/agent-skills/assets/implement-feature/references/briefs.md +94 -0
  24. package/packages/agent-skills/assets/implement-item/SKILL.md +71 -0
  25. package/packages/agent-skills/assets/mechanics/SKILL.md +38 -0
  26. package/packages/agent-skills/assets/plan-feature/SKILL.md +141 -0
  27. package/packages/agent-skills/assets/prepare-promotion/SKILL.md +91 -0
  28. package/packages/agent-skills/assets/ralplan/SKILL.md +149 -0
  29. package/packages/agent-skills/assets/refactor-safely/SKILL.md +4 -3
  30. package/packages/agent-skills/assets/report-bug/SKILL.md +72 -0
  31. package/packages/agent-skills/assets/review-change/SKILL.md +82 -0
  32. package/packages/agent-skills/assets/review-plan/SKILL.md +124 -0
  33. package/packages/agent-skills/assets/triage-workitems/SKILL.md +99 -0
  34. package/packages/agent-skills/assets/ultragoal/SKILL.md +77 -0
  35. package/packages/agent-skills/assets/validate-design/SKILL.md +70 -0
  36. package/packages/agent-skills/assets/verify-item/SKILL.md +71 -0
  37. package/packages/agent-skills/assets/workitems-process/SKILL.md +147 -0
  38. package/packages/agent-skills/assets/workitems-process/references/ado-and-git.md +194 -0
  39. package/packages/agent-skills/assets/workitems-process/references/evidence.md +74 -0
  40. package/packages/agent-skills/assets/workitems-process/references/workitem-states.md +92 -0
  41. package/packages/agent-skills/assets/write-feature/SKILL.md +95 -0
  42. package/packages/agent-skills/dist/deploy.d.ts +7 -0
  43. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  44. package/packages/agent-skills/dist/deploy.js +52 -5
  45. package/packages/agent-skills/dist/deploy.js.map +1 -1
  46. package/packages/agent-skills/src/deploy.ts +48 -4
  47. package/packages/azdo/scripts/probe-ado-rules.ts +1 -1
  48. package/packages/azdo/src/activity-log.ts +118 -0
  49. package/packages/azdo/src/azdo-attachments.ts +108 -0
  50. package/packages/azdo/src/azdo-pl.ts +100 -3
  51. package/packages/azdo/src/azdo-pr.ts +194 -11
  52. package/packages/azdo/src/azdo-wit.ts +329 -18
  53. package/packages/azdo/src/cli.ts +42 -2
  54. package/packages/azdo/src/lib/client.ts +7 -1
  55. package/packages/azdo/src/lib/validate-pr-metadata.ts +14 -5
  56. package/packages/azdo/src/mcp-server.ts +53 -5
  57. package/packages/azdo/src/tool-dispatch.ts +53 -4
  58. package/packages/azdo/src/workflow-ladder.csv +125 -107
  59. package/packages/azdo/src/workflow-rules.ts +254 -97
  60. package/packages/repo-controls/README.md +12 -4
  61. package/packages/repo-controls/assets/scaffold/docs/AGENTS.md +5 -5
  62. package/packages/repo-controls/assets/scaffold/plans/AGENTS.md +42 -13
  63. package/packages/repo-controls/assets/scaffold/root/CONTRIBUTING.md +5 -5
  64. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +0 -11
  65. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +4 -6
  66. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +15 -0
  67. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +1 -17
  68. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +3 -22
  69. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +39 -182
  70. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/features/az601-clean/intent.md +17 -0
  71. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +90 -321
  72. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +31 -5
  73. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +282 -54
  74. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/design.md +35 -0
  75. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/file-map-reference.md +19 -0
  76. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/intent.md +25 -0
  77. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +25 -0
  78. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/requirements.md +44 -0
  79. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/requirements.md +44 -0
  80. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +646 -53
  81. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +3 -3
  82. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +1 -12
  83. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +1 -1
  84. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +1 -1
@@ -3,10 +3,11 @@
3
3
  // repo invoked this file as a CLI; see mcp-server.ts's runTool() history).
4
4
  import {
5
5
  validatePullRequestDescription,
6
- validatePullRequestMetadata,
7
6
  validatePullRequestTitle,
8
7
  } from './lib/validate-pr-metadata.ts'
9
8
  import { AdoClient, PR_TARGET_BRANCH, adoProjectUrl, currentBranch, resolveRepository } from './lib/client.ts';
9
+ import { uploadAttachment } from './azdo-attachments.ts';
10
+ import { evaluateWorkflowTransition, formatWorkflowRefusal } from './workflow-rules.ts';
10
11
 
11
12
  let client: AdoClient | undefined
12
13
 
@@ -34,17 +35,22 @@ export function pullRequestWebUrl(repository: string, pullRequestId: number | st
34
35
  * the call succeeded, the tool reported success, and the PR stayed unarmed.
35
36
  * That silent no-op is why this is a named function with its own test rather
36
37
  * than an object literal at the call site.
38
+ *
39
+ * P2: `completionOptions.transitionWorkItems` is set by PR kind — true only
40
+ * for implementation and Fixes Bug PRs, where ADO moves the linked items to
41
+ * Done when the PR merges into integration (planning and ordinary PRs move
42
+ * nothing on merge).
37
43
  */
38
- export function autoCompleteBody(identityId: string): {
44
+ export function autoCompleteBody(identityId: string, transitionWorkItems = false): {
39
45
  autoCompleteSetBy: { id: string }
40
- completionOptions: { mergeStrategy: string; deleteSourceBranch: boolean }
46
+ completionOptions: { mergeStrategy: string; deleteSourceBranch: boolean; transitionWorkItems: boolean }
41
47
  } {
42
48
  if (!identityId) {
43
49
  throw new Error('cannot arm auto-complete: no identity to attribute it to')
44
50
  }
45
51
  return {
46
52
  autoCompleteSetBy: { id: identityId },
47
- completionOptions: { mergeStrategy: 'squash', deleteSourceBranch: true },
53
+ completionOptions: { mergeStrategy: 'squash', deleteSourceBranch: true, transitionWorkItems },
48
54
  }
49
55
  }
50
56
 
@@ -147,8 +153,11 @@ export async function prListOrGet(args: PrListOrGetArgs): Promise<string> {
147
153
  return JSON.stringify(prs, null, 2);
148
154
  }
149
155
 
150
- function assertValidCreateMetadata(title: string, description: string) {
151
- const metadataErrors = validatePullRequestMetadata(title, description)
156
+ function assertValidCreateMetadata(title: string, description: string, docsOnlySections: boolean) {
157
+ const metadataErrors = [
158
+ ...validatePullRequestTitle(title),
159
+ ...validatePullRequestDescription(description, { docsOnlySections }),
160
+ ]
152
161
  if (metadataErrors.length === 0) return
153
162
  throw new Error(metadataErrors.map((error) => `pr-policy: FAIL — ${error}`).join('\n'))
154
163
  }
@@ -169,6 +178,8 @@ export type PrCreateOrUpdateArgs = {
169
178
  autoComplete?: boolean;
170
179
  mergeStrategy?: string;
171
180
  deleteSourceBranch?: boolean;
181
+ /** P4: the content of the kind's mandatory Feature attachment (planning/implementation PRs only). */
182
+ attachmentContent?: string;
172
183
  /** Overrides `AZURE_DEVOPS_REPO` for this call. */
173
184
  repository?: string;
174
185
  /** Rejected removed routing input; PRs always use the checked-out branch. */
@@ -184,8 +195,130 @@ export function assertPrSourceBranch(source: string | undefined): asserts source
184
195
  if (source === 'main') throw new Error(`a pull request is never opened from main; open a branch into ${PR_TARGET_BRANCH}`);
185
196
  }
186
197
 
198
+ /**
199
+ * P1: the PR kind comes from the source branch naming a work item
200
+ * (azdo-git-process.md § PRs). `docs/az<id>-<slug>-plan` is a planning PR;
201
+ * `fix/az<bug id>-<slug>` is a Fixes Bug implementation PR naming one Bug;
202
+ * any other `<type>/az<id>-<slug>` branch names a Feature and is an
203
+ * implementation PR. Anything else is ordinary. The kind can still fall
204
+ * back to ordinary when the board holds no item in a wireable state.
205
+ */
206
+ export type PrKind = 'planning' | 'implementation' | 'ordinary';
207
+
208
+ export interface PrKindTarget {
209
+ kind: PrKind;
210
+ /** The work item the branch names (undefined for ordinary). */
211
+ itemId?: number;
212
+ itemType?: 'Feature' | 'Bug';
213
+ }
214
+
215
+ const PLANNING_BRANCH = /^docs\/az(\d+)-[a-z0-9-]+-plan$/;
216
+ const FIXES_BUG_BRANCH = /^fix\/az(\d+)-[a-z0-9-]+$/;
217
+ const FEATURE_BRANCH = /^(?:feat|chore|docs|perf|refactor)\/az(\d+)-[a-z0-9-]+$/;
218
+
219
+ export function derivePrKind(sourceBranch: string): PrKindTarget {
220
+ const planning = PLANNING_BRANCH.exec(sourceBranch);
221
+ if (planning) return { kind: 'planning', itemId: Number(planning[1]), itemType: 'Feature' };
222
+ const fixes = FIXES_BUG_BRANCH.exec(sourceBranch);
223
+ if (fixes) return { kind: 'implementation', itemId: Number(fixes[1]), itemType: 'Bug' };
224
+ const feature = FEATURE_BRANCH.exec(sourceBranch);
225
+ if (feature) return { kind: 'implementation', itemId: Number(feature[1]), itemType: 'Feature' };
226
+ return { kind: 'ordinary' };
227
+ }
228
+
229
+ /** The board states P1 wires, per kind. Done is never wired (terminal); neither are the parked/removed states. */
230
+ const PLANNING_WIREABLE: ReadonlySet<string> = new Set(['In Planning']);
231
+ const PBI_IMPLEMENTATION_WIREABLE: ReadonlySet<string> = new Set(['Approved', 'Design Validation', 'In Implementation', 'In Testing', 'In Review', 'Updating Docs']);
232
+ const BUG_IMPLEMENTATION_WIREABLE: ReadonlySet<string> = new Set(['Approved', 'In Reproduction', 'In Fixing', 'In Testing', 'In Review', 'Updating Docs']);
233
+
234
+ /** P2's raise-time move targets: planning items → Awaiting Approval; loop items → Updating Docs. */
235
+ const PBI_LOOP: ReadonlySet<string> = new Set(['In Implementation', 'In Testing', 'In Review', 'Updating Docs']);
236
+ const BUG_LOOP: ReadonlySet<string> = new Set(['In Fixing', 'In Testing', 'In Review', 'Updating Docs']);
237
+
238
+ export interface WiredItem {
239
+ id: number;
240
+ type: 'Product Backlog Item' | 'Bug';
241
+ state: string;
242
+ }
243
+
244
+ function relationIds(item: any, rel: string): number[] {
245
+ const relationId = (url: string) => Number(/(\d+)\/?$/.exec(url)?.[1]);
246
+ return (item.relations ?? [])
247
+ .filter((relation: any) => relation.rel === rel)
248
+ .map((relation: any) => relationId(relation.url))
249
+ .filter((id: number) => Number.isInteger(id));
250
+ }
251
+
252
+ /**
253
+ * P1: the items to wire, read from the board. Planning wires the Feature's
254
+ * in-scope items at In Planning; implementation wires the Feature's items
255
+ * (or the one Bug) at Approved or later. An empty result demotes the PR to
256
+ * ordinary — no links, no state moves, no attachment.
257
+ */
258
+ export async function resolveWiredItems(c: AdoClient, target: PrKindTarget): Promise<WiredItem[]> {
259
+ if (target.itemId === undefined || target.itemType === undefined) return [];
260
+ const item = await c.get<any>(`/wit/workitems/${target.itemId}`, { 'api-version': '7.1', $expand: 'all' });
261
+ if (target.itemType === 'Bug') {
262
+ const state = item.fields?.['System.State'];
263
+ return BUG_IMPLEMENTATION_WIREABLE.has(state) ? [{ id: item.id, type: 'Bug', state }] : [];
264
+ }
265
+ const wireable = target.kind === 'planning' ? PLANNING_WIREABLE : PBI_IMPLEMENTATION_WIREABLE;
266
+ const childIds = relationIds(item, 'System.LinkTypes.Hierarchy-Forward');
267
+ if (!childIds.length) return [];
268
+ const batch = await c.post<any>('/wit/workitemsbatch', { ids: childIds, $expand: 'all' }, { 'api-version': '7.1' });
269
+ return (batch.value ?? [])
270
+ .filter((child: any) => ['Product Backlog Item', 'Bug'].includes(child.fields?.['System.WorkItemType']))
271
+ .filter((child: any) => wireable.has(child.fields?.['System.State']))
272
+ .map((child: any) => ({ id: child.id, type: child.fields['System.WorkItemType'], state: child.fields['System.State'] }));
273
+ }
274
+
275
+ /** P2's raise-time target state for one wired item, or undefined when the item stays where it is. */
276
+ export function raiseMoveTarget(kind: PrKind, item: WiredItem): string | undefined {
277
+ if (kind === 'planning') return 'Awaiting Approval';
278
+ if (kind === 'implementation') {
279
+ const loop = item.type === 'Bug' ? BUG_LOOP : PBI_LOOP;
280
+ return loop.has(item.state) && item.state !== 'Updating Docs' ? 'Updating Docs' : undefined;
281
+ }
282
+ return undefined;
283
+ }
284
+
285
+ /**
286
+ * P2: move the linked items themselves when the PR is raised. Every move is
287
+ * pre-checked against the workflow fence (the same rule an agent write
288
+ * faces) so a PR is never raised half-wired.
289
+ */
290
+ async function moveWiredItems(c: AdoClient, kind: PrKind, items: WiredItem[]): Promise<void> {
291
+ for (const item of items) {
292
+ const to = raiseMoveTarget(kind, item);
293
+ if (to === undefined) continue;
294
+ const violations = evaluateWorkflowTransition(item.type, item.state, to);
295
+ if (violations.length) throw new Error(formatWorkflowRefusal(violations));
296
+ await c.patch<any>(
297
+ `/wit/workitems/${item.id}`,
298
+ [{ op: 'add', path: '/fields/System.State', value: to }],
299
+ { 'api-version': '7.1' },
300
+ 'application/json-patch+json',
301
+ );
302
+ }
303
+ }
304
+
305
+ /**
306
+ * P4: the work item the mandatory attachment hangs off — the named Feature,
307
+ * or, for a Fixes Bug PR, its Fixes Feature (board-rules.md § Attachments).
308
+ * The file name is completed by the caller once the PR id exists.
309
+ */
310
+ async function attachmentTargetWorkItemId(c: AdoClient, target: PrKindTarget): Promise<number> {
311
+ if (target.itemType === 'Bug') {
312
+ const bug = await c.get<any>(`/wit/workitems/${target.itemId}`, { 'api-version': '7.1', $expand: 'all' });
313
+ const [parentId] = relationIds(bug, 'System.LinkTypes.Hierarchy-Reverse');
314
+ if (parentId === undefined) throw new Error(`Fixes Bug ${target.itemId} has no parent Fixes Feature to attach the implementation report to (P4)`);
315
+ return parentId;
316
+ }
317
+ return target.itemId!;
318
+ }
319
+
187
320
  /** The exact next steps returned after PR creation, including live-stage failure polling. */
188
- export function prCreateResult(repository: string, pullRequestId: number, title: string, status: string, autoComplete: boolean): Record<string, unknown> {
321
+ export function prCreateResult(repository: string, pullRequestId: number, title: string, status: string, autoComplete: boolean, kind: PrKind = 'ordinary', wiredItems: number[] = []): Record<string, unknown> {
189
322
  const prRef = `refs/pull/${pullRequestId}/merge`;
190
323
  return {
191
324
  id: pullRequestId,
@@ -193,6 +326,8 @@ export function prCreateResult(repository: string, pullRequestId: number, title:
193
326
  status,
194
327
  url: pullRequestWebUrl(repository, pullRequestId),
195
328
  autoComplete,
329
+ kind,
330
+ workItems: wiredItems,
196
331
  summary: 'Creating this pull request started a validation build.',
197
332
  poll: [
198
333
  `1. build_list_or_get({ branch: '${prRef}' }) — take builds[0].id.`,
@@ -258,9 +393,42 @@ export async function prCreateOrUpdate(args: PrCreateOrUpdateArgs): Promise<stri
258
393
  assertPrSourceBranch(source);
259
394
  if (!title) throw new Error('title is required for create');
260
395
 
261
- assertValidCreateMetadata(title, description)
262
-
263
396
  const c = await getClient()
397
+
398
+ // P1: derive the kind from the source branch AND the board. No item in a
399
+ // wireable state demotes the PR to ordinary — nothing links, moves or
400
+ // attaches (the intent.md PR is the standing example).
401
+ const kindTarget = derivePrKind(source);
402
+ const wired = kindTarget.kind === 'ordinary' ? [] : await resolveWiredItems(c, kindTarget);
403
+ const kind: PrKind = wired.length ? kindTarget.kind : 'ordinary';
404
+
405
+ // P3: a planning PR may write "none (docs-only)" in the last two sections.
406
+ assertValidCreateMetadata(title, description, kind === 'planning')
407
+
408
+ // P4: the agent supplies the mandatory attachment's content; a planning or
409
+ // implementation PR refuses to raise without it. The planning attachment's
410
+ // file name comes from the Feature's plan fields and is validated here,
411
+ // before any PR exists.
412
+ let planningAttachment: { workItemId: number; fileName: string } | undefined;
413
+ let implAttachmentTarget: number | undefined;
414
+ if (kind !== 'ordinary') {
415
+ if (args.attachmentContent === undefined || args.attachmentContent.trim() === '') {
416
+ const article = kind === 'implementation' ? 'an' : 'a';
417
+ throw new Error(`${article} ${kind} PR requires attachmentContent — the mandatory ${kind === 'planning' ? 'plan-r<N>-<sha7>.md plan attachment' : 'impl-report-pr<id>.md implementation report'} (P4); refusing to raise the PR without it`)
418
+ }
419
+ if (kind === 'planning') {
420
+ const feature = await c.get<any>(`/wit/workitems/${kindTarget.itemId}`, { 'api-version': '7.1' });
421
+ const revision = feature.fields?.['Custom.PlanRevision'];
422
+ const hash = feature.fields?.['Custom.PlanHash'];
423
+ if (typeof revision !== 'number' || typeof hash !== 'string' || !hash) {
424
+ throw new Error('a planning PR needs the Feature\'s Custom.PlanRevision and Custom.PlanHash to name the mandatory plan attachment (P4)')
425
+ }
426
+ planningAttachment = { workItemId: kindTarget.itemId!, fileName: `plan-r${revision}-${hash.slice(0, 7)}.md` };
427
+ } else {
428
+ implAttachmentTarget = await attachmentTargetWorkItemId(c, kindTarget);
429
+ }
430
+ }
431
+
264
432
  await assertBranchReadyForPr(c, repository, source, target)
265
433
  const body: any = {
266
434
  sourceRefName: `refs/heads/${source}`,
@@ -268,6 +436,9 @@ export async function prCreateOrUpdate(args: PrCreateOrUpdateArgs): Promise<stri
268
436
  title,
269
437
  description,
270
438
  };
439
+ // P1: link the wired items at create, as `az repos pr create --work-items`
440
+ // does through workItemRefs on the create body.
441
+ if (wired.length) body.workItemRefs = wired.map((item) => ({ id: item.id }));
271
442
 
272
443
  const pr = await c.post<any>(
273
444
  pullRequestsPath(repository),
@@ -275,15 +446,27 @@ export async function prCreateOrUpdate(args: PrCreateOrUpdateArgs): Promise<stri
275
446
  { 'api-version': '7.1' }
276
447
  );
277
448
 
449
+ // P2: the tool moves the linked items itself at raise — planning items to
450
+ // Awaiting Approval, loop items to Updating Docs. Implementation PRs also
451
+ // arm transitionWorkItems so ADO lands the linked items on Done at merge.
452
+ await moveWiredItems(c, kind, wired);
453
+
454
+ // P4: attach the mandatory Feature attachment now that the PR id exists.
455
+ if (planningAttachment) {
456
+ await uploadAttachment(c, planningAttachment.workItemId, planningAttachment.fileName, args.attachmentContent!);
457
+ } else if (implAttachmentTarget !== undefined) {
458
+ await uploadAttachment(c, implAttachmentTarget, `impl-report-pr${pr.pullRequestId}.md`, args.attachmentContent!);
459
+ }
460
+
278
461
  if (arm) {
279
462
  // The PR we just created was created BY us, so its `createdBy` is the
280
463
  // authenticated identity — no extra lookup needed to attribute the arming.
281
464
  await c.patch<any>(
282
465
  pullRequestsPath(repository, pr.pullRequestId),
283
- autoCompleteBody(pr.createdBy?.id ?? ''),
466
+ autoCompleteBody(pr.createdBy?.id ?? '', kind === 'implementation'),
284
467
  { 'api-version': '7.1' }
285
468
  );
286
469
  }
287
470
 
288
- return JSON.stringify(prCreateResult(repository, pr.pullRequestId, pr.title, pr.status, arm), null, 2);
471
+ return JSON.stringify(prCreateResult(repository, pr.pullRequestId, pr.title, pr.status, arm, kind, wired.map((item) => item.id)), null, 2);
289
472
  }