agentplane 0.3.4 → 0.3.6

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 (217) hide show
  1. package/README.md +103 -75
  2. package/assets/AGENTS.md +4 -2
  3. package/bin/dist-guard.js +13 -3
  4. package/bin/runtime-watch.d.ts +1 -0
  5. package/bin/runtime-watch.js +22 -5
  6. package/bin/stale-dist-policy.js +9 -2
  7. package/dist/.build-manifest.json +220 -790
  8. package/dist/adapters/task-backend/task-backend-adapter.d.ts +1 -1
  9. package/dist/adapters/task-backend/task-backend-adapter.d.ts.map +1 -1
  10. package/dist/adapters/task-backend/task-backend-adapter.js +5 -2
  11. package/dist/backends/task-backend/local-backend.d.ts +13 -0
  12. package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
  13. package/dist/backends/task-backend/local-backend.js +17 -0
  14. package/dist/backends/task-backend/redmine-backend.d.ts +18 -0
  15. package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
  16. package/dist/backends/task-backend/redmine-backend.js +35 -25
  17. package/dist/backends/task-backend/shared/types.d.ts +20 -0
  18. package/dist/backends/task-backend/shared/types.d.ts.map +1 -1
  19. package/dist/backends/task-backend/shared.d.ts +1 -1
  20. package/dist/backends/task-backend/shared.d.ts.map +1 -1
  21. package/dist/backends/task-backend.d.ts +1 -1
  22. package/dist/backends/task-backend.d.ts.map +1 -1
  23. package/dist/backends/task-backend.test-helpers.d.ts +4 -0
  24. package/dist/backends/task-backend.test-helpers.d.ts.map +1 -0
  25. package/dist/backends/task-backend.test-helpers.js +33 -0
  26. package/dist/cli/bootstrap-guide.d.ts.map +1 -1
  27. package/dist/cli/bootstrap-guide.js +1 -0
  28. package/dist/cli/command-guide.d.ts.map +1 -1
  29. package/dist/cli/command-guide.js +3 -2
  30. package/dist/cli/reason-codes.d.ts.map +1 -1
  31. package/dist/cli/reason-codes.js +30 -0
  32. package/dist/cli/run-cli/command-catalog/core.d.ts +3 -0
  33. package/dist/cli/run-cli/command-catalog/core.d.ts.map +1 -0
  34. package/dist/cli/run-cli/command-catalog/core.js +137 -0
  35. package/dist/cli/run-cli/command-catalog/lifecycle.d.ts +3 -0
  36. package/dist/cli/run-cli/command-catalog/lifecycle.d.ts.map +1 -0
  37. package/dist/cli/run-cli/command-catalog/lifecycle.js +52 -0
  38. package/dist/cli/run-cli/command-catalog/project.d.ts +3 -0
  39. package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -0
  40. package/dist/cli/run-cli/command-catalog/project.js +78 -0
  41. package/dist/cli/run-cli/command-catalog/shared.d.ts +19 -0
  42. package/dist/cli/run-cli/command-catalog/shared.d.ts.map +1 -0
  43. package/dist/cli/run-cli/command-catalog/shared.js +9 -0
  44. package/dist/cli/run-cli/command-catalog/task.d.ts +3 -0
  45. package/dist/cli/run-cli/command-catalog/task.d.ts.map +1 -0
  46. package/dist/cli/run-cli/command-catalog/task.js +85 -0
  47. package/dist/cli/run-cli/command-catalog.d.ts +3 -18
  48. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  49. package/dist/cli/run-cli/command-catalog.js +8 -337
  50. package/dist/cli/run-cli/commands/ide.d.ts.map +1 -1
  51. package/dist/cli/run-cli/commands/ide.js +64 -2
  52. package/dist/cli/run-cli/commands/init/ui.d.ts.map +1 -1
  53. package/dist/cli/run-cli/commands/init/ui.js +33 -13
  54. package/dist/cli/run-cli.core.pr-flow.test-helpers.d.ts +3 -0
  55. package/dist/cli/run-cli.core.pr-flow.test-helpers.d.ts.map +1 -0
  56. package/dist/cli/run-cli.core.pr-flow.test-helpers.js +41 -0
  57. package/dist/cli/run-cli.core.tasks.test-helpers.d.ts +2 -0
  58. package/dist/cli/run-cli.core.tasks.test-helpers.d.ts.map +1 -0
  59. package/dist/cli/run-cli.core.tasks.test-helpers.js +6 -0
  60. package/dist/cli/run-cli.test-helpers.d.ts +3 -0
  61. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  62. package/dist/cli/run-cli.test-helpers.js +138 -6
  63. package/dist/commands/commit.spec.d.ts.map +1 -1
  64. package/dist/commands/commit.spec.js +2 -2
  65. package/dist/commands/doctor/runtime.d.ts.map +1 -1
  66. package/dist/commands/doctor/runtime.js +3 -6
  67. package/dist/commands/doctor/workspace.d.ts +4 -1
  68. package/dist/commands/doctor/workspace.d.ts.map +1 -1
  69. package/dist/commands/doctor/workspace.js +87 -4
  70. package/dist/commands/doctor.run.d.ts.map +1 -1
  71. package/dist/commands/doctor.run.js +8 -1
  72. package/dist/commands/guard/commit.command.js +1 -1
  73. package/dist/commands/guard/impl/allow.d.ts +5 -0
  74. package/dist/commands/guard/impl/allow.d.ts.map +1 -1
  75. package/dist/commands/guard/impl/allow.js +15 -10
  76. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  77. package/dist/commands/guard/impl/commands.js +137 -18
  78. package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
  79. package/dist/commands/guard/impl/comment-commit.js +2 -0
  80. package/dist/commands/hooks/index.d.ts.map +1 -1
  81. package/dist/commands/hooks/index.js +8 -35
  82. package/dist/commands/recipes/impl/apply.d.ts +4 -0
  83. package/dist/commands/recipes/impl/apply.d.ts.map +1 -1
  84. package/dist/commands/recipes/impl/apply.js +34 -0
  85. package/dist/commands/recipes/impl/commands/explain.d.ts.map +1 -1
  86. package/dist/commands/recipes/impl/commands/explain.js +70 -11
  87. package/dist/commands/recipes/impl/commands/info.d.ts.map +1 -1
  88. package/dist/commands/recipes/impl/commands/info.js +24 -12
  89. package/dist/commands/recipes/impl/commands/install.d.ts.map +1 -1
  90. package/dist/commands/recipes/impl/commands/install.js +32 -36
  91. package/dist/commands/recipes/impl/commands/list.d.ts.map +1 -1
  92. package/dist/commands/recipes/impl/commands/list.js +7 -4
  93. package/dist/commands/recipes/impl/commands/remove.d.ts.map +1 -1
  94. package/dist/commands/recipes/impl/commands/remove.js +9 -11
  95. package/dist/commands/recipes/impl/constants.d.ts +2 -0
  96. package/dist/commands/recipes/impl/constants.d.ts.map +1 -1
  97. package/dist/commands/recipes/impl/constants.js +2 -0
  98. package/dist/commands/recipes/impl/manifest.d.ts.map +1 -1
  99. package/dist/commands/recipes/impl/manifest.js +219 -23
  100. package/dist/commands/recipes/impl/normalize.d.ts +3 -0
  101. package/dist/commands/recipes/impl/normalize.d.ts.map +1 -1
  102. package/dist/commands/recipes/impl/normalize.js +28 -24
  103. package/dist/commands/recipes/impl/paths.d.ts +9 -0
  104. package/dist/commands/recipes/impl/paths.d.ts.map +1 -1
  105. package/dist/commands/recipes/impl/paths.js +10 -1
  106. package/dist/commands/recipes/impl/project-installed-recipes.d.ts +7 -0
  107. package/dist/commands/recipes/impl/project-installed-recipes.d.ts.map +1 -0
  108. package/dist/commands/recipes/impl/project-installed-recipes.js +102 -0
  109. package/dist/commands/recipes/impl/resolver.d.ts +20 -0
  110. package/dist/commands/recipes/impl/resolver.d.ts.map +1 -0
  111. package/dist/commands/recipes/impl/resolver.js +220 -0
  112. package/dist/commands/recipes/impl/scenario.d.ts.map +1 -1
  113. package/dist/commands/recipes/impl/scenario.js +40 -11
  114. package/dist/commands/recipes/impl/types.d.ts +145 -16
  115. package/dist/commands/recipes/impl/types.d.ts.map +1 -1
  116. package/dist/commands/recipes/install.spec.d.ts.map +1 -1
  117. package/dist/commands/recipes/install.spec.js +3 -2
  118. package/dist/commands/recipes.d.ts +6 -4
  119. package/dist/commands/recipes.d.ts.map +1 -1
  120. package/dist/commands/recipes.js +5 -3
  121. package/dist/commands/recipes.test-helpers.d.ts +185 -0
  122. package/dist/commands/recipes.test-helpers.d.ts.map +1 -0
  123. package/dist/commands/recipes.test-helpers.js +339 -0
  124. package/dist/commands/scenario/impl/commands.d.ts.map +1 -1
  125. package/dist/commands/scenario/impl/commands.js +192 -336
  126. package/dist/commands/scenario/info.command.d.ts.map +1 -1
  127. package/dist/commands/scenario/info.command.js +7 -2
  128. package/dist/commands/scenario/list.command.js +2 -2
  129. package/dist/commands/scenario/run.command.d.ts.map +1 -1
  130. package/dist/commands/scenario/run.command.js +7 -2
  131. package/dist/commands/shared/git-context.d.ts +1 -0
  132. package/dist/commands/shared/git-context.d.ts.map +1 -1
  133. package/dist/commands/shared/git-context.js +4 -0
  134. package/dist/commands/shared/reconcile-check.d.ts.map +1 -1
  135. package/dist/commands/shared/reconcile-check.js +77 -2
  136. package/dist/commands/shared/task-backend.d.ts +5 -0
  137. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  138. package/dist/commands/shared/task-backend.js +24 -0
  139. package/dist/commands/shared/task-store.d.ts +32 -1
  140. package/dist/commands/shared/task-store.d.ts.map +1 -1
  141. package/dist/commands/shared/task-store.js +166 -42
  142. package/dist/commands/task/block.d.ts.map +1 -1
  143. package/dist/commands/task/block.js +46 -29
  144. package/dist/commands/task/close-duplicate.d.ts.map +1 -1
  145. package/dist/commands/task/close-duplicate.js +12 -37
  146. package/dist/commands/task/close-noop.d.ts.map +1 -1
  147. package/dist/commands/task/close-noop.js +12 -30
  148. package/dist/commands/task/close-shared.d.ts +14 -0
  149. package/dist/commands/task/close-shared.d.ts.map +1 -0
  150. package/dist/commands/task/close-shared.js +76 -0
  151. package/dist/commands/task/comment.d.ts.map +1 -1
  152. package/dist/commands/task/comment.js +35 -17
  153. package/dist/commands/task/doc-set.command.d.ts +2 -1
  154. package/dist/commands/task/doc-set.command.d.ts.map +1 -1
  155. package/dist/commands/task/doc-set.command.js +36 -4
  156. package/dist/commands/task/doc-template.d.ts.map +1 -1
  157. package/dist/commands/task/doc-template.js +2 -7
  158. package/dist/commands/task/doc.command.js +1 -1
  159. package/dist/commands/task/doc.d.ts +2 -1
  160. package/dist/commands/task/doc.d.ts.map +1 -1
  161. package/dist/commands/task/doc.js +123 -71
  162. package/dist/commands/task/export.d.ts.map +1 -1
  163. package/dist/commands/task/export.js +4 -4
  164. package/dist/commands/task/finish.d.ts.map +1 -1
  165. package/dist/commands/task/finish.js +141 -78
  166. package/dist/commands/task/migrate-doc.d.ts.map +1 -1
  167. package/dist/commands/task/migrate-doc.js +15 -11
  168. package/dist/commands/task/plan-set.command.js +1 -1
  169. package/dist/commands/task/plan.command.d.ts +8 -0
  170. package/dist/commands/task/plan.command.d.ts.map +1 -0
  171. package/dist/commands/task/plan.command.js +37 -0
  172. package/dist/commands/task/plan.d.ts.map +1 -1
  173. package/dist/commands/task/plan.js +190 -93
  174. package/dist/commands/task/set-status.command.d.ts.map +1 -1
  175. package/dist/commands/task/set-status.command.js +1 -1
  176. package/dist/commands/task/set-status.d.ts.map +1 -1
  177. package/dist/commands/task/set-status.js +40 -3
  178. package/dist/commands/task/shared/docs.d.ts +1 -0
  179. package/dist/commands/task/shared/docs.d.ts.map +1 -1
  180. package/dist/commands/task/shared/docs.js +7 -0
  181. package/dist/commands/task/shared/transitions.d.ts +0 -2
  182. package/dist/commands/task/shared/transitions.d.ts.map +1 -1
  183. package/dist/commands/task/shared/transitions.js +0 -6
  184. package/dist/commands/task/shared.d.ts +2 -2
  185. package/dist/commands/task/shared.d.ts.map +1 -1
  186. package/dist/commands/task/shared.js +2 -2
  187. package/dist/commands/task/start.d.ts.map +1 -1
  188. package/dist/commands/task/start.js +88 -63
  189. package/dist/commands/task/task.command.d.ts +8 -0
  190. package/dist/commands/task/task.command.d.ts.map +1 -0
  191. package/dist/commands/task/task.command.js +71 -0
  192. package/dist/commands/task/verify-command-shared.d.ts +16 -0
  193. package/dist/commands/task/verify-command-shared.d.ts.map +1 -0
  194. package/dist/commands/task/verify-command-shared.js +53 -0
  195. package/dist/commands/task/verify-ok.command.d.ts +2 -6
  196. package/dist/commands/task/verify-ok.command.d.ts.map +1 -1
  197. package/dist/commands/task/verify-ok.command.js +8 -50
  198. package/dist/commands/task/verify-record.d.ts.map +1 -1
  199. package/dist/commands/task/verify-record.js +119 -140
  200. package/dist/commands/task/verify-rework.command.d.ts +2 -6
  201. package/dist/commands/task/verify-rework.command.d.ts.map +1 -1
  202. package/dist/commands/task/verify-rework.command.js +8 -50
  203. package/dist/commands/verify.spec.d.ts.map +1 -1
  204. package/dist/commands/verify.spec.js +3 -12
  205. package/dist/policy/rules/allowlist.d.ts.map +1 -1
  206. package/dist/policy/rules/allowlist.js +13 -4
  207. package/dist/policy/rules/protected-paths.d.ts.map +1 -1
  208. package/dist/policy/rules/protected-paths.js +6 -1
  209. package/dist/ports/task-backend-port.d.ts +1 -1
  210. package/dist/ports/task-backend-port.d.ts.map +1 -1
  211. package/dist/shared/agent-emoji.d.ts.map +1 -1
  212. package/dist/shared/protected-paths.d.ts +7 -0
  213. package/dist/shared/protected-paths.d.ts.map +1 -1
  214. package/dist/shared/protected-paths.js +26 -10
  215. package/dist/shared/repo-cli-version.d.ts.map +1 -1
  216. package/dist/shared/repo-cli-version.js +9 -3
  217. package/package.json +2 -2
