agentplane 0.3.10 → 0.3.11

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 (228) hide show
  1. package/assets/policy/governance.md +3 -4
  2. package/assets/policy/incidents.md +19 -88
  3. package/assets/policy/workflow.branch_pr.md +1 -1
  4. package/assets/policy/workflow.direct.md +2 -2
  5. package/bin/agentplane.js +56 -1
  6. package/bin/runtime-watch.js +1 -0
  7. package/bin/stale-dist-policy.d.ts +1 -1
  8. package/bin/stale-dist-policy.js +13 -0
  9. package/dist/.build-manifest.json +219 -154
  10. package/dist/cli/bootstrap-guide.d.ts.map +1 -1
  11. package/dist/cli/bootstrap-guide.js +3 -2
  12. package/dist/cli/command-guide.d.ts.map +1 -1
  13. package/dist/cli/command-guide.js +2 -1
  14. package/dist/cli/command-invocations.d.ts.map +1 -1
  15. package/dist/cli/command-invocations.js +4 -1
  16. package/dist/cli/run-cli/command-catalog/project.d.ts +1 -1
  17. package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -1
  18. package/dist/cli/run-cli/command-catalog/project.js +3 -1
  19. package/dist/cli/run-cli/command-catalog/task.d.ts +1 -1
  20. package/dist/cli/run-cli/command-catalog/task.d.ts.map +1 -1
  21. package/dist/cli/run-cli/command-catalog/task.js +10 -0
  22. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  23. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  24. package/dist/cli/run-cli/commands/core/preflight.d.ts.map +1 -1
  25. package/dist/cli/run-cli/commands/core/preflight.js +44 -1
  26. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  27. package/dist/cli/run-cli.test-helpers.js +12 -0
  28. package/dist/commands/branch/cleanup-merged.d.ts +2 -0
  29. package/dist/commands/branch/cleanup-merged.d.ts.map +1 -1
  30. package/dist/commands/branch/cleanup-merged.js +132 -28
  31. package/dist/commands/branch/work-start.d.ts.map +1 -1
  32. package/dist/commands/branch/work-start.js +60 -1
  33. package/dist/commands/cleanup/merged.command.d.ts +2 -0
  34. package/dist/commands/cleanup/merged.command.d.ts.map +1 -1
  35. package/dist/commands/cleanup/merged.command.js +24 -0
  36. package/dist/commands/doctor/branch-pr.d.ts +4 -0
  37. package/dist/commands/doctor/branch-pr.d.ts.map +1 -0
  38. package/dist/commands/doctor/branch-pr.js +96 -0
  39. package/dist/commands/doctor/fixes.d.ts +5 -0
  40. package/dist/commands/doctor/fixes.d.ts.map +1 -1
  41. package/dist/commands/doctor/fixes.js +70 -0
  42. package/dist/commands/doctor.run.d.ts.map +1 -1
  43. package/dist/commands/doctor.run.js +6 -1
  44. package/dist/commands/finish.run.d.ts.map +1 -1
  45. package/dist/commands/finish.run.js +11 -0
  46. package/dist/commands/finish.spec.d.ts +11 -0
  47. package/dist/commands/finish.spec.d.ts.map +1 -1
  48. package/dist/commands/finish.spec.js +51 -0
  49. package/dist/commands/guard/impl/close-message.d.ts.map +1 -1
  50. package/dist/commands/guard/impl/close-message.js +23 -6
  51. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  52. package/dist/commands/guard/impl/commands.js +24 -2
  53. package/dist/commands/guard/impl/env.d.ts +1 -0
  54. package/dist/commands/guard/impl/env.d.ts.map +1 -1
  55. package/dist/commands/guard/impl/env.js +1 -0
  56. package/dist/commands/hooks/index.d.ts.map +1 -1
  57. package/dist/commands/hooks/index.js +98 -1
  58. package/dist/commands/incidents/collect.command.d.ts.map +1 -1
  59. package/dist/commands/incidents/collect.command.js +12 -7
  60. package/dist/commands/incidents/incidents.command.js +1 -1
  61. package/dist/commands/incidents/shared.d.ts +34 -0
  62. package/dist/commands/incidents/shared.d.ts.map +1 -1
  63. package/dist/commands/incidents/shared.js +166 -12
  64. package/dist/commands/pr/check.d.ts.map +1 -1
  65. package/dist/commands/pr/check.js +238 -135
  66. package/dist/commands/pr/close-superseded.d.ts +9 -0
  67. package/dist/commands/pr/close-superseded.d.ts.map +1 -0
  68. package/dist/commands/pr/close-superseded.js +129 -0
  69. package/dist/commands/pr/close.d.ts +11 -0
  70. package/dist/commands/pr/close.d.ts.map +1 -0
  71. package/dist/commands/pr/close.js +116 -0
  72. package/dist/commands/pr/index.d.ts +2 -0
  73. package/dist/commands/pr/index.d.ts.map +1 -1
  74. package/dist/commands/pr/index.js +2 -0
  75. package/dist/commands/pr/integrate/artifacts.d.ts +7 -0
  76. package/dist/commands/pr/integrate/artifacts.d.ts.map +1 -1
  77. package/dist/commands/pr/integrate/artifacts.js +66 -1
  78. package/dist/commands/pr/integrate/cmd.d.ts.map +1 -1
  79. package/dist/commands/pr/integrate/cmd.js +16 -0
  80. package/dist/commands/pr/integrate/internal/bootstrap-guidance.d.ts +8 -0
  81. package/dist/commands/pr/integrate/internal/bootstrap-guidance.d.ts.map +1 -0
  82. package/dist/commands/pr/integrate/internal/bootstrap-guidance.js +59 -0
  83. package/dist/commands/pr/integrate/internal/finalize.d.ts.map +1 -1
  84. package/dist/commands/pr/integrate/internal/finalize.js +40 -12
  85. package/dist/commands/pr/integrate/internal/merge.d.ts.map +1 -1
  86. package/dist/commands/pr/integrate/internal/merge.js +36 -13
  87. package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.d.ts +13 -0
  88. package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.d.ts.map +1 -0
  89. package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.js +25 -0
  90. package/dist/commands/pr/integrate/internal/prepare.d.ts +3 -2
  91. package/dist/commands/pr/integrate/internal/prepare.d.ts.map +1 -1
  92. package/dist/commands/pr/integrate/internal/prepare.js +101 -38
  93. package/dist/commands/pr/internal/freshness.d.ts +20 -0
  94. package/dist/commands/pr/internal/freshness.d.ts.map +1 -0
  95. package/dist/commands/pr/internal/freshness.js +50 -0
  96. package/dist/commands/pr/internal/gh-api.d.ts +6 -0
  97. package/dist/commands/pr/internal/gh-api.d.ts.map +1 -0
  98. package/dist/commands/pr/internal/gh-api.js +80 -0
  99. package/dist/commands/pr/internal/pr-paths.d.ts +10 -0
  100. package/dist/commands/pr/internal/pr-paths.d.ts.map +1 -1
  101. package/dist/commands/pr/internal/pr-paths.js +10 -0
  102. package/dist/commands/pr/internal/review-template.d.ts.map +1 -1
  103. package/dist/commands/pr/internal/review-template.js +37 -4
  104. package/dist/commands/pr/internal/sync.d.ts +9 -0
  105. package/dist/commands/pr/internal/sync.d.ts.map +1 -1
  106. package/dist/commands/pr/internal/sync.js +462 -122
  107. package/dist/commands/pr/open.d.ts +1 -0
  108. package/dist/commands/pr/open.d.ts.map +1 -1
  109. package/dist/commands/pr/open.js +13 -2
  110. package/dist/commands/pr/pr.command.d.ts +15 -0
  111. package/dist/commands/pr/pr.command.d.ts.map +1 -1
  112. package/dist/commands/pr/pr.command.js +118 -2
  113. package/dist/commands/pr/update.d.ts.map +1 -1
  114. package/dist/commands/pr/update.js +59 -1
  115. package/dist/commands/release/apply.command.d.ts.map +1 -1
  116. package/dist/commands/release/apply.command.js +3 -17
  117. package/dist/commands/release/apply.preflight.d.ts.map +1 -1
  118. package/dist/commands/release/apply.preflight.js +1 -1
  119. package/dist/commands/shared/gh-transport.d.ts +16 -0
  120. package/dist/commands/shared/gh-transport.d.ts.map +1 -0
  121. package/dist/commands/shared/gh-transport.js +71 -0
  122. package/dist/commands/shared/git-diff.d.ts +3 -1
  123. package/dist/commands/shared/git-diff.d.ts.map +1 -1
  124. package/dist/commands/shared/git-diff.js +10 -2
  125. package/dist/commands/shared/git-ops.d.ts +1 -0
  126. package/dist/commands/shared/git-ops.d.ts.map +1 -1
  127. package/dist/commands/shared/git-ops.js +15 -0
  128. package/dist/commands/shared/git-worktree.d.ts +2 -0
  129. package/dist/commands/shared/git-worktree.d.ts.map +1 -1
  130. package/dist/commands/shared/git-worktree.js +22 -2
  131. package/dist/commands/shared/post-commit-pr-artifacts.d.ts +9 -0
  132. package/dist/commands/shared/post-commit-pr-artifacts.d.ts.map +1 -0
  133. package/dist/commands/shared/post-commit-pr-artifacts.js +22 -0
  134. package/dist/commands/shared/pr-meta.d.ts +20 -0
  135. package/dist/commands/shared/pr-meta.d.ts.map +1 -1
  136. package/dist/commands/shared/pr-meta.js +125 -0
  137. package/dist/commands/shared/task-backend.d.ts +7 -0
  138. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  139. package/dist/commands/shared/task-backend.js +34 -22
  140. package/dist/commands/task/close-duplicate.d.ts.map +1 -1
  141. package/dist/commands/task/close-duplicate.js +34 -1
  142. package/dist/commands/task/derive.js +1 -1
  143. package/dist/commands/task/doc-template.d.ts.map +1 -1
  144. package/dist/commands/task/doc-template.js +7 -11
  145. package/dist/commands/task/findings-add.command.d.ts +20 -0
  146. package/dist/commands/task/findings-add.command.d.ts.map +1 -0
  147. package/dist/commands/task/findings-add.command.js +165 -0
  148. package/dist/commands/task/findings.command.d.ts +7 -0
  149. package/dist/commands/task/findings.command.d.ts.map +1 -0
  150. package/dist/commands/task/findings.command.js +20 -0
  151. package/dist/commands/task/findings.d.ts +63 -0
  152. package/dist/commands/task/findings.d.ts.map +1 -0
  153. package/dist/commands/task/findings.js +188 -0
  154. package/dist/commands/task/finish-shared.d.ts +1 -0
  155. package/dist/commands/task/finish-shared.d.ts.map +1 -1
  156. package/dist/commands/task/finish-shared.js +55 -1
  157. package/dist/commands/task/finish.d.ts +10 -0
  158. package/dist/commands/task/finish.d.ts.map +1 -1
  159. package/dist/commands/task/finish.js +125 -6
  160. package/dist/commands/task/hosted-close-pr.command.d.ts +11 -0
  161. package/dist/commands/task/hosted-close-pr.command.d.ts.map +1 -0
  162. package/dist/commands/task/hosted-close-pr.command.js +414 -0
  163. package/dist/commands/task/hosted-close.command.d.ts.map +1 -1
  164. package/dist/commands/task/hosted-close.command.js +49 -1
  165. package/dist/commands/task/hosted-merge-sync.d.ts +38 -0
  166. package/dist/commands/task/hosted-merge-sync.d.ts.map +1 -1
  167. package/dist/commands/task/hosted-merge-sync.js +249 -17
  168. package/dist/commands/task/index.d.ts +1 -0
  169. package/dist/commands/task/index.d.ts.map +1 -1
  170. package/dist/commands/task/index.js +1 -0
  171. package/dist/commands/task/new.d.ts +1 -0
  172. package/dist/commands/task/new.d.ts.map +1 -1
  173. package/dist/commands/task/new.js +71 -1
  174. package/dist/commands/task/new.spec.d.ts.map +1 -1
  175. package/dist/commands/task/new.spec.js +7 -0
  176. package/dist/commands/task/normalize.command.d.ts +2 -0
  177. package/dist/commands/task/normalize.command.d.ts.map +1 -1
  178. package/dist/commands/task/normalize.command.js +45 -0
  179. package/dist/commands/task/normalize.d.ts +2 -0
  180. package/dist/commands/task/normalize.d.ts.map +1 -1
  181. package/dist/commands/task/normalize.js +85 -8
  182. package/dist/commands/task/plan.d.ts.map +1 -1
  183. package/dist/commands/task/plan.js +7 -10
  184. package/dist/commands/task/shared/docs.d.ts +6 -0
  185. package/dist/commands/task/shared/docs.d.ts.map +1 -1
  186. package/dist/commands/task/shared/docs.js +14 -0
  187. package/dist/commands/task/shared/transitions.d.ts.map +1 -1
  188. package/dist/commands/task/shared/transitions.js +11 -1
  189. package/dist/commands/task/shared.d.ts +1 -1
  190. package/dist/commands/task/shared.d.ts.map +1 -1
  191. package/dist/commands/task/shared.js +1 -1
  192. package/dist/commands/task/start-ready.d.ts.map +1 -1
  193. package/dist/commands/task/start-ready.js +86 -0
  194. package/dist/commands/task/start.d.ts.map +1 -1
  195. package/dist/commands/task/start.js +7 -10
  196. package/dist/commands/task/task.command.d.ts.map +1 -1
  197. package/dist/commands/task/task.command.js +4 -0
  198. package/dist/commands/task/verify-command-shared.d.ts +19 -0
  199. package/dist/commands/task/verify-command-shared.d.ts.map +1 -1
  200. package/dist/commands/task/verify-command-shared.js +152 -1
  201. package/dist/commands/task/verify-ok.command.d.ts.map +1 -1
  202. package/dist/commands/task/verify-ok.command.js +15 -2
  203. package/dist/commands/task/verify-record.d.ts +36 -0
  204. package/dist/commands/task/verify-record.d.ts.map +1 -1
  205. package/dist/commands/task/verify-record.js +166 -11
  206. package/dist/commands/task/verify-rework.command.d.ts.map +1 -1
  207. package/dist/commands/task/verify-rework.command.js +15 -2
  208. package/dist/commands/task/verify-show.command.d.ts +1 -1
  209. package/dist/commands/task/verify-show.command.d.ts.map +1 -1
  210. package/dist/commands/task/verify-show.command.js +28 -1
  211. package/dist/commands/verify.run.d.ts.map +1 -1
  212. package/dist/commands/verify.run.js +12 -0
  213. package/dist/commands/verify.spec.d.ts +2 -6
  214. package/dist/commands/verify.spec.d.ts.map +1 -1
  215. package/dist/commands/verify.spec.js +30 -3
  216. package/dist/runtime/incidents/index.d.ts +1 -1
  217. package/dist/runtime/incidents/index.d.ts.map +1 -1
  218. package/dist/runtime/incidents/resolve.d.ts.map +1 -1
  219. package/dist/runtime/incidents/resolve.js +319 -73
  220. package/dist/runtime/incidents/types.d.ts +14 -2
  221. package/dist/runtime/incidents/types.d.ts.map +1 -1
  222. package/dist/shared/env.d.ts +1 -0
  223. package/dist/shared/env.d.ts.map +1 -1
  224. package/dist/shared/env.js +22 -1
  225. package/dist/shared/protected-paths.d.ts +1 -1
  226. package/dist/shared/protected-paths.d.ts.map +1 -1
  227. package/dist/shared/protected-paths.js +4 -0
  228. package/package.json +2 -2
