agentplane 0.3.5 → 0.3.7

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 (256) 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 +251 -821
  8. package/dist/adapters/task-backend/task-backend-adapter.d.ts +2 -2
  9. package/dist/adapters/task-backend/task-backend-adapter.d.ts.map +1 -1
  10. package/dist/adapters/task-backend/task-backend-adapter.js +2 -2
  11. package/dist/backends/task-backend/local-backend.d.ts +7 -5
  12. package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
  13. package/dist/backends/task-backend/local-backend.js +79 -7
  14. package/dist/backends/task-backend/redmine/env.d.ts +1 -1
  15. package/dist/backends/task-backend/redmine/env.d.ts.map +1 -1
  16. package/dist/backends/task-backend/redmine/env.js +3 -0
  17. package/dist/backends/task-backend/redmine/inspect.d.ts +11 -0
  18. package/dist/backends/task-backend/redmine/inspect.d.ts.map +1 -0
  19. package/dist/backends/task-backend/redmine/inspect.js +75 -0
  20. package/dist/backends/task-backend/redmine/mapping.d.ts.map +1 -1
  21. package/dist/backends/task-backend/redmine/mapping.js +21 -2
  22. package/dist/backends/task-backend/redmine/state.d.ts +17 -0
  23. package/dist/backends/task-backend/redmine/state.d.ts.map +1 -0
  24. package/dist/backends/task-backend/redmine/state.js +95 -0
  25. package/dist/backends/task-backend/redmine-backend.d.ts +10 -16
  26. package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
  27. package/dist/backends/task-backend/redmine-backend.js +205 -15
  28. package/dist/backends/task-backend/shared/constants.d.ts +1 -1
  29. package/dist/backends/task-backend/shared/constants.js +1 -1
  30. package/dist/backends/task-backend/shared/record.d.ts.map +1 -1
  31. package/dist/backends/task-backend/shared/record.js +20 -1
  32. package/dist/backends/task-backend/shared/types.d.ts +42 -4
  33. package/dist/backends/task-backend/shared/types.d.ts.map +1 -1
  34. package/dist/backends/task-backend/shared.d.ts +1 -1
  35. package/dist/backends/task-backend/shared.d.ts.map +1 -1
  36. package/dist/backends/task-backend.d.ts +1 -1
  37. package/dist/backends/task-backend.d.ts.map +1 -1
  38. package/dist/backends/task-backend.test-helpers.d.ts +4 -0
  39. package/dist/backends/task-backend.test-helpers.d.ts.map +1 -0
  40. package/dist/backends/task-backend.test-helpers.js +33 -0
  41. package/dist/backends/task-index.d.ts.map +1 -1
  42. package/dist/backends/task-index.js +1 -0
  43. package/dist/cli/bootstrap-guide.d.ts.map +1 -1
  44. package/dist/cli/bootstrap-guide.js +1 -0
  45. package/dist/cli/command-guide.d.ts.map +1 -1
  46. package/dist/cli/command-guide.js +3 -2
  47. package/dist/cli/reason-codes.d.ts.map +1 -1
  48. package/dist/cli/reason-codes.js +30 -0
  49. package/dist/cli/run-cli/command-catalog/core.d.ts +3 -0
  50. package/dist/cli/run-cli/command-catalog/core.d.ts.map +1 -0
  51. package/dist/cli/run-cli/command-catalog/core.js +137 -0
  52. package/dist/cli/run-cli/command-catalog/lifecycle.d.ts +3 -0
  53. package/dist/cli/run-cli/command-catalog/lifecycle.d.ts.map +1 -0
  54. package/dist/cli/run-cli/command-catalog/lifecycle.js +52 -0
  55. package/dist/cli/run-cli/command-catalog/project.d.ts +3 -0
  56. package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -0
  57. package/dist/cli/run-cli/command-catalog/project.js +80 -0
  58. package/dist/cli/run-cli/command-catalog/shared.d.ts +19 -0
  59. package/dist/cli/run-cli/command-catalog/shared.d.ts.map +1 -0
  60. package/dist/cli/run-cli/command-catalog/shared.js +9 -0
  61. package/dist/cli/run-cli/command-catalog/task.d.ts +3 -0
  62. package/dist/cli/run-cli/command-catalog/task.d.ts.map +1 -0
  63. package/dist/cli/run-cli/command-catalog/task.js +85 -0
  64. package/dist/cli/run-cli/command-catalog.d.ts +3 -18
  65. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  66. package/dist/cli/run-cli/command-catalog.js +8 -337
  67. package/dist/cli/run-cli/commands/ide.d.ts.map +1 -1
  68. package/dist/cli/run-cli/commands/ide.js +64 -2
  69. package/dist/cli/run-cli/commands/init/ui.d.ts.map +1 -1
  70. package/dist/cli/run-cli/commands/init/ui.js +33 -13
  71. package/dist/cli/run-cli/commands/init/write-env.d.ts.map +1 -1
  72. package/dist/cli/run-cli/commands/init/write-env.js +12 -0
  73. package/dist/cli/run-cli.core.pr-flow.test-helpers.d.ts +3 -0
  74. package/dist/cli/run-cli.core.pr-flow.test-helpers.d.ts.map +1 -0
  75. package/dist/cli/run-cli.core.pr-flow.test-helpers.js +41 -0
  76. package/dist/cli/run-cli.core.tasks.test-helpers.d.ts +2 -0
  77. package/dist/cli/run-cli.core.tasks.test-helpers.d.ts.map +1 -0
  78. package/dist/cli/run-cli.core.tasks.test-helpers.js +6 -0
  79. package/dist/cli/run-cli.test-helpers.d.ts +3 -0
  80. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  81. package/dist/cli/run-cli.test-helpers.js +140 -6
  82. package/dist/commands/backend/sync.command.d.ts +5 -1
  83. package/dist/commands/backend/sync.command.d.ts.map +1 -1
  84. package/dist/commands/backend/sync.command.js +67 -3
  85. package/dist/commands/backend.d.ts +22 -0
  86. package/dist/commands/backend.d.ts.map +1 -1
  87. package/dist/commands/backend.js +110 -1
  88. package/dist/commands/commit.spec.d.ts.map +1 -1
  89. package/dist/commands/commit.spec.js +31 -7
  90. package/dist/commands/doctor/runtime.d.ts.map +1 -1
  91. package/dist/commands/doctor/runtime.js +3 -6
  92. package/dist/commands/doctor/workspace.d.ts +8 -0
  93. package/dist/commands/doctor/workspace.d.ts.map +1 -1
  94. package/dist/commands/doctor/workspace.js +127 -3
  95. package/dist/commands/guard/commit.command.d.ts.map +1 -1
  96. package/dist/commands/guard/commit.command.js +30 -6
  97. package/dist/commands/guard/impl/allow.d.ts +9 -0
  98. package/dist/commands/guard/impl/allow.d.ts.map +1 -1
  99. package/dist/commands/guard/impl/allow.js +26 -10
  100. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  101. package/dist/commands/guard/impl/commands.js +146 -18
  102. package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
  103. package/dist/commands/guard/impl/comment-commit.js +2 -0
  104. package/dist/commands/hooks/index.d.ts.map +1 -1
  105. package/dist/commands/hooks/index.js +8 -35
  106. package/dist/commands/recipes/impl/apply.d.ts +4 -0
  107. package/dist/commands/recipes/impl/apply.d.ts.map +1 -1
  108. package/dist/commands/recipes/impl/apply.js +34 -0
  109. package/dist/commands/recipes/impl/commands/explain.d.ts.map +1 -1
  110. package/dist/commands/recipes/impl/commands/explain.js +70 -11
  111. package/dist/commands/recipes/impl/commands/info.d.ts.map +1 -1
  112. package/dist/commands/recipes/impl/commands/info.js +24 -12
  113. package/dist/commands/recipes/impl/commands/install.d.ts.map +1 -1
  114. package/dist/commands/recipes/impl/commands/install.js +32 -36
  115. package/dist/commands/recipes/impl/commands/list.d.ts.map +1 -1
  116. package/dist/commands/recipes/impl/commands/list.js +7 -4
  117. package/dist/commands/recipes/impl/commands/remove.d.ts.map +1 -1
  118. package/dist/commands/recipes/impl/commands/remove.js +9 -11
  119. package/dist/commands/recipes/impl/constants.d.ts +2 -0
  120. package/dist/commands/recipes/impl/constants.d.ts.map +1 -1
  121. package/dist/commands/recipes/impl/constants.js +2 -0
  122. package/dist/commands/recipes/impl/manifest.d.ts.map +1 -1
  123. package/dist/commands/recipes/impl/manifest.js +219 -23
  124. package/dist/commands/recipes/impl/normalize.d.ts +3 -0
  125. package/dist/commands/recipes/impl/normalize.d.ts.map +1 -1
  126. package/dist/commands/recipes/impl/normalize.js +28 -24
  127. package/dist/commands/recipes/impl/paths.d.ts +9 -0
  128. package/dist/commands/recipes/impl/paths.d.ts.map +1 -1
  129. package/dist/commands/recipes/impl/paths.js +10 -1
  130. package/dist/commands/recipes/impl/project-installed-recipes.d.ts +7 -0
  131. package/dist/commands/recipes/impl/project-installed-recipes.d.ts.map +1 -0
  132. package/dist/commands/recipes/impl/project-installed-recipes.js +102 -0
  133. package/dist/commands/recipes/impl/resolver.d.ts +20 -0
  134. package/dist/commands/recipes/impl/resolver.d.ts.map +1 -0
  135. package/dist/commands/recipes/impl/resolver.js +220 -0
  136. package/dist/commands/recipes/impl/scenario.d.ts.map +1 -1
  137. package/dist/commands/recipes/impl/scenario.js +40 -11
  138. package/dist/commands/recipes/impl/types.d.ts +145 -16
  139. package/dist/commands/recipes/impl/types.d.ts.map +1 -1
  140. package/dist/commands/recipes/install.spec.d.ts.map +1 -1
  141. package/dist/commands/recipes/install.spec.js +3 -2
  142. package/dist/commands/recipes.d.ts +6 -4
  143. package/dist/commands/recipes.d.ts.map +1 -1
  144. package/dist/commands/recipes.js +5 -3
  145. package/dist/commands/recipes.test-helpers.d.ts +185 -0
  146. package/dist/commands/recipes.test-helpers.d.ts.map +1 -0
  147. package/dist/commands/recipes.test-helpers.js +339 -0
  148. package/dist/commands/scenario/impl/commands.d.ts.map +1 -1
  149. package/dist/commands/scenario/impl/commands.js +192 -336
  150. package/dist/commands/scenario/info.command.d.ts.map +1 -1
  151. package/dist/commands/scenario/info.command.js +7 -2
  152. package/dist/commands/scenario/list.command.js +2 -2
  153. package/dist/commands/scenario/run.command.d.ts.map +1 -1
  154. package/dist/commands/scenario/run.command.js +7 -2
  155. package/dist/commands/shared/reconcile-check.d.ts.map +1 -1
  156. package/dist/commands/shared/reconcile-check.js +77 -2
  157. package/dist/commands/shared/task-backend.d.ts +1 -1
  158. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  159. package/dist/commands/shared/task-backend.js +9 -0
  160. package/dist/commands/shared/task-store.d.ts +92 -2
  161. package/dist/commands/shared/task-store.d.ts.map +1 -1
  162. package/dist/commands/shared/task-store.js +405 -43
  163. package/dist/commands/task/block.d.ts.map +1 -1
  164. package/dist/commands/task/block.js +84 -46
  165. package/dist/commands/task/close-duplicate.d.ts.map +1 -1
  166. package/dist/commands/task/close-duplicate.js +12 -37
  167. package/dist/commands/task/close-noop.d.ts.map +1 -1
  168. package/dist/commands/task/close-noop.js +12 -30
  169. package/dist/commands/task/close-shared.d.ts +14 -0
  170. package/dist/commands/task/close-shared.d.ts.map +1 -0
  171. package/dist/commands/task/close-shared.js +73 -0
  172. package/dist/commands/task/comment.d.ts.map +1 -1
  173. package/dist/commands/task/comment.js +34 -21
  174. package/dist/commands/task/derive.command.d.ts +1 -0
  175. package/dist/commands/task/derive.command.d.ts.map +1 -1
  176. package/dist/commands/task/derive.command.js +15 -2
  177. package/dist/commands/task/derive.d.ts +1 -0
  178. package/dist/commands/task/derive.d.ts.map +1 -1
  179. package/dist/commands/task/derive.js +27 -4
  180. package/dist/commands/task/doc-set.command.d.ts +2 -1
  181. package/dist/commands/task/doc-set.command.d.ts.map +1 -1
  182. package/dist/commands/task/doc-set.command.js +36 -4
  183. package/dist/commands/task/doc-template.d.ts.map +1 -1
  184. package/dist/commands/task/doc-template.js +2 -7
  185. package/dist/commands/task/doc.command.js +1 -1
  186. package/dist/commands/task/doc.d.ts +2 -1
  187. package/dist/commands/task/doc.d.ts.map +1 -1
  188. package/dist/commands/task/doc.js +139 -76
  189. package/dist/commands/task/finish.d.ts.map +1 -1
  190. package/dist/commands/task/finish.js +142 -80
  191. package/dist/commands/task/migrate-doc.d.ts +15 -0
  192. package/dist/commands/task/migrate-doc.d.ts.map +1 -1
  193. package/dist/commands/task/migrate-doc.js +128 -43
  194. package/dist/commands/task/new.d.ts.map +1 -1
  195. package/dist/commands/task/new.js +3 -1
  196. package/dist/commands/task/plan-set.command.js +1 -1
  197. package/dist/commands/task/plan.command.d.ts +8 -0
  198. package/dist/commands/task/plan.command.d.ts.map +1 -0
  199. package/dist/commands/task/plan.command.js +37 -0
  200. package/dist/commands/task/plan.d.ts.map +1 -1
  201. package/dist/commands/task/plan.js +198 -101
  202. package/dist/commands/task/set-status.command.d.ts.map +1 -1
  203. package/dist/commands/task/set-status.command.js +1 -1
  204. package/dist/commands/task/set-status.d.ts.map +1 -1
  205. package/dist/commands/task/set-status.js +115 -35
  206. package/dist/commands/task/shared/dependencies.d.ts +1 -0
  207. package/dist/commands/task/shared/dependencies.d.ts.map +1 -1
  208. package/dist/commands/task/shared/dependencies.js +10 -0
  209. package/dist/commands/task/shared/docs.d.ts +1 -0
  210. package/dist/commands/task/shared/docs.d.ts.map +1 -1
  211. package/dist/commands/task/shared/docs.js +8 -1
  212. package/dist/commands/task/shared/transitions.d.ts +17 -2
  213. package/dist/commands/task/shared/transitions.d.ts.map +1 -1
  214. package/dist/commands/task/shared/transitions.js +20 -13
  215. package/dist/commands/task/shared.d.ts +3 -3
  216. package/dist/commands/task/shared.d.ts.map +1 -1
  217. package/dist/commands/task/shared.js +3 -3
  218. package/dist/commands/task/start.d.ts.map +1 -1
  219. package/dist/commands/task/start.js +101 -71
  220. package/dist/commands/task/task.command.d.ts +8 -0
  221. package/dist/commands/task/task.command.d.ts.map +1 -0
  222. package/dist/commands/task/task.command.js +71 -0
  223. package/dist/commands/task/verify-command-shared.d.ts +16 -0
  224. package/dist/commands/task/verify-command-shared.d.ts.map +1 -0
  225. package/dist/commands/task/verify-command-shared.js +53 -0
  226. package/dist/commands/task/verify-ok.command.d.ts +2 -6
  227. package/dist/commands/task/verify-ok.command.d.ts.map +1 -1
  228. package/dist/commands/task/verify-ok.command.js +8 -50
  229. package/dist/commands/task/verify-record.d.ts.map +1 -1
  230. package/dist/commands/task/verify-record.js +124 -145
  231. package/dist/commands/task/verify-rework.command.d.ts +2 -6
  232. package/dist/commands/task/verify-rework.command.d.ts.map +1 -1
  233. package/dist/commands/task/verify-rework.command.js +8 -50
  234. package/dist/commands/upgrade/apply.d.ts +2 -0
  235. package/dist/commands/upgrade/apply.d.ts.map +1 -1
  236. package/dist/commands/upgrade/apply.js +33 -1
  237. package/dist/commands/upgrade.command.d.ts.map +1 -1
  238. package/dist/commands/upgrade.command.js +25 -0
  239. package/dist/commands/upgrade.d.ts +1 -0
  240. package/dist/commands/upgrade.d.ts.map +1 -1
  241. package/dist/commands/upgrade.js +34 -0
  242. package/dist/commands/verify.spec.d.ts.map +1 -1
  243. package/dist/commands/verify.spec.js +3 -12
  244. package/dist/policy/rules/allowlist.d.ts.map +1 -1
  245. package/dist/policy/rules/allowlist.js +16 -4
  246. package/dist/policy/rules/protected-paths.d.ts.map +1 -1
  247. package/dist/policy/rules/protected-paths.js +6 -1
  248. package/dist/ports/task-backend-port.d.ts +2 -2
  249. package/dist/ports/task-backend-port.d.ts.map +1 -1
  250. package/dist/shared/agent-emoji.d.ts.map +1 -1
  251. package/dist/shared/protected-paths.d.ts +17 -0
  252. package/dist/shared/protected-paths.d.ts.map +1 -1
  253. package/dist/shared/protected-paths.js +59 -10
  254. package/dist/shared/repo-cli-version.d.ts.map +1 -1
  255. package/dist/shared/repo-cli-version.js +9 -3
  256. package/package.json +2 -2