@@ -2,20 +2,20 @@ import path from "node:path";
2
2
  import { mapBackendError } from "../../cli/error-map.js";
3
3
  import { backendNotSupportedMessage } from "../../cli/output.js";
4
4
  import { CliError } from "../../shared/errors.js";
5
- import { loadCommandContext } from "../shared/task-backend.js";
5
+ import { exportTaskProjectionSnapshot, loadCommandContext, } from "../shared/task-backend.js";
6
6
  export async function cmdTaskExport(opts) {
7
7
  try {
8
8
  const ctx = opts.ctx ??
9
9
  (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
10
10
  const outPath = path.join(ctx.resolvedProject.gitRoot, ctx.config.paths.tasks_path);
11
- if (!ctx.taskBackend.exportTasksJson) {
11
+ if (!ctx.taskBackend.exportProjectionSnapshot && !ctx.taskBackend.exportTasksJson) {
12
12
  throw new CliError({
13
13
  exitCode: 3,
14
14
  code: "E_VALIDATION",
15
- message: backendNotSupportedMessage("exportTasksJson()"),
15
+ message: backendNotSupportedMessage("exportProjectionSnapshot()"),
16
16
  });
17
17
  }
18
- await ctx.taskBackend.exportTasksJson(outPath);
18
+ await exportTaskProjectionSnapshot({ ctx, outputPath: outPath });
19
19
  process.stdout.write(`${path.relative(ctx.resolvedProject.gitRoot, outPath)}\n`);
20
20
  return 0;
21
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"finish.d.ts","sourceRoot":"","sources":["../../../src/commands/task/finish.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAqCnC,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,wBAAwB,EAAE,OAAO,CAAC;IAClC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2XlB"}
1
+ {"version":3,"file":"finish.d.ts","sourceRoot":"","sources":["../../../src/commands/task/finish.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAwFnC,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,wBAAwB,EAAE,OAAO,CAAC;IAClC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgZlB"}
@@ -28,6 +28,43 @@ async function clearDirectWorkLockIfMatches(opts) {
28
28
  // best-effort
29
29
  }
30
30
  }
31
+ function assertTaskCanFinish(opts) {
32
+ if (!opts.force && String(opts.task.status || "TODO").toUpperCase() === "DONE") {
33
+ throw new CliError({
34
+ exitCode: 2,
35
+ code: "E_USAGE",
36
+ message: `Task is already DONE: ${opts.task.id} (use --force to override)`,
37
+ });
38
+ }
39
+ ensureVerificationSatisfiedIfRequired(opts.task, opts.config);
40
+ const normalizedDoc = ensureDocSections(typeof opts.task.doc === "string" ? opts.task.doc : "", opts.config.tasks.doc.required_sections);
41
+ ensureAgentFilledRequiredDocSections({
42
+ task: opts.task,
43
+ config: opts.config,
44
+ doc: normalizedDoc,
45
+ action: "finish task",
46
+ });
47
+ if (!opts.isMetaTask)
48
+ return;
49
+ const tags = Array.isArray(opts.task.tags)
50
+ ? opts.task.tags.filter((t) => typeof t === "string")
51
+ : [];
52
+ const isSpike = tags.includes("spike");
53
+ if (!isSpike && opts.taskCount === 1 && !opts.resultSummary) {
54
+ throw new CliError({
55
+ exitCode: 2,
56
+ code: "E_USAGE",
57
+ message: "Missing required --result for non-spike tasks.",
58
+ });
59
+ }
60
+ if (opts.resultProvided && !opts.resultSummary) {
61
+ throw new CliError({
62
+ exitCode: 2,
63
+ code: "E_USAGE",
64
+ message: "Invalid value for --result: empty.",
65
+ });
66
+ }
67
+ }
31
68
  export async function cmdFinish(opts) {
32
69
  try {
33
70
  const ctx = opts.ctx ??
@@ -114,14 +151,16 @@ export async function cmdFinish(opts) {
114
151
  : await readHeadCommit(gitRoot);
115
152
  const useStore = backendIsLocalFileBackend(ctx);
116
153
  const store = useStore ? getTaskStore(ctx) : null;
154
+ const backendWritesTaskReadmes = ctx.taskBackend.capabilities.writes_task_readmes === true;
117
155
  const defaultDirectCloseCommit = ctx.config.workflow_mode === "direct" &&
118
- useStore &&
156
+ backendWritesTaskReadmes &&
119
157
  opts.taskIds.length === 1 &&
120
158
  !opts.commitFromComment &&
121
159
  !statusCommitRequested;
122
160
  const shouldCloseCommit = opts.closeCommit === true || (defaultDirectCloseCommit && opts.noCloseCommit !== true);
123
161
  const metaTaskId = opts.taskIds.length === 1 ? (opts.taskIds[0] ?? "") : "";
124
162
  const wantMeta = typeof opts.result === "string" || typeof opts.risk === "string" || opts.breaking === true;
163
+ const resultProvided = typeof opts.result === "string";
125
164
  if (wantMeta && opts.taskIds.length !== 1) {
126
165
  throw new CliError({
127
166
  exitCode: 2,
@@ -136,78 +175,87 @@ export async function cmdFinish(opts) {
136
175
  let primaryTag = null;
137
176
  for (const taskId of opts.taskIds) {
138
177
  const task = useStore ? await store.get(taskId) : await loadTaskFromContext({ ctx, taskId });
139
- if (!opts.force) {
140
- const currentStatus = String(task.status || "TODO").toUpperCase();
141
- if (currentStatus === "DONE") {
142
- throw new CliError({
143
- exitCode: 2,
144
- code: "E_USAGE",
145
- message: `Task is already DONE: ${task.id} (use --force to override)`,
146
- });
147
- }
148
- }
178
+ assertTaskCanFinish({
179
+ task,
180
+ config: ctx.config,
181
+ taskCount: opts.taskIds.length,
182
+ isMetaTask: taskId === metaTaskId,
183
+ resultProvided,
184
+ resultSummary,
185
+ force: opts.force,
186
+ });
149
187
  if (taskId === primaryTaskId &&
150
188
  (opts.commitFromComment || statusCommitRequested) &&
151
189
  primaryStatusFrom === null) {
152
190
  primaryStatusFrom = String(task.status || "TODO").toUpperCase();
153
191
  primaryTag = resolvePrimaryTag(toStringArray(task.tags), ctx).primary;
154
192
  }
155
- ensureVerificationSatisfiedIfRequired(task, ctx.config);
156
- const normalizedDoc = ensureDocSections(typeof task.doc === "string" ? task.doc : "", ctx.config.tasks.doc.required_sections);
157
- ensureAgentFilledRequiredDocSections({
158
- task,
159
- config: ctx.config,
160
- doc: normalizedDoc,
161
- action: "finish task",
162
- });
163
- if (taskId === metaTaskId) {
164
- const tags = Array.isArray(task.tags)
165
- ? task.tags.filter((t) => typeof t === "string")
166
- : [];
167
- const isSpike = tags.includes("spike");
168
- if (!isSpike && opts.taskIds.length === 1 && !resultSummary) {
169
- throw new CliError({
170
- exitCode: 2,
171
- code: "E_USAGE",
172
- message: "Missing required --result for non-spike tasks.",
173
- });
174
- }
175
- if (typeof opts.result === "string" && !resultSummary) {
176
- throw new CliError({
177
- exitCode: 2,
178
- code: "E_USAGE",
179
- message: "Invalid value for --result: empty.",
193
+ const at = nowIso();
194
+ if (useStore) {
195
+ await store.patch(taskId, (current) => {
196
+ assertTaskCanFinish({
197
+ task: current,
198
+ config: ctx.config,
199
+ taskCount: opts.taskIds.length,
200
+ isMetaTask: taskId === metaTaskId,
201
+ resultProvided,
202
+ resultSummary,
203
+ force: opts.force,
180
204
  });
181
- }
205
+ const currentStatus = String(current.status || "TODO").toUpperCase();
206
+ return {
207
+ task: {
208
+ status: "DONE",
209
+ commit: { hash: commitInfo.hash, message: commitInfo.message },
210
+ ...(taskId === metaTaskId && resultSummary ? { result_summary: resultSummary } : {}),
211
+ ...(taskId === metaTaskId && riskLevel ? { risk_level: riskLevel } : {}),
212
+ ...(taskId === metaTaskId && breaking ? { breaking: true } : {}),
213
+ },
214
+ appendComments: [{ author: opts.author, body: opts.body }],
215
+ appendEvents: [
216
+ {
217
+ type: "status",
218
+ at,
219
+ author: opts.author,
220
+ from: currentStatus,
221
+ to: "DONE",
222
+ note: opts.body,
223
+ },
224
+ ],
225
+ docMeta: {
226
+ touch: true,
227
+ updatedBy: opts.author,
228
+ version: normalizeTaskDocVersion(current.doc_version),
229
+ },
230
+ };
231
+ });
232
+ }
233
+ else {
234
+ const existingComments = Array.isArray(task.comments)
235
+ ? task.comments.filter((item) => !!item && typeof item.author === "string" && typeof item.body === "string")
236
+ : [];
237
+ const nextTask = {
238
+ ...task,
239
+ status: "DONE",
240
+ commit: { hash: commitInfo.hash, message: commitInfo.message },
241
+ comments: [...existingComments, { author: opts.author, body: opts.body }],
242
+ events: appendTaskEvent(task, {
243
+ type: "status",
244
+ at,
245
+ author: opts.author,
246
+ from: String(task.status || "TODO").toUpperCase(),
247
+ to: "DONE",
248
+ note: opts.body,
249
+ }),
250
+ result_summary: taskId === metaTaskId && resultSummary ? resultSummary : task.result_summary,
251
+ risk_level: taskId === metaTaskId && riskLevel ? riskLevel : task.risk_level,
252
+ breaking: taskId === metaTaskId && breaking ? true : task.breaking,
253
+ doc_version: normalizeTaskDocVersion(task.doc_version),
254
+ doc_updated_at: at,
255
+ doc_updated_by: opts.author,
256
+ };
257
+ await ctx.taskBackend.writeTask(nextTask);
182
258
  }
183
- const existingComments = Array.isArray(task.comments)
184
- ? task.comments.filter((item) => !!item && typeof item.author === "string" && typeof item.body === "string")
185
- : [];
186
- const commentsValue = [...existingComments, { author: opts.author, body: opts.body }];
187
- const at = nowIso();
188
- const nextTask = {
189
- ...task,
190
- status: "DONE",
191
- commit: { hash: commitInfo.hash, message: commitInfo.message },
192
- comments: commentsValue,
193
- events: appendTaskEvent(task, {
194
- type: "status",
195
- at,
196
- author: opts.author,
197
- from: String(task.status || "TODO").toUpperCase(),
198
- to: "DONE",
199
- note: opts.body,
200
- }),
201
- result_summary: taskId === metaTaskId && resultSummary ? resultSummary : task.result_summary,
202
- risk_level: taskId === metaTaskId && riskLevel ? riskLevel : task.risk_level,
203
- breaking: taskId === metaTaskId && breaking ? true : task.breaking,
204
- doc_version: normalizeTaskDocVersion(task.doc_version),
205
- doc_updated_at: at,
206
- doc_updated_by: opts.author,
207
- };
208
- await (useStore
209
- ? store.update(taskId, () => nextTask)
210
- : ctx.taskBackend.writeTask(nextTask));
211
259
  }
212
260
  if (opts.commitFromComment || statusCommitRequested) {
213
261
  enforceStatusCommitPolicy({
@@ -232,6 +280,9 @@ export async function cmdFinish(opts) {
232
280
  }
233
281
  }
234
282
  if (opts.commitFromComment) {
283
+ if (!opts.quiet) {
284
+ process.stdout.write(`${infoMessage("task marked DONE; creating commit from verification comment")}\n`);
285
+ }
235
286
  if (typeof opts.commitEmoji === "string" && opts.commitEmoji.trim() !== "✅") {
236
287
  throw new CliError({
237
288
  exitCode: 2,
@@ -262,20 +313,29 @@ export async function cmdFinish(opts) {
262
313
  // commitFromComment creates the git commit and returns the actual head hash/subject.
263
314
  // Refresh task commit metadata to this hash and amend the same commit in local mode so
264
315
  // "task done" metadata does not require a manual follow-up close commit.
265
- const taskAfterCommit = useStore
266
- ? await store.get(primaryTaskId)
267
- : await loadTaskFromContext({ ctx, taskId: primaryTaskId });
268
- const updatedAfterCommit = {
269
- ...taskAfterCommit,
270
- commit: { hash: committed.hash, message: committed.message },
271
- doc_version: normalizeTaskDocVersion(taskAfterCommit.doc_version),
272
- doc_updated_at: nowIso(),
273
- doc_updated_by: opts.author,
274
- };
275
316
  await (useStore
276
- ? store.update(primaryTaskId, () => updatedAfterCommit)
277
- : ctx.taskBackend.writeTask(updatedAfterCommit));
278
- if (useStore) {
317
+ ? store.patch(primaryTaskId, (current) => ({
318
+ task: {
319
+ commit: { hash: committed.hash, message: committed.message },
320
+ },
321
+ docMeta: {
322
+ touch: true,
323
+ updatedBy: opts.author,
324
+ version: normalizeTaskDocVersion(current.doc_version),
325
+ },
326
+ }))
327
+ : (async () => {
328
+ const taskAfterCommit = await loadTaskFromContext({ ctx, taskId: primaryTaskId });
329
+ const updatedAfterCommit = {
330
+ ...taskAfterCommit,
331
+ commit: { hash: committed.hash, message: committed.message },
332
+ doc_version: normalizeTaskDocVersion(taskAfterCommit.doc_version),
333
+ doc_updated_at: nowIso(),
334
+ doc_updated_by: opts.author,
335
+ };
336
+ await ctx.taskBackend.writeTask(updatedAfterCommit);
337
+ })());
338
+ if (backendWritesTaskReadmes) {
279
339
  const workflowReadmeRelPath = path.join(ctx.config.paths.workflow_dir, primaryTaskId, "README.md");
280
340
  await ctx.git.stage([workflowReadmeRelPath]);
281
341
  const env = buildGitCommitEnv({
@@ -293,6 +353,9 @@ export async function cmdFinish(opts) {
293
353
  }
294
354
  }
295
355
  if (statusCommitRequested) {
356
+ if (!opts.quiet) {
357
+ process.stdout.write(`${infoMessage("task marked DONE; creating status commit")}\n`);
358
+ }
296
359
  if (typeof opts.statusCommitEmoji === "string" && opts.statusCommitEmoji.trim() !== "✅") {
297
360
  throw new CliError({
298
361
  exitCode: 2,
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-doc.d.ts","sourceRoot":"","sources":["../../../src/commands/task/migrate-doc.ts"],"names":[],"mappings":"AAkSA,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwClB"}
1
+ {"version":3,"file":"migrate-doc.d.ts","sourceRoot":"","sources":["../../../src/commands/task/migrate-doc.ts"],"names":[],"mappings":"AA2RA,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDlB"}
@@ -6,11 +6,11 @@ import { exitCodeForError } from "../../cli/exit-codes.js";
6
6
  import { fileExists, getPathKind } from "../../cli/fs-utils.js";
7
7
  import { successMessage } from "../../cli/output.js";
8
8
  import { CliError } from "../../shared/errors.js";
9
- import { extractDocSection, extractTaskObservationSection, normalizeTaskDocVersion, normalizeVerificationSectionLayout, } from "./shared/docs.js";
9
+ import { exportTaskProjectionSnapshot, loadCommandContext } from "../shared/task-backend.js";
10
+ import { extractDocSection, extractTaskObservationSection, decodeEscapedTaskTextNewlines, normalizeTaskDocVersion, normalizeVerificationSectionLayout, } from "./shared/docs.js";
10
11
  import { defaultTaskDocV3 } from "./doc-template.js";
11
12
  const V3_CANONICAL_ORDER = [
12
13
  "Summary",
13
- "Context",
14
14
  "Scope",
15
15
  "Plan",
16
16
  "Verify Steps",
@@ -61,13 +61,7 @@ function renderMarkdownSections(sections) {
61
61
  function normalizeLiteralNewlinesInHumanSection(title, text) {
62
62
  if (!HUMAN_TEXT_SECTIONS.has(normalizeSectionKey(title)))
63
63
  return text.trimEnd();
64
- let next = text.replaceAll("\r\n", "\n");
65
- const escapedDoubleNewline = next.includes(String.raw `\n\n`) || next.includes(String.raw `\r\n\r\n`);
66
- const escapedNewlineMatches = next.match(/\\n/g) ?? [];
67
- if (escapedDoubleNewline || escapedNewlineMatches.length >= 2) {
68
- next = next.replaceAll(String.raw `\r\n`, "\n").replaceAll(String.raw `\n`, "\n");
69
- }
70
- return next.trimEnd();
64
+ return decodeEscapedTaskTextNewlines(text).trimEnd();
71
65
  }
72
66
  function firstSectionText(sections, title) {
73
67
  const target = normalizeSectionKey(title);
@@ -98,8 +92,6 @@ function migrateDocToV3(opts) {
98
92
  const currentText = firstSectionText(currentSections, title);
99
93
  const defaultText = firstSectionText(defaultSections, title) ?? "";
100
94
  let nextText = currentText ?? defaultText;
101
- if (title === "Context" && !(currentText ?? "").trim())
102
- continue;
103
95
  if (title === "Verification") {
104
96
  nextText = normalizeVerificationSectionLayout(currentText ?? defaultText, 3);
105
97
  }
@@ -242,6 +234,12 @@ export async function cmdTaskMigrateDoc(opts) {
242
234
  rootOverride: opts.rootOverride ?? null,
243
235
  });
244
236
  const loaded = await loadConfig(resolved.agentplaneDir);
237
+ const ctx = await loadCommandContext({
238
+ cwd: opts.cwd,
239
+ rootOverride: opts.rootOverride ?? null,
240
+ resolvedProject: resolved,
241
+ config: loaded.config,
242
+ });
245
243
  const tasksDir = path.join(resolved.gitRoot, loaded.config.paths.workflow_dir);
246
244
  const readmePaths = await resolveReadmePaths({ tasksDir, params });
247
245
  if (!params.all) {
@@ -262,6 +260,12 @@ export async function cmdTaskMigrateDoc(opts) {
262
260
  if (res.changed)
263
261
  changed += 1;
264
262
  }
263
+ // Refresh the local export snapshot so doctor and other snapshot-based checks
264
+ // immediately observe the migrated README contract without a separate task export.
265
+ if (ctx.taskBackend.exportProjectionSnapshot || ctx.taskBackend.exportTasksJson) {
266
+ const outPath = path.join(resolved.gitRoot, loaded.config.paths.tasks_path);
267
+ await exportTaskProjectionSnapshot({ ctx, outputPath: outPath });
268
+ }
265
269
  if (!params.quiet) {
266
270
  process.stdout.write(`${successMessage("migrated task docs", undefined, `changed=${changed}`)}\n`);
267
271
  }
@@ -10,7 +10,7 @@ export const taskPlanSetSpec = {
10
10
  kind: "string",
11
11
  name: "text",
12
12
  valueHint: "<text>",
13
- description: "Plan text to write into the task README Plan section.",
13
+ description: String.raw `Plan text to write into the task README Plan section. Literal escaped newlines (\n) are normalized for inline text.`,
14
14
  },
15
15
  {
16
16
  kind: "string",
@@ -0,0 +1,8 @@
1
+ import type { CommandHandler, CommandSpec } from "../../cli/spec/spec.js";
2
+ type TaskPlanGroupParsed = {
3
+ cmd: string[];
4
+ };
5
+ export declare const taskPlanSpec: CommandSpec<TaskPlanGroupParsed>;
6
+ export declare const runTaskPlan: CommandHandler<TaskPlanGroupParsed>;
7
+ export {};
8
+ //# sourceMappingURL=plan.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/plan.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAItF,KAAK,mBAAmB,GAAG;IAAE,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAI7C,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,mBAAmB,CAqBzD,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,cAAc,CAAC,mBAAmB,CAW3D,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { usageError } from "../../cli/spec/errors.js";
2
+ import { suggestOne } from "../../cli/spec/suggest.js";
3
+ const TASK_PLAN_SUBCOMMANDS = ["set", "approve", "reject"];
4
+ export const taskPlanSpec = {
5
+ id: ["task", "plan"],
6
+ group: "Task",
7
+ summary: "Task plan commands (set/approve/reject).",
8
+ synopsis: ["agentplane task plan <set|approve|reject> [args] [options]"],
9
+ args: [{ name: "cmd", required: false, variadic: true, valueHint: "<subcommand>" }],
10
+ examples: [
11
+ {
12
+ cmd: 'agentplane task plan set <task-id> --text "..." --updated-by ORCHESTRATOR',
13
+ why: "Write or replace the task plan.",
14
+ },
15
+ {
16
+ cmd: "agentplane task plan approve <task-id> --by ORCHESTRATOR",
17
+ why: "Approve the current task plan.",
18
+ },
19
+ {
20
+ cmd: 'agentplane task plan reject <task-id> --by ORCHESTRATOR --note "..."',
21
+ why: "Reject the current task plan with a note.",
22
+ },
23
+ ],
24
+ parse: (raw) => ({ cmd: (raw.args.cmd ?? []) }),
25
+ };
26
+ export const runTaskPlan = (_ctx, p) => {
27
+ const input = p.cmd.join(" ");
28
+ const suggestion = suggestOne(input, [...TASK_PLAN_SUBCOMMANDS]);
29
+ const suffix = suggestion ? ` Did you mean: ${suggestion}?` : "";
30
+ const message = p.cmd.length === 0 ? "Missing subcommand." : `Unknown subcommand: ${p.cmd[0]}.`;
31
+ throw usageError({
32
+ spec: taskPlanSpec,
33
+ command: "task plan",
34
+ message: `${message}${suffix}`,
35
+ context: { command: "task plan" },
36
+ });
37
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/commands/task/plan.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAuDnC,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ElB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CA4FlB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDlB"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/commands/task/plan.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAkJnC,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8HlB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CA2DlB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAkElB"}