@@ -1,5 +1,7 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
3
  import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
3
5
  import { loadConfig, resolveBaseBranch, resolveProject } from "@agentplaneorg/core";
4
6
  import { evaluatePolicy } from "../../policy/evaluate.js";
5
7
  import { mapBackendError, mapCoreError } from "../../cli/error-map.js";
@@ -9,10 +11,20 @@ import { CliError } from "../../shared/errors.js";
9
11
  import { GitContext } from "../shared/git-context.js";
10
12
  import { throwIfPolicyDenied } from "../shared/policy-deny.js";
11
13
  import { gitCurrentBranch, gitRevParse } from "../shared/git-ops.js";
14
+ import { parseTaskIdFromBranch, parseTaskIdFromCloseBranch } from "../shared/git-worktree.js";
12
15
  import { isPathWithin } from "../shared/path.js";
13
16
  const HOOK_MARKER = "agentplane-hook";
14
17
  const SHIM_MARKER = "agentplane-hook-shim";
15
18
  export const HOOK_NAMES = ["commit-msg", "pre-commit", "pre-push"];
19
+ async function inferTaskIdFromBranchContext(opts) {
20
+ try {
21
+ const branch = await gitCurrentBranch(opts.gitRoot);
22
+ return (parseTaskIdFromBranch(opts.taskPrefix, branch) ?? parseTaskIdFromCloseBranch(branch) ?? "");
23
+ }
24
+ catch {
25
+ return "";
26
+ }
27
+ }
16
28
  async function resolveGitHooksDir(cwd) {
17
29
  const repoRoot = await gitRevParse(cwd, ["--show-toplevel"]);
18
30
  const commonDirRaw = await gitRevParse(cwd, ["--git-common-dir"]);
@@ -116,6 +128,47 @@ function readCommitSubject(message) {
116
128
  }
117
129
  return "";
118
130
  }