@@ -6,7 +6,7 @@ import { backendNotSupportedMessage } from "../../cli/output.js";
6
6
  import { CliError } from "../../shared/errors.js";
7
7
  import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
8
8
  import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
9
- import { ensureAgentFilledRequiredDocSections, extractDocSection, extractTaskObservationSection, isVerifyStepsFilled, nowIso, normalizeTaskDocVersion, taskObservationSectionName, requiresVerifyStepsByPrimary, toStringArray, } from "./shared.js";
9
+ import { decodeEscapedTaskTextNewlines, ensureAgentFilledRequiredDocSections, extractDocSection, extractTaskObservationSection, isVerifyStepsFilled, nowIso, normalizeTaskDocVersion, taskObservationSectionName, requiresVerifyStepsByPrimary, toStringArray, } from "./shared.js";
10
10
  async function loadPlanTask(opts) {
11
11
  const ctx = opts.ctx ??
12
12
  (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
@@ -28,6 +28,68 @@ async function loadPlanTask(opts) {
28
28
  function normalizeForComparison(text) {
29
29
  return text.replaceAll("\r\n", "\n").trim();
30
30
  }
31
+ function buildPlanDocUpdate(opts) {
32
+ const baseDoc = ensureDocSections(opts.currentDocRaw ?? "", opts.requiredSections);
33
+ const currentPlan = extractDocSection(baseDoc, "Plan") ?? "";
34
+ const planChanged = normalizeForComparison(currentPlan) !== normalizeForComparison(opts.text);
35
+ const nextDoc = ensureDocSections(setMarkdownSection(baseDoc, "Plan", opts.text), opts.requiredSections);
36
+ return {
37
+ currentPlan,
38
+ nextPlan: extractDocSection(nextDoc, "Plan") ?? "",
39
+ planChanged,
40
+ docChanged: nextDoc !== baseDoc,
41
+ nextDoc,
42
+ };
43
+ }
44
+ function assertPlanSectionPresent(taskId, doc, action) {
45
+ const plan = extractDocSection(doc, "Plan");
46
+ if (!plan || plan.trim().length === 0) {
47
+ throw new CliError({
48
+ exitCode: 3,
49
+ code: "E_VALIDATION",
50
+ message: `${taskId}: cannot ${action} plan: ## Plan section is missing or empty`,
51
+ });
52
+ }
53
+ }
54
+ function assertPlanCanBeApproved(opts) {
55
+ assertPlanSectionPresent(opts.task.id, opts.doc, "approve");
56
+ ensureAgentFilledRequiredDocSections({
57
+ task: opts.task,
58
+ config: opts.config,
59
+ doc: opts.doc,
60
+ action: "approve plan",
61
+ });
62
+ const enforceVerifySteps = opts.config.tasks.verify.enforce_on_plan_approve !== false;
63
+ if (!enforceVerifySteps)
64
+ return;
65
+ const tags = toStringArray(opts.task.tags);
66
+ const spikeTag = (opts.config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
67
+ const verifyRequired = requiresVerifyStepsByPrimary(tags, opts.config);
68
+ const isSpike = tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
69
+ if (verifyRequired || isSpike) {
70
+ const verifySteps = extractDocSection(opts.doc, "Verify Steps");
71
+ if (!isVerifyStepsFilled(verifySteps)) {
72
+ throw new CliError({
73
+ exitCode: 3,
74
+ code: "E_VALIDATION",
75
+ message: `${opts.task.id}: cannot approve plan: ## Verify Steps section is missing/empty/unfilled ` +
76
+ "(fill it before approving plan)",
77
+ });
78
+ }
79
+ }
80
+ if (!isSpike)
81
+ return;
82
+ const observationSection = taskObservationSectionName(normalizeTaskDocVersion(opts.task.doc_version));
83
+ const observation = extractTaskObservationSection(opts.doc, normalizeTaskDocVersion(opts.task.doc_version));
84
+ if (!observation || observation.trim().length === 0) {
85
+ throw new CliError({
86
+ exitCode: 3,
87
+ code: "E_VALIDATION",
88
+ message: `${opts.task.id}: cannot approve plan for spike: ## ${observationSection} section is missing or empty ` +
89
+ "(include Findings/Decision/Next Steps)",
90
+ });
91
+ }
92
+ }
31
93
  export async function cmdTaskPlanSet(opts) {
32
94
  try {
33
95
  const { ctx, backend, task, useStore, store } = await loadPlanTask({
@@ -60,6 +122,9 @@ export async function cmdTaskPlanSet(opts) {
60
122
  updatedBy = trimmed;
61
123
  }
62
124
  let text = opts.text ?? "";
125
+ if (hasText) {
126
+ text = decodeEscapedTaskTextNewlines(text);
127
+ }
63
128
  if (hasFile) {
64
129
  try {
65
130
  text = await readFile(path.resolve(opts.cwd, opts.file ?? ""), "utf8");
@@ -68,28 +133,80 @@ export async function cmdTaskPlanSet(opts) {
68
133
  throw mapCoreError(err, { command: "task plan set", filePath: opts.file ?? "" });
69
134
  }
70
135
  }
71
- const existingDoc = useStore
72
- ? String(task.doc ?? "")
73
- : (typeof task.doc === "string" ? task.doc : "") || (await backend.getTaskDoc(task.id));
74
- const baseDoc = ensureDocSections(existingDoc ?? "", config.tasks.doc.required_sections);
75
- const currentPlan = extractDocSection(baseDoc, "Plan") ?? "";
76
- const planChanged = normalizeForComparison(currentPlan) !== normalizeForComparison(text);
77
- const nextDoc = ensureDocSections(setMarkdownSection(baseDoc, "Plan", text), config.tasks.doc.required_sections);
78
- const docChanged = nextDoc !== baseDoc;
79
136
  const readmePath = path.join(resolved.gitRoot, config.paths.workflow_dir, task.id, "README.md");
80
- if (!planChanged && !docChanged && !updatedBy) {
81
- process.stdout.write(`${readmePath}\n`);
82
- return 0;
137
+ if (useStore) {
138
+ let expectedCurrentPlan;
139
+ await store.patch(opts.taskId, (current) => {
140
+ const { currentPlan, nextPlan, planChanged, docChanged } = buildPlanDocUpdate({
141
+ currentDocRaw: String(current.doc ?? ""),
142
+ text,
143
+ requiredSections: config.tasks.doc.required_sections,
144
+ });
145
+ if (!planChanged && !docChanged && !updatedBy)
146
+ return null;
147
+ if (!docChanged) {
148
+ return {
149
+ ...(planChanged
150
+ ? {
151
+ task: {
152
+ plan_approval: {
153
+ state: "pending",
154
+ updated_at: null,
155
+ updated_by: null,
156
+ note: null,
157
+ },
158
+ },
159
+ }
160
+ : {}),
161
+ ...(updatedBy ? { docMeta: { touch: true, updatedBy } } : {}),
162
+ };
163
+ }
164
+ expectedCurrentPlan ??= currentPlan;
165
+ return {
166
+ doc: {
167
+ kind: "set-section",
168
+ section: "Plan",
169
+ text: nextPlan,
170
+ requiredSections: config.tasks.doc.required_sections,
171
+ expectedCurrentText: expectedCurrentPlan,
172
+ },
173
+ ...(planChanged
174
+ ? {
175
+ task: {
176
+ plan_approval: {
177
+ state: "pending",
178
+ updated_at: null,
179
+ updated_by: null,
180
+ note: null,
181
+ },
182
+ },
183
+ }
184
+ : {}),
185
+ ...(updatedBy ? { docMeta: { updatedBy } } : {}),
186
+ };
187
+ });
188
+ }
189
+ else {
190
+ const existingDoc = (typeof task.doc === "string" ? task.doc : "") || (await backend.getTaskDoc(task.id));
191
+ const { planChanged, docChanged, nextDoc } = buildPlanDocUpdate({
192
+ currentDocRaw: existingDoc,
193
+ text,
194
+ requiredSections: config.tasks.doc.required_sections,
195
+ });
196
+ if (!planChanged && !docChanged && !updatedBy) {
197
+ process.stdout.write(`${readmePath}\n`);
198
+ return 0;
199
+ }
200
+ const nextTask = {
201
+ ...task,
202
+ doc: nextDoc,
203
+ ...(planChanged
204
+ ? { plan_approval: { state: "pending", updated_at: null, updated_by: null, note: null } }
205
+ : {}),
206
+ ...(updatedBy ? { doc_updated_by: updatedBy } : {}),
207
+ };
208
+ await backend.writeTask(nextTask);
83
209
  }
84
- const nextTask = {
85
- ...task,
86
- doc: nextDoc,
87
- ...(planChanged
88
- ? { plan_approval: { state: "pending", updated_at: null, updated_by: null, note: null } }
89
- : {}),
90
- ...(updatedBy ? { doc_updated_by: updatedBy } : {}),
91
- };
92
- await (useStore ? store.update(opts.taskId, () => nextTask) : backend.writeTask(nextTask));
93
210
  process.stdout.write(`${readmePath}\n`);
94
211
  return 0;
95
212
  }
@@ -117,64 +234,36 @@ export async function cmdTaskPlanApprove(opts) {
117
234
  });
118
235
  }
119
236
  const note = typeof opts.note === "string" ? opts.note.trim() : "";
120
- const existingDoc = useStore
121
- ? String(task.doc ?? "")
122
- : (typeof task.doc === "string" ? task.doc : "") || (await backend.getTaskDoc(task.id));
123
- const baseDoc = ensureDocSections(existingDoc ?? "", config.tasks.doc.required_sections);
124
- const plan = extractDocSection(baseDoc, "Plan");
125
- if (!plan || plan.trim().length === 0) {
126
- throw new CliError({
127
- exitCode: 3,
128
- code: "E_VALIDATION",
129
- message: `${task.id}: cannot approve plan: ## Plan section is missing or empty`,
130
- });
131
- }
132
- ensureAgentFilledRequiredDocSections({
133
- task,
134
- config,
135
- doc: baseDoc,
136
- action: "approve plan",
137
- });
138
- const enforceVerifySteps = config.tasks.verify.enforce_on_plan_approve !== false;
139
- if (enforceVerifySteps) {
140
- const tags = toStringArray(task.tags);
141
- const spikeTag = (config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
142
- const verifyRequired = requiresVerifyStepsByPrimary(tags, config);
143
- const isSpike = tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
144
- if (verifyRequired || isSpike) {
145
- const verifySteps = extractDocSection(baseDoc, "Verify Steps");
146
- if (!isVerifyStepsFilled(verifySteps)) {
147
- throw new CliError({
148
- exitCode: 3,
149
- code: "E_VALIDATION",
150
- message: `${task.id}: cannot approve plan: ## Verify Steps section is missing/empty/unfilled ` +
151
- "(fill it before approving plan)",
152
- });
153
- }
154
- }
155
- if (isSpike) {
156
- const observationSection = taskObservationSectionName(normalizeTaskDocVersion(task.doc_version));
157
- const observation = extractTaskObservationSection(baseDoc, normalizeTaskDocVersion(task.doc_version));
158
- if (!observation || observation.trim().length === 0) {
159
- throw new CliError({
160
- exitCode: 3,
161
- code: "E_VALIDATION",
162
- message: `${task.id}: cannot approve plan for spike: ## ${observationSection} section is missing or empty ` +
163
- "(include Findings/Decision/Next Steps)",
164
- });
165
- }
166
- }
167
- }
168
- const nextTask = {
169
- ...task,
170
- plan_approval: {
171
- state: "approved",
172
- updated_at: nowIso(),
173
- updated_by: by,
174
- note: note || null,
175
- },
176
- };
177
- await (useStore ? store.update(opts.taskId, () => nextTask) : backend.writeTask(nextTask));
237
+ const approvedAt = nowIso();
238
+ await (useStore
239
+ ? store.patch(opts.taskId, (current) => {
240
+ const currentDoc = ensureDocSections(String(current.doc ?? ""), config.tasks.doc.required_sections);
241
+ assertPlanCanBeApproved({ task: current, config, doc: currentDoc });
242
+ return {
243
+ task: {
244
+ plan_approval: {
245
+ state: "approved",
246
+ updated_at: approvedAt,
247
+ updated_by: by,
248
+ note: note || null,
249
+ },
250
+ },
251
+ };
252
+ })
253
+ : (async () => {
254
+ const existingDoc = (typeof task.doc === "string" ? task.doc : "") || (await backend.getTaskDoc(task.id));
255
+ const baseDoc = ensureDocSections(existingDoc ?? "", config.tasks.doc.required_sections);
256
+ assertPlanCanBeApproved({ task, config, doc: baseDoc });
257
+ await backend.writeTask({
258
+ ...task,
259
+ plan_approval: {
260
+ state: "approved",
261
+ updated_at: approvedAt,
262
+ updated_by: by,
263
+ note: note || null,
264
+ },
265
+ });
266
+ })());
178
267
  return 0;
179
268
  }
180
269
  catch (err) {
@@ -208,28 +297,36 @@ export async function cmdTaskPlanReject(opts) {
208
297
  message: "Invalid value for --note: empty.",
209
298
  });
210
299
  }
211
- const existingDoc = useStore
212
- ? String(task.doc ?? "")
213
- : (typeof task.doc === "string" ? task.doc : "") || (await backend.getTaskDoc(task.id));
214
- const baseDoc = ensureDocSections(existingDoc ?? "", config.tasks.doc.required_sections);
215
- const plan = extractDocSection(baseDoc, "Plan");
216
- if (!plan || plan.trim().length === 0) {
217
- throw new CliError({
218
- exitCode: 3,
219
- code: "E_VALIDATION",
220
- message: `${task.id}: cannot reject plan: ## Plan section is missing or empty`,
221
- });
222
- }
223
- const nextTask = {
224
- ...task,
225
- plan_approval: {
226
- state: "rejected",
227
- updated_at: nowIso(),
228
- updated_by: by,
229
- note: note || null,
230
- },
231
- };
232
- await (useStore ? store.update(opts.taskId, () => nextTask) : backend.writeTask(nextTask));
300
+ const rejectedAt = nowIso();
301
+ await (useStore
302
+ ? store.patch(opts.taskId, (current) => {
303
+ const currentDoc = ensureDocSections(String(current.doc ?? ""), config.tasks.doc.required_sections);
304
+ assertPlanSectionPresent(current.id, currentDoc, "reject");
305
+ return {
306
+ task: {
307
+ plan_approval: {
308
+ state: "rejected",
309
+ updated_at: rejectedAt,
310
+ updated_by: by,
311
+ note: note || null,
312
+ },
313
+ },
314
+ };
315
+ })
316
+ : (async () => {
317
+ const existingDoc = (typeof task.doc === "string" ? task.doc : "") || (await backend.getTaskDoc(task.id));
318
+ const baseDoc = ensureDocSections(existingDoc ?? "", config.tasks.doc.required_sections);
319
+ assertPlanSectionPresent(task.id, baseDoc, "reject");
320
+ await backend.writeTask({
321
+ ...task,
322
+ plan_approval: {
323
+ state: "rejected",
324
+ updated_at: rejectedAt,
325
+ updated_by: by,
326
+ note: note || null,
327
+ },
328
+ });
329
+ })());
233
330
  return 0;
234
331
  }
235
332
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"set-status.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/set-status.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAOtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,mBAAmB,CAyK9D,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC5E,KAAK,UAAU,EAAE,GAAG,mBAAmB,KAAG,OAAO,CAAC,MAAM,CAAC,CAsBxE"}
1
+ {"version":3,"file":"set-status.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/set-status.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAOtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,mBAAmB,CA0K9D,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC5E,KAAK,UAAU,EAAE,GAAG,mBAAmB,KAAG,OAAO,CAAC,MAAM,CAAC,CAsBxE"}
@@ -71,7 +71,7 @@ export const taskSetStatusSpec = {
71
71
  kind: "boolean",
72
72
  name: "commit-allow-tasks",
73
73
  default: true,
74
- description: "Allow committing under .agentplane/tasks when commit-from-comment is used.",
74
+ description: "Allow the tasks export snapshot plus artifacts under the active task subtree when commit-from-comment is used.",
75
75
  },
76
76
  {
77
77
  kind: "boolean",
@@ -1 +1 @@
1
- {"version":3,"file":"set-status.d.ts","sourceRoot":"","sources":["../../../src/commands/task/set-status.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAiBnC,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwJlB"}
1
+ {"version":3,"file":"set-status.d.ts","sourceRoot":"","sources":["../../../src/commands/task/set-status.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AA2BnC,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,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,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2OlB"}
@@ -5,8 +5,8 @@ import { CliError } from "../../shared/errors.js";
5
5
  import { commitFromComment } from "../guard/index.js";
6
6
  import { ensureActionApproved } from "../shared/approval-requirements.js";
7
7
  import { loadCommandContext, loadTaskFromContext, resolveDocUpdatedBy, } from "../shared/task-backend.js";
8
- import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
9
- import { appendTaskEvent, defaultCommitEmojiForStatus, ensureCommentCommitAllowed, ensureStatusTransitionAllowed, normalizeTaskDocVersion, normalizeTaskStatus, nowIso, readCommitInfo, resolveTaskDependencyState, resolvePrimaryTag, toStringArray, } from "./shared.js";
8
+ import { appendTaskCommentIntent, appendTaskEventIntent, backendIsLocalFileBackend, getTaskStore, mutateTaskStore, setTaskFieldsIntent, touchTaskDocMetaIntent, } from "../shared/task-store.js";
9
+ import { appendTaskEvent, dependencyWarningMessages, defaultCommitEmojiForStatus, ensureCommentCommitAllowed, resolveCommentCommitWarning, ensureStatusTransitionAllowed, normalizeTaskDocVersion, normalizeTaskStatus, nowIso, readCommitInfo, resolveTaskDependencyState, resolvePrimaryTag, toStringArray, } from "./shared.js";
10
10
  export async function cmdTaskSetStatus(opts) {
11
11
  const nextStatus = normalizeTaskStatus(opts.status);
12
12
  if (nextStatus === "DONE" && !opts.force) {
@@ -42,38 +42,40 @@ export async function cmdTaskSetStatus(opts) {
42
42
  ? await store.get(opts.taskId)
43
43
  : await loadTaskFromContext({ ctx, taskId: opts.taskId });
44
44
  const currentStatus = String(task.status || "TODO").toUpperCase();
45
- ensureStatusTransitionAllowed({
46
- currentStatus,
47
- nextStatus,
48
- force: opts.force,
49
- });
50
- if (!opts.force && (nextStatus === "DOING" || nextStatus === "DONE")) {
51
- const dep = await resolveTaskDependencyState(task, ctx.taskBackend);
52
- if (dep.missing.length > 0 || dep.incomplete.length > 0) {
53
- if (!opts.quiet) {
54
- if (dep.missing.length > 0) {
55
- process.stderr.write(`${warnMessage(`missing deps: ${dep.missing.join(", ")}`)}\n`);
56
- }
57
- if (dep.incomplete.length > 0) {
58
- process.stderr.write(`${warnMessage(`incomplete deps: ${dep.incomplete.join(", ")}`)}\n`);
45
+ if (!useStore) {
46
+ ensureStatusTransitionAllowed({
47
+ currentStatus,
48
+ nextStatus,
49
+ force: opts.force,
50
+ });
51
+ if (!opts.force && (nextStatus === "DOING" || nextStatus === "DONE")) {
52
+ const dep = await resolveTaskDependencyState(task, ctx.taskBackend);
53
+ if (dep.missing.length > 0 || dep.incomplete.length > 0) {
54
+ if (!opts.quiet) {
55
+ if (dep.missing.length > 0) {
56
+ process.stderr.write(`${warnMessage(`missing deps: ${dep.missing.join(", ")}`)}\n`);
57
+ }
58
+ if (dep.incomplete.length > 0) {
59
+ process.stderr.write(`${warnMessage(`incomplete deps: ${dep.incomplete.join(", ")}`)}\n`);
60
+ }
59
61
  }
62
+ throw new CliError({
63
+ exitCode: 2,
64
+ code: "E_USAGE",
65
+ message: `Task is not ready: ${task.id} (use --force to override)`,
66
+ });
60
67
  }
61
- throw new CliError({
62
- exitCode: 2,
63
- code: "E_USAGE",
64
- message: `Task is not ready: ${task.id} (use --force to override)`,
65
- });
66
68
  }
69
+ ensureCommentCommitAllowed({
70
+ enabled: opts.commitFromComment,
71
+ config,
72
+ action: "task set-status",
73
+ confirmed: opts.confirmStatusCommit,
74
+ quiet: opts.quiet,
75
+ statusFrom: currentStatus,
76
+ statusTo: nextStatus,
77
+ });
67
78
  }
68
- ensureCommentCommitAllowed({
69
- enabled: opts.commitFromComment,
70
- config,
71
- action: "task set-status",
72
- confirmed: opts.confirmStatusCommit,
73
- quiet: opts.quiet,
74
- statusFrom: currentStatus,
75
- statusTo: nextStatus,
76
- });
77
79
  const existingComments = Array.isArray(task.comments)
78
80
  ? task.comments.filter((item) => !!item && typeof item.author === "string" && typeof item.body === "string")
79
81
  : [];
@@ -87,6 +89,13 @@ export async function cmdTaskSetStatus(opts) {
87
89
  }
88
90
  const at = nowIso();
89
91
  const eventAuthor = resolveDocUpdatedBy(task, opts.author);
92
+ const commitInfo = opts.commit ? await readCommitInfo(resolved.gitRoot, opts.commit) : null;
93
+ const nextCommit = opts.commit
94
+ ? { hash: commitInfo.hash, message: commitInfo.message }
95
+ : undefined;
96
+ let currentStatusForCommit = currentStatus;
97
+ let primaryTagForCommit = resolvePrimaryTag(toStringArray(task.tags), ctx).primary;
98
+ let deferredWarnings = [];
90
99
  const next = {
91
100
  ...task,
92
101
  status: nextStatus,
@@ -104,10 +113,81 @@ export async function cmdTaskSetStatus(opts) {
104
113
  doc_updated_by: eventAuthor,
105
114
  };
106
115
  if (opts.commit) {
107
- const commitInfo = await readCommitInfo(resolved.gitRoot, opts.commit);
108
- next.commit = { hash: commitInfo.hash, message: commitInfo.message };
116
+ next.commit = nextCommit;
117
+ }
118
+ try {
119
+ await (useStore
120
+ ? mutateTaskStore(store, opts.taskId, async (current) => {
121
+ deferredWarnings = [];
122
+ const currentStatus = String(current.status || "TODO").toUpperCase();
123
+ currentStatusForCommit = currentStatus;
124
+ primaryTagForCommit = resolvePrimaryTag(toStringArray(current.tags), ctx).primary;
125
+ ensureStatusTransitionAllowed({
126
+ currentStatus,
127
+ nextStatus,
128
+ force: opts.force,
129
+ });
130
+ if (!opts.force && (nextStatus === "DOING" || nextStatus === "DONE")) {
131
+ const dep = await resolveTaskDependencyState(current, ctx.taskBackend);
132
+ deferredWarnings = [...deferredWarnings, ...dependencyWarningMessages(dep)];
133
+ if (dep.missing.length > 0 || dep.incomplete.length > 0) {
134
+ throw new CliError({
135
+ exitCode: 2,
136
+ code: "E_USAGE",
137
+ message: `Task is not ready: ${current.id} (use --force to override)`,
138
+ });
139
+ }
140
+ }
141
+ const commitWarning = resolveCommentCommitWarning({
142
+ enabled: opts.commitFromComment,
143
+ config,
144
+ action: "task set-status",
145
+ confirmed: opts.confirmStatusCommit,
146
+ quiet: opts.quiet,
147
+ statusFrom: currentStatus,
148
+ statusTo: nextStatus,
149
+ });
150
+ if (commitWarning)
151
+ deferredWarnings.push(commitWarning);
152
+ const currentEventAuthor = resolveDocUpdatedBy(current, opts.author);
153
+ const intents = [
154
+ setTaskFieldsIntent({
155
+ status: nextStatus,
156
+ ...(nextCommit ? { commit: nextCommit } : {}),
157
+ }),
158
+ appendTaskEventIntent({
159
+ type: "status",
160
+ at,
161
+ author: currentEventAuthor,
162
+ from: currentStatus,
163
+ to: nextStatus,
164
+ note: commentBody,
165
+ }),
166
+ touchTaskDocMetaIntent({
167
+ updatedBy: currentEventAuthor,
168
+ version: normalizeTaskDocVersion(current.doc_version),
169
+ }),
170
+ ];
171
+ if (commentBody) {
172
+ intents.splice(1, 0, appendTaskCommentIntent({ author: opts.author, body: commentBody }));
173
+ }
174
+ return intents;
175
+ })
176
+ : ctx.taskBackend.writeTask(next));
177
+ }
178
+ catch (err) {
179
+ if (err instanceof CliError && !opts.quiet) {
180
+ for (const warning of new Set(deferredWarnings)) {
181
+ process.stderr.write(`${warnMessage(warning)}\n`);
182
+ }
183
+ }
184
+ throw err;
185
+ }
186
+ if (!opts.quiet) {
187
+ for (const warning of new Set(deferredWarnings)) {
188
+ process.stderr.write(`${warnMessage(warning)}\n`);
189
+ }
109
190
  }
110
- await (useStore ? store.update(opts.taskId, () => next) : ctx.taskBackend.writeTask(next));
111
191
  // tasks.json is export-only; generated via `agentplane task export`.
112
192
  if (opts.commitFromComment) {
113
193
  if (!opts.body) {
@@ -122,9 +202,9 @@ export async function cmdTaskSetStatus(opts) {
122
202
  cwd: opts.cwd,
123
203
  rootOverride: opts.rootOverride,
124
204
  taskId: opts.taskId,
125
- primaryTag: resolvePrimaryTag(toStringArray(task.tags), ctx).primary,
205
+ primaryTag: primaryTagForCommit,
126
206
  author: opts.author,
127
- statusFrom: currentStatus,
207
+ statusFrom: currentStatusForCommit,
128
208
  statusTo: nextStatus,
129
209
  commentBody: opts.body,
130
210
  formattedComment: formatCommentBodyForCommit(opts.body, config),
@@ -10,6 +10,7 @@ export declare function ensureTaskDependsOnGraphIsAcyclic(opts: {
10
10
  dependsOn: string[];
11
11
  }): Promise<void>;
12
12
  export declare function resolveTaskDependencyState(task: TaskData, backend: Pick<TaskBackend, "getTask" | "getTasks">): Promise<DependencyState>;
13
+ export declare function dependencyWarningMessages(dep: DependencyState): string[];
13
14
  export declare function buildDependencyState(tasks: TaskData[]): Map<string, DependencyState>;
14
15
  export declare function formatTaskLine(task: TaskData, depState?: DependencyState): string;
15
16
  //# sourceMappingURL=dependencies.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../../../src/commands/task/shared/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAK/E,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAkCF,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BhB;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,UAAU,CAAC,GACjD,OAAO,CAAC,eAAe,CAAC,CAuB1B;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAqBpF;AAgBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,MAAM,CAejF"}
1
+ {"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../../../src/commands/task/shared/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAK/E,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAkCF,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BhB;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,UAAU,CAAC,GACjD,OAAO,CAAC,eAAe,CAAC,CAuB1B;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,EAAE,CASxE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAqBpF;AAgBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,MAAM,CAejF"}
@@ -83,6 +83,16 @@ export async function resolveTaskDependencyState(task, backend) {
83
83
  }
84
84
  return { dependsOn, missing, incomplete };
85
85
  }
86
+ export function dependencyWarningMessages(dep) {
87
+ const warnings = [];
88
+ if (dep.missing.length > 0) {
89
+ warnings.push(`missing deps: ${dep.missing.join(", ")}`);
90
+ }
91
+ if (dep.incomplete.length > 0) {
92
+ warnings.push(`incomplete deps: ${dep.incomplete.join(", ")}`);
93
+ }
94
+ return warnings;
95
+ }
86
96
  export function buildDependencyState(tasks) {
87
97
  const byId = new Map(tasks.map((task) => [task.id, task]));
88
98
  const state = new Map();
@@ -5,6 +5,7 @@ export declare const VERIFY_STEPS_PLACEHOLDER = "<!-- TODO: REPLACE WITH TASK-SP
5
5
  export declare const VERIFICATION_RESULTS_BEGIN = "<!-- BEGIN VERIFICATION RESULTS -->";
6
6
  export declare const VERIFICATION_RESULTS_END = "<!-- END VERIFICATION RESULTS -->";
7
7
  export type TaskDocVersion = 2 | 3;
8
+ export declare function decodeEscapedTaskTextNewlines(text: string): string;
8
9
  export declare function extractDocSection(doc: string, sectionName: string): string | null;
9
10
  export declare function isVerifyStepsFilled(sectionText: string | null): boolean;
10
11
  export declare function normalizeTaskDocVersion(value: unknown, fallback?: TaskDocVersion): TaskDocVersion;
@@ -1 +1 @@
1
- {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../../../src/commands/task/shared/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAElE,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,eAAO,MAAM,wBAAwB,+DACyB,CAAC;AAC/D,eAAO,MAAM,0BAA0B,wCAAwC,CAAC;AAChF,eAAO,MAAM,wBAAwB,sCAAsC,CAAC;AAC5E,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,CAAC;AAEnC,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjF;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKvE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,cAAkB,GAC3B,cAAc,CAEhB;AAED,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,OAAO,EAAE,cAAc,GACtB,MAAM,CAyCR;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,GAAG,UAAU,CAExF;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAIjG;AAMD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKtE;AAED,wBAAgB,oCAAoC,CAAC,IAAI,EAAE;IACzD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CA6BP"}
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../../../src/commands/task/shared/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAElE,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,eAAO,MAAM,wBAAwB,+DACyB,CAAC;AAC/D,eAAO,MAAM,0BAA0B,wCAAwC,CAAC;AAChF,eAAO,MAAM,wBAAwB,sCAAsC,CAAC;AAC5E,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,CAAC;AAEnC,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjF;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKvE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,cAAkB,GAC3B,cAAc,CAEhB;AAED,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,OAAO,EAAE,cAAc,GACtB,MAAM,CAyCR;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,GAAG,UAAU,CAExF;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAIjG;AAMD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKtE;AAED,wBAAgB,oCAAoC,CAAC,IAAI,EAAE;IACzD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CA6BP"}
@@ -6,6 +6,13 @@ export function nowIso() {
6
6
  export const VERIFY_STEPS_PLACEHOLDER = "<!-- TODO: REPLACE WITH TASK-SPECIFIC ACCEPTANCE STEPS -->";
7
7
  export const VERIFICATION_RESULTS_BEGIN = "<!-- BEGIN VERIFICATION RESULTS -->";
8
8
  export const VERIFICATION_RESULTS_END = "<!-- END VERIFICATION RESULTS -->";
9
+ export function decodeEscapedTaskTextNewlines(text) {
10
+ const normalized = text.replaceAll("\r\n", "\n");
11
+ if (!normalized.includes(String.raw `\n`) && !normalized.includes(String.raw `\r\n`)) {
12
+ return normalized;
13
+ }
14
+ return normalized.replaceAll(String.raw `\r\n`, "\n").replaceAll(String.raw `\n`, "\n");
15
+ }
9
16
  export function extractDocSection(doc, sectionName) {
10
17
  const lines = doc.replaceAll("\r\n", "\n").split("\n");
11
18
  let capturing = false;
@@ -33,7 +40,7 @@ export function isVerifyStepsFilled(sectionText) {
33
40
  return false;
34
41
  return true;
35
42
  }
36
- export function normalizeTaskDocVersion(value, fallback = 2) {
43
+ export function normalizeTaskDocVersion(value, fallback = 3) {
37
44
  return value === 3 ? 3 : value === 2 ? 2 : fallback;
38
45
  }
39
46
  export function normalizeVerificationSectionLayout(sectionText, version) {