131
+ function resolveBundledPrePushHookScriptPath() {
132
+ return fileURLToPath(new URL("../../../../../scripts/run-pre-push-hook.mjs", import.meta.url));
133
+ }
134
+ async function readHookStdinUtf8(timeoutMs = 25) {
135
+ if (process.stdin.isTTY)
136
+ return "";
137
+ const chunks = [];
138
+ const consume = () => {
139
+ let chunk = process.stdin.read();
140
+ while (chunk !== null) {
141
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
142
+ chunk = process.stdin.read();
143
+ }
144
+ };
145
+ consume();
146
+ if (chunks.length > 0 || process.stdin.readableEnded) {
147
+ return Buffer.concat(chunks).toString("utf8");
148
+ }
149
+ await new Promise((resolve) => {
150
+ const finish = () => {
151
+ clearTimeout(timer);
152
+ process.stdin.off("readable", onReadable);
153
+ process.stdin.off("end", onEnd);
154
+ resolve();
155
+ };
156
+ const onReadable = () => {
157
+ consume();
158
+ finish();
159
+ };
160
+ const onEnd = () => {
161
+ consume();
162
+ finish();
163
+ };
164
+ const timer = setTimeout(finish, timeoutMs);
165
+ process.stdin.on("readable", onReadable);
166
+ process.stdin.on("end", onEnd);
167
+ process.stdin.resume();
168
+ });
169
+ consume();
170
+ return Buffer.concat(chunks).toString("utf8");
171
+ }
119
172
  export async function cmdHooksInstall(opts) {
120
173
  try {
121
174
  const resolved = await resolveProject({
@@ -201,7 +254,11 @@ export async function cmdHooksRun(opts) {
201
254
  rootOverride: opts.rootOverride ?? null,
202
255
  });
203
256
  const loaded = await loadConfig(resolved.agentplaneDir);
204
- const taskId = (process.env.AGENTPLANE_TASK_ID ?? "").trim();
257
+ const taskId = (process.env.AGENTPLANE_TASK_ID ?? "").trim() ||
258
+ (await inferTaskIdFromBranchContext({
259
+ gitRoot: resolved.gitRoot,
260
+ taskPrefix: loaded.config.branch.task_prefix,
261
+ }));
205
262
  const statusTo = (process.env.AGENTPLANE_STATUS_TO ?? "").trim().toUpperCase();
206
263
  const emoji = subject.split(/\s+/).find(Boolean) ?? "";
207
264
  if (taskId && statusTo === "DONE" && emoji !== "✅") {
@@ -269,9 +326,49 @@ export async function cmdHooksRun(opts) {
269
326
  throwIfPolicyDenied(res);
270
327
  return 0;
271
328
  }
329
+ if (opts.hook === "pre-push") {
330
+ const resolved = await resolveProject({
331
+ cwd: opts.cwd,
332
+ rootOverride: opts.rootOverride ?? null,
333
+ });
334
+ const scriptPath = resolveBundledPrePushHookScriptPath();
335
+ if (!(await fileExists(scriptPath))) {
336
+ throw new CliError({
337
+ exitCode: 2,
338
+ code: "E_USAGE",
339
+ message: `Missing pre-push hook script: ${scriptPath}`,
340
+ });
341
+ }
342
+ const result = spawnSync("node", [scriptPath], {
343
+ cwd: resolved.gitRoot,
344
+ env: process.env,
345
+ encoding: "utf8",
346
+ input: await readHookStdinUtf8(),
347
+ stdio: "pipe",
348
+ });
349
+ if (typeof result.stdout === "string" && result.stdout.length > 0) {
350
+ process.stdout.write(result.stdout);
351
+ }
352
+ if (typeof result.stderr === "string" && result.stderr.length > 0) {
353
+ process.stderr.write(result.stderr);
354
+ }
355
+ if (result.error)
356
+ throw result.error;
357
+ return result.status ?? (result.signal ? 1 : 0);
358
+ }
272
359
  return 0;
273
360
  }
274
361
  catch (err) {
362
+ const status = err?.status;
363
+ const stdout = err?.stdout;
364
+ const stderr = err?.stderr;
365
+ if (typeof stdout === "string" && stdout.length > 0)
366
+ process.stdout.write(stdout);
367
+ if (typeof stderr === "string" && stderr.length > 0)
368
+ process.stderr.write(stderr);
369
+ if (typeof status === "number" && Number.isInteger(status) && status >= 0) {
370
+ return status;
371
+ }
275
372
  if (err instanceof CliError)
276
373
  throw err;
277
374
  throw mapBackendError(err, {
@@ -1 +1 @@
1
- {"version":3,"file":"collect.command.d.ts","sourceRoot":"","sources":["../../../src/commands/incidents/collect.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGpF,KAAK,sBAAsB,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAIF,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,sBAAsB,CAoCpE,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC/E,KAAK,UAAU,EAAE,GAAG,sBAAsB,KAAG,OAAO,CAAC,MAAM,CAAC,CA+B3E"}
1
+ {"version":3,"file":"collect.command.d.ts","sourceRoot":"","sources":["../../../src/commands/incidents/collect.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGpF,KAAK,sBAAsB,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAIF,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,sBAAsB,CAmCpE,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC/E,KAAK,UAAU,EAAE,GAAG,sBAAsB,KAAG,OAAO,CAAC,MAAM,CAAC,CAsC3E"}
@@ -1,11 +1,11 @@
1
1
  import { createCliEmitter } from "../../cli/output.js";
2
2
  import { loadCommandContext } from "../shared/task-backend.js";
3
- import { collectTaskIncidents } from "./shared.js";
3
+ import { collectTaskIncidents, renderIncidentCollectionPlanOutcome } from "./shared.js";
4
4
  const output = createCliEmitter();
5
5
  export const incidentsCollectSpec = {
6
6
  id: ["incidents", "collect"],
7
7
  group: "Policy",
8
- summary: "Promote structured external incident-candidates from a task into the incident registry.",
8
+ summary: "Promote reusable resolved external findings from a task into the incident registry.",
9
9
  args: [{ name: "task-id", required: true, valueHint: "<task-id>" }],
10
10
  options: [
11
11
  {
@@ -28,7 +28,7 @@ export const incidentsCollectSpec = {
28
28
  },
29
29
  {
30
30
  cmd: "agentplane incidents collect 202604031416-HEJWTM --check --json",
31
- why: "Validate that incident-candidate blocks are complete before finish.",
31
+ why: "Validate that reusable resolved external findings are complete before finish.",
32
32
  },
33
33
  ],
34
34
  parse: (raw) => ({
@@ -51,17 +51,22 @@ export function makeRunIncidentsCollectHandler(getCtx) {
51
51
  task_id: p.taskId,
52
52
  checked_only: p.check,
53
53
  candidates: result.plan.candidates.length,
54
+ skipped: result.plan.skipped,
54
55
  promotable: result.plan.promotable.map((item) => item.entry),
55
56
  duplicates: result.plan.duplicates.map((item) => item.entry.id),
56
57
  wrote: result.wrote,
57
58
  registry_path: result.registryPath,
59
+ registry_paths: result.registryPaths,
58
60
  });
59
61
  return 0;
60
62
  }
61
- output.success(p.check ? "checked" : "collected", p.taskId, `candidates=${result.plan.candidates.length} promoted=${result.plan.promotable.length} duplicates=${result.plan.duplicates.length}`);
62
- if (result.plan.promotable.length > 0 && !p.check) {
63
- output.info(`Incident registry updated: ${result.registryPath}`);
64
- }
63
+ output.success(p.check ? "checked" : "collected", p.taskId, `candidates=${result.plan.candidates.length} skipped=${result.plan.skipped.length} promoted=${result.plan.promotable.length} duplicates=${result.plan.duplicates.length}`);
64
+ output.info(renderIncidentCollectionPlanOutcome(result.plan, {
65
+ wrote: result.wrote,
66
+ context: "collect",
67
+ promotedIds: result.plan.promotable.map((item) => item.entry.id),
68
+ registryPaths: result.registryPaths,
69
+ }));
65
70
  return 0;
66
71
  };
67
72
  }
@@ -6,7 +6,7 @@ export const incidentsSpec = {
6
6
  synopsis: ["agentplane incidents <collect|advise> [args] [options]"],
7
7
  args: [{ name: "cmd", required: false, variadic: true, valueHint: "<command>" }],
8
8
  notes: [
9
- "Use `incidents collect` to promote structured incident-candidate findings into `.agentplane/policy/incidents.md`.",
9
+ "Use `incidents collect` to promote resolved reusable external findings into `.agentplane/policy/incidents.md`.",
10
10
  "Use `incidents advise` to query registry advice by task id or lightweight scope/tags.",
11
11
  ],
12
12
  parse: (raw) => parseGroupCommand(raw),
@@ -2,6 +2,7 @@ import type { TaskData } from "../../backends/task-backend.js";
2
2
  import { type IncidentAdviceMatch, type IncidentAdviceQuery, type IncidentCollectionPlan, type IncidentRegistry } from "../../runtime/incidents/index.js";
3
3
  import type { CommandContext } from "../shared/task-backend.js";
4
4
  export declare const INCIDENTS_POLICY_PATH = ".agentplane/policy/incidents.md";
5
+ export declare const INCIDENTS_POLICY_ASSET_PATH = "packages/agentplane/assets/policy/incidents.md";
5
6
  export type LoadedTaskIncidents = {
6
7
  task: TaskData;
7
8
  findings: string;
@@ -9,6 +10,7 @@ export type LoadedTaskIncidents = {
9
10
  query: IncidentAdviceQuery;
10
11
  };
11
12
  export declare function incidentRegistryPath(ctx: CommandContext): string;
13
+ export declare function incidentRegistryAssetPath(ctx: CommandContext): string;
12
14
  export declare function loadIncidentRegistry(ctx: CommandContext): Promise<{
13
15
  registryPath: string;
14
16
  registryText: string;
@@ -25,11 +27,43 @@ export declare function collectTaskIncidents(opts: {
25
27
  }): Promise<{
26
28
  loaded: LoadedTaskIncidents;
27
29
  registryPath: string;
30
+ registryPaths: string[];
28
31
  registryText: string;
29
32
  registry: IncidentRegistry;
30
33
  plan: IncidentCollectionPlan;
31
34
  wrote: boolean;
32
35
  }>;
36
+ export declare function inspectTaskIncidents(opts: {
37
+ ctx: CommandContext;
38
+ taskId: string;
39
+ task?: TaskData | null;
40
+ now?: Date;
41
+ }): Promise<{
42
+ loaded: LoadedTaskIncidents;
43
+ registryPath: string;
44
+ registryPaths: string[];
45
+ registryText: string;
46
+ registry: IncidentRegistry;
47
+ plan: IncidentCollectionPlan;
48
+ }>;
49
+ export declare function renderIncidentCollectionOutcome(promotedCount: number): string;
50
+ export declare function renderIncidentCollectionPlanOutcome(plan: {
51
+ candidates?: readonly unknown[];
52
+ skipped?: readonly unknown[];
53
+ promotable?: readonly unknown[];
54
+ duplicates?: readonly unknown[];
55
+ issues?: readonly {
56
+ missingFields?: readonly string[];
57
+ }[];
58
+ findingsTextPresent?: boolean;
59
+ structuredFindingCount?: number;
60
+ }, opts?: {
61
+ wrote?: boolean;
62
+ context?: "collect" | "verify" | "finish" | "generic";
63
+ promotedIds?: readonly string[];
64
+ registryPaths?: readonly string[];
65
+ taskId?: string | null;
66
+ }): string;
33
67
  export declare function adviseTaskIncidents(opts: {
34
68
  ctx: CommandContext;
35
69
  taskId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/incidents/shared.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAG/D,OAAO,EAOL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACtB,MAAM,kCAAkC,CAAC;AAM1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,eAAO,MAAM,qBAAqB,oCAAoC,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC;CAC5B,CAAC;AAYF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAEhE;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IACvE,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,gBAAgB,CAAC;CAC5B,CAAC,CAQD;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,QAAQ,GAAG,IAAI,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAyB9B;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,sBAAsB,GAC3B,MAAM,CAUR;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,mBAAmB,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,sBAAsB,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC,CA+BD;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,mBAAmB,EAAE,CAAC;CAChC,CAAC,CAWD"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/incidents/shared.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ import { writeTextIfChanged } from "../../shared/write-if-changed.js";
5
5
  import { appendIncidentRegistryEntries, buildIncidentAdviceQueryFromTask, createIncidentRegistrySkeleton, parseIncidentRegistry, planIncidentCollection, resolveIncidentAdviceMatches, } from "../../runtime/incidents/index.js";
6
6
  import { extractDocSection, extractTaskObservationSection, normalizeTaskDocVersion, } from "../task/shared.js";
7
7
  export const INCIDENTS_POLICY_PATH = ".agentplane/policy/incidents.md";
8
+ export const INCIDENTS_POLICY_ASSET_PATH = "packages/agentplane/assets/policy/incidents.md";
9
+ const INCIDENTS_POLICY_LINE_BUDGET = 100;
8
10
  async function readTextIfExists(filePath) {
9
11
  try {
10
12
  return await readFile(filePath, "utf8");
@@ -19,6 +21,41 @@ async function readTextIfExists(filePath) {
19
21
  export function incidentRegistryPath(ctx) {
20
22
  return path.join(ctx.resolvedProject.gitRoot, INCIDENTS_POLICY_PATH);
21
23
  }
24
+ export function incidentRegistryAssetPath(ctx) {
25
+ return path.join(ctx.resolvedProject.gitRoot, INCIDENTS_POLICY_ASSET_PATH);
26
+ }
27
+ function normalizeIncidentRegistryDocument(text) {
28
+ const normalized = text.replaceAll("\r\n", "\n").trimEnd();
29
+ return normalized.length > 0 ? `${normalized}\n` : createIncidentRegistrySkeleton();
30
+ }
31
+ async function writeIncidentRegistryMirrors(ctx, content) {
32
+ const registryPath = incidentRegistryPath(ctx);
33
+ const assetPath = incidentRegistryAssetPath(ctx);
34
+ const assetExists = (await readTextIfExists(assetPath)) !== null;
35
+ const normalizedContent = normalizeIncidentRegistryDocument(content);
36
+ let wroteRegistry = await writeTextIfChanged(registryPath, normalizedContent);
37
+ let wroteAsset = false;
38
+ if (assetExists) {
39
+ const canonicalText = (await readTextIfExists(registryPath)) ?? normalizedContent;
40
+ wroteAsset = await writeTextIfChanged(assetPath, canonicalText);
41
+ const registryText = await readTextIfExists(registryPath);
42
+ if (registryText !== canonicalText) {
43
+ wroteRegistry = (await writeTextIfChanged(registryPath, canonicalText)) || wroteRegistry;
44
+ }
45
+ }
46
+ return wroteRegistry || wroteAsset;
47
+ }
48
+ async function resolveIncidentRegistryMirrorPaths(ctx) {
49
+ const paths = [INCIDENTS_POLICY_PATH];
50
+ const assetPath = incidentRegistryAssetPath(ctx);
51
+ if ((await readTextIfExists(assetPath)) !== null) {
52
+ paths.push(INCIDENTS_POLICY_ASSET_PATH);
53
+ }
54
+ return paths;
55
+ }
56
+ function countTextLines(text) {
57
+ return text.replaceAll("\r\n", "\n").split("\n").length;
58
+ }
22
59
  export async function loadIncidentRegistry(ctx) {
23
60
  const registryPath = incidentRegistryPath(ctx);
24
61
  const registryText = (await readTextIfExists(registryPath)) ?? createIncidentRegistrySkeleton();
@@ -60,19 +97,48 @@ export function formatIncidentCollectionIssues(taskId, plan) {
60
97
  return `line ${issue.candidate.line}: ${scope} -> missing ${issue.missingFields.join(", ")}`;
61
98
  });
62
99
  return [
63
- `${taskId}: incident-candidate entries require explicit external incident metadata before promotion.`,
100
+ `${taskId}: reusable external findings need explicit external marking and enough recovery detail before promotion.`,
64
101
  "Required fields:",
65
102
  ...issueLines.map((line) => `- ${line}`),
66
103
  ].join("\n");
67
104
  }
68
105
  export async function collectTaskIncidents(opts) {
106
+ const inspected = await inspectTaskIncidents(opts);
107
+ const { loaded, registryPath, registryPaths, registryText, registry, plan } = inspected;
108
+ if (plan.issues.length > 0) {
109
+ throw new CliError({
110
+ exitCode: 3,
111
+ code: "E_VALIDATION",
112
+ message: formatIncidentCollectionIssues(opts.taskId, plan),
113
+ });
114
+ }
115
+ const nextText = appendIncidentRegistryEntries(registryText, plan.promotable.map((item) => item.entry));
116
+ if (plan.promotable.length > 0) {
117
+ const nextLineCount = countTextLines(nextText);
118
+ if (nextLineCount > INCIDENTS_POLICY_LINE_BUDGET) {
119
+ throw new CliError({
120
+ exitCode: 3,
121
+ code: "E_VALIDATION",
122
+ message: `Incident registry write would exceed policy budget: ${nextLineCount} lines ` +
123
+ `(limit ${INCIDENTS_POLICY_LINE_BUDGET}). Compact or promote fewer entries before writing.`,
124
+ });
125
+ }
126
+ }
127
+ const wrote = opts.write && plan.promotable.length > 0
128
+ ? await writeIncidentRegistryMirrors(opts.ctx, nextText)
129
+ : false;
130
+ return { loaded, registryPath, registryPaths, registryText, registry, plan, wrote };
131
+ }
132
+ export async function inspectTaskIncidents(opts) {
69
133
  const loaded = await loadTaskIncidents(opts.ctx, opts.taskId, opts.task ?? null);
70
134
  const { registryPath, registryText, registry } = await loadIncidentRegistry(opts.ctx);
135
+ const registryPaths = await resolveIncidentRegistryMirrorPaths(opts.ctx);
71
136
  const plan = planIncidentCollection({
72
137
  task: {
73
138
  id: loaded.task.id,
74
139
  title: loaded.task.title,
75
140
  description: loaded.task.description,
141
+ scope: loaded.scope,
76
142
  tags: loaded.task.tags ?? [],
77
143
  commitHash: loaded.task.commit?.hash ?? null,
78
144
  },
@@ -80,18 +146,106 @@ export async function collectTaskIncidents(opts) {
80
146
  registry,
81
147
  now: opts.now,
82
148
  });
83
- if (plan.issues.length > 0) {
84
- throw new CliError({
85
- exitCode: 3,
86
- code: "E_VALIDATION",
87
- message: formatIncidentCollectionIssues(opts.taskId, plan),
88
- });
149
+ return { loaded, registryPath, registryPaths, registryText, registry, plan };
150
+ }
151
+ export function renderIncidentCollectionOutcome(promotedCount) {
152
+ return promotedCount > 0
153
+ ? `incident registry updated (${promotedCount} promoted)`
154
+ : "incident registry unchanged (no promotable external findings)";
155
+ }
156
+ function summarizeDetailList(values, maxItems = 3) {
157
+ const filtered = values.map((value) => value.trim()).filter((value) => value.length > 0);
158
+ if (filtered.length === 0)
159
+ return null;
160
+ if (filtered.length <= maxItems)
161
+ return filtered.join(", ");
162
+ return `${filtered.slice(0, maxItems).join(", ")}, +${filtered.length - maxItems} more`;
163
+ }
164
+ export function renderIncidentCollectionPlanOutcome(plan, opts) {
165
+ const candidates = Array.isArray(plan.candidates) ? plan.candidates.length : 0;
166
+ const skipped = Array.isArray(plan.skipped) ? plan.skipped.length : 0;
167
+ const promoted = Array.isArray(plan.promotable) ? plan.promotable.length : 0;
168
+ const duplicates = Array.isArray(plan.duplicates) ? plan.duplicates.length : 0;
169
+ const issues = Array.isArray(plan.issues) ? plan.issues.length : 0;
170
+ const findingsTextPresent = plan.findingsTextPresent === true;
171
+ const structuredFindingCount = typeof plan.structuredFindingCount === "number" ? plan.structuredFindingCount : 0;
172
+ const wrote = opts?.wrote === true;
173
+ const context = opts?.context ?? "generic";
174
+ const taskId = typeof opts?.taskId === "string" && opts.taskId.trim().length > 0 ? opts.taskId.trim() : null;
175
+ const findingsNextStep = taskId
176
+ ? ` next: agentplane task findings add ${taskId} --observation "<observation>" --impact "<impact>" --resolution "<resolution>"`
177
+ : "";
178
+ if (promoted > 0 && wrote) {
179
+ const suffix = [];
180
+ if (duplicates > 0)
181
+ suffix.push(`${duplicates} duplicate${duplicates === 1 ? "" : "s"}`);
182
+ if (skipped > 0)
183
+ suffix.push(`${skipped} skipped structured finding${skipped === 1 ? "" : "s"}`);
184
+ const base = suffix.length > 0
185
+ ? `incident registry updated (${promoted} promoted; ${suffix.join("; ")})`
186
+ : renderIncidentCollectionOutcome(promoted);
187
+ const details = [];
188
+ const promotedIds = Array.isArray(opts?.promotedIds)
189
+ ? opts.promotedIds.filter((id) => typeof id === "string" && id.trim().length > 0)
190
+ : [];
191
+ const registryPaths = Array.isArray(opts?.registryPaths)
192
+ ? opts.registryPaths.filter((filePath) => typeof filePath === "string" && filePath.trim().length > 0)
193
+ : [];
194
+ const idsSummary = summarizeDetailList(promotedIds);
195
+ const pathsSummary = summarizeDetailList(registryPaths);
196
+ if (idsSummary)
197
+ details.push(`ids=${idsSummary}`);
198
+ if (pathsSummary)
199
+ details.push(`files=${pathsSummary}`);
200
+ return details.length > 0 ? `${base} ${details.join(" ")}` : base;
89
201
  }
90
- const nextText = appendIncidentRegistryEntries(registryText, plan.promotable.map((item) => item.entry));
91
- const wrote = opts.write && plan.promotable.length > 0
92
- ? await writeTextIfChanged(registryPath, nextText)
93
- : false;
94
- return { loaded, registryPath, registryText, registry, plan, wrote };
202
+ if (promoted > 0 && !wrote) {
203
+ if (context === "collect") {
204
+ return `incident registry unchanged (${promoted} promotable external finding${promoted === 1 ? "" : "s"} validated; rerun without --check to update incidents.md)`;
205
+ }
206
+ if (context === "verify") {
207
+ return `incident registry unchanged (${promoted} promotable external finding${promoted === 1 ? "" : "s"} stayed task-local in the current task worktree; run verify --collect-incidents, agentplane incidents collect <task-id>, or finish on the base branch to update incidents.md)`;
208
+ }
209
+ return `incident registry unchanged (${promoted} promotable external finding${promoted === 1 ? "" : "s"} pending promotion)`;
210
+ }
211
+ if (issues > 0) {
212
+ const issueEntries = Array.isArray(plan.issues)
213
+ ? plan.issues
214
+ : [];
215
+ const firstIssue = issueEntries[0];
216
+ const rawMissingFields = firstIssue?.missingFields;
217
+ const missingFields = Array.isArray(rawMissingFields)
218
+ ? rawMissingFields.filter((field) => typeof field === "string" && field.trim().length > 0)
219
+ : [];
220
+ const detail = missingFields.length > 0
221
+ ? ` missing required fields: ${missingFields.join(", ")}`
222
+ : " missing required promotion fields";
223
+ const suffix = issues > 1 ? `; +${issues - 1} more candidate${issues - 1 === 1 ? "" : "s"}` : "";
224
+ return `incident registry unchanged (${issues} structured finding candidate${issues === 1 ? "" : "s"} still invalid;${detail}${suffix})`;
225
+ }
226
+ if (skipped > 0) {
227
+ return `incident registry unchanged (${skipped} structured finding${skipped === 1 ? "" : "s"} stayed task-local in the current checkout: mark reusable external findings with Promotion: incident-candidate plus Fixability: external, or use task findings add without --local-only)`;
228
+ }
229
+ if (candidates === 0 && structuredFindingCount === 0 && findingsTextPresent) {
230
+ return "incident registry unchanged (plain Findings text stays task-local in the current checkout and does not update incidents.md: add a structured Observation/Impact/Resolution block for reusable external incidents, or use task findings add without --local-only)";
231
+ }
232
+ if (candidates === 0) {
233
+ if (context === "verify") {
234
+ return ("incident registry unchanged (plain verify note stayed task-local and did not update " +
235
+ "incidents.md: add --observation, --impact, and --resolution for a reusable incident, " +
236
+ `then rerun with --collect-incidents or collect later on the base branch.${findingsNextStep})`);
237
+ }
238
+ if (context === "finish") {
239
+ return ("incident registry unchanged (plain finish body/result stayed task-local and did not " +
240
+ "update incidents.md: add --observation, --impact, and --resolution for a reusable " +
241
+ `incident before closeout.${findingsNextStep})`);
242
+ }
243
+ return "incident registry unchanged (no structured incident findings)";
244
+ }
245
+ if (duplicates > 0 && duplicates === candidates) {
246
+ return `incident registry unchanged (${duplicates} duplicate incident${duplicates === 1 ? "" : "s"} already recorded)`;
247
+ }
248
+ return "incident registry unchanged (no promotable external findings)";
95
249
  }
96
250
  export async function adviseTaskIncidents(opts) {
97
251
  const loaded = await loadTaskIncidents(opts.ctx, opts.taskId, opts.task ?? null);
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/commands/pr/check.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AA8DnC,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiMlB"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/commands/pr/check.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAgLnC,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2NlB"}