agentplane 0.3.6 → 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 (120) hide show
  1. package/dist/.build-manifest.json +106 -96
  2. package/dist/adapters/task-backend/task-backend-adapter.d.ts +2 -2
  3. package/dist/adapters/task-backend/task-backend-adapter.d.ts.map +1 -1
  4. package/dist/adapters/task-backend/task-backend-adapter.js +2 -2
  5. package/dist/backends/task-backend/local-backend.d.ts +7 -5
  6. package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
  7. package/dist/backends/task-backend/local-backend.js +79 -7
  8. package/dist/backends/task-backend/redmine/env.d.ts +1 -1
  9. package/dist/backends/task-backend/redmine/env.d.ts.map +1 -1
  10. package/dist/backends/task-backend/redmine/env.js +3 -0
  11. package/dist/backends/task-backend/redmine/inspect.d.ts +11 -0
  12. package/dist/backends/task-backend/redmine/inspect.d.ts.map +1 -0
  13. package/dist/backends/task-backend/redmine/inspect.js +75 -0
  14. package/dist/backends/task-backend/redmine/mapping.d.ts.map +1 -1
  15. package/dist/backends/task-backend/redmine/mapping.js +21 -2
  16. package/dist/backends/task-backend/redmine/state.d.ts +17 -0
  17. package/dist/backends/task-backend/redmine/state.d.ts.map +1 -0
  18. package/dist/backends/task-backend/redmine/state.js +95 -0
  19. package/dist/backends/task-backend/redmine-backend.d.ts +10 -16
  20. package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
  21. package/dist/backends/task-backend/redmine-backend.js +205 -15
  22. package/dist/backends/task-backend/shared/constants.d.ts +1 -1
  23. package/dist/backends/task-backend/shared/constants.js +1 -1
  24. package/dist/backends/task-backend/shared/record.d.ts.map +1 -1
  25. package/dist/backends/task-backend/shared/record.js +20 -1
  26. package/dist/backends/task-backend/shared/types.d.ts +42 -4
  27. package/dist/backends/task-backend/shared/types.d.ts.map +1 -1
  28. package/dist/backends/task-backend/shared.d.ts +1 -1
  29. package/dist/backends/task-backend/shared.d.ts.map +1 -1
  30. package/dist/backends/task-backend.d.ts +1 -1
  31. package/dist/backends/task-backend.d.ts.map +1 -1
  32. package/dist/backends/task-index.d.ts.map +1 -1
  33. package/dist/backends/task-index.js +1 -0
  34. package/dist/cli/run-cli/command-catalog/project.d.ts +1 -1
  35. package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -1
  36. package/dist/cli/run-cli/command-catalog/project.js +3 -1
  37. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  38. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  39. package/dist/cli/run-cli/commands/init/write-env.d.ts.map +1 -1
  40. package/dist/cli/run-cli/commands/init/write-env.js +12 -0
  41. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  42. package/dist/cli/run-cli.test-helpers.js +2 -0
  43. package/dist/commands/backend/sync.command.d.ts +5 -1
  44. package/dist/commands/backend/sync.command.d.ts.map +1 -1
  45. package/dist/commands/backend/sync.command.js +67 -3
  46. package/dist/commands/backend.d.ts +22 -0
  47. package/dist/commands/backend.d.ts.map +1 -1
  48. package/dist/commands/backend.js +110 -1
  49. package/dist/commands/commit.spec.d.ts.map +1 -1
  50. package/dist/commands/commit.spec.js +30 -6
  51. package/dist/commands/doctor/workspace.d.ts +8 -0
  52. package/dist/commands/doctor/workspace.d.ts.map +1 -1
  53. package/dist/commands/doctor/workspace.js +127 -3
  54. package/dist/commands/guard/commit.command.d.ts.map +1 -1
  55. package/dist/commands/guard/commit.command.js +30 -6
  56. package/dist/commands/guard/impl/allow.d.ts +4 -0
  57. package/dist/commands/guard/impl/allow.d.ts.map +1 -1
  58. package/dist/commands/guard/impl/allow.js +14 -3
  59. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  60. package/dist/commands/guard/impl/commands.js +11 -2
  61. package/dist/commands/shared/task-backend.d.ts +1 -1
  62. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  63. package/dist/commands/shared/task-backend.js +9 -0
  64. package/dist/commands/shared/task-store.d.ts +61 -2
  65. package/dist/commands/shared/task-store.d.ts.map +1 -1
  66. package/dist/commands/shared/task-store.js +298 -60
  67. package/dist/commands/task/block.d.ts.map +1 -1
  68. package/dist/commands/task/block.js +58 -37
  69. package/dist/commands/task/close-shared.d.ts.map +1 -1
  70. package/dist/commands/task/close-shared.js +17 -20
  71. package/dist/commands/task/comment.d.ts.map +1 -1
  72. package/dist/commands/task/comment.js +14 -19
  73. package/dist/commands/task/derive.command.d.ts +1 -0
  74. package/dist/commands/task/derive.command.d.ts.map +1 -1
  75. package/dist/commands/task/derive.command.js +15 -2
  76. package/dist/commands/task/derive.d.ts +1 -0
  77. package/dist/commands/task/derive.d.ts.map +1 -1
  78. package/dist/commands/task/derive.js +27 -4
  79. package/dist/commands/task/doc.d.ts.map +1 -1
  80. package/dist/commands/task/doc.js +16 -5
  81. package/dist/commands/task/finish.d.ts.map +1 -1
  82. package/dist/commands/task/finish.js +41 -41
  83. package/dist/commands/task/migrate-doc.d.ts +15 -0
  84. package/dist/commands/task/migrate-doc.d.ts.map +1 -1
  85. package/dist/commands/task/migrate-doc.js +126 -35
  86. package/dist/commands/task/new.d.ts.map +1 -1
  87. package/dist/commands/task/new.js +3 -1
  88. package/dist/commands/task/plan.js +28 -28
  89. package/dist/commands/task/set-status.d.ts.map +1 -1
  90. package/dist/commands/task/set-status.js +104 -61
  91. package/dist/commands/task/shared/dependencies.d.ts +1 -0
  92. package/dist/commands/task/shared/dependencies.d.ts.map +1 -1
  93. package/dist/commands/task/shared/dependencies.js +10 -0
  94. package/dist/commands/task/shared/docs.js +1 -1
  95. package/dist/commands/task/shared/transitions.d.ts +17 -0
  96. package/dist/commands/task/shared/transitions.d.ts.map +1 -1
  97. package/dist/commands/task/shared/transitions.js +20 -7
  98. package/dist/commands/task/shared.d.ts +2 -2
  99. package/dist/commands/task/shared.d.ts.map +1 -1
  100. package/dist/commands/task/shared.js +2 -2
  101. package/dist/commands/task/start.d.ts.map +1 -1
  102. package/dist/commands/task/start.js +33 -28
  103. package/dist/commands/task/verify-record.d.ts.map +1 -1
  104. package/dist/commands/task/verify-record.js +32 -32
  105. package/dist/commands/upgrade/apply.d.ts +2 -0
  106. package/dist/commands/upgrade/apply.d.ts.map +1 -1
  107. package/dist/commands/upgrade/apply.js +33 -1
  108. package/dist/commands/upgrade.command.d.ts.map +1 -1
  109. package/dist/commands/upgrade.command.js +25 -0
  110. package/dist/commands/upgrade.d.ts +1 -0
  111. package/dist/commands/upgrade.d.ts.map +1 -1
  112. package/dist/commands/upgrade.js +34 -0
  113. package/dist/policy/rules/allowlist.d.ts.map +1 -1
  114. package/dist/policy/rules/allowlist.js +12 -9
  115. package/dist/ports/task-backend-port.d.ts +2 -2
  116. package/dist/ports/task-backend-port.d.ts.map +1 -1
  117. package/dist/shared/protected-paths.d.ts +10 -0
  118. package/dist/shared/protected-paths.d.ts.map +1 -1
  119. package/dist/shared/protected-paths.js +33 -0
  120. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  import { mapBackendError } from "../../cli/error-map.js";
2
2
  import { successMessage } from "../../cli/output.js";
3
3
  import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
4
- import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
4
+ import { appendTaskCommentIntent, appendTaskEventIntent, backendIsLocalFileBackend, getTaskStore, touchTaskDocMetaIntent, } from "../shared/task-store.js";
5
5
  import { appendTaskEvent, normalizeTaskDocVersion, nowIso } from "./shared.js";
6
6
  export async function cmdTaskComment(opts) {
7
7
  try {
@@ -9,27 +9,22 @@ export async function cmdTaskComment(opts) {
9
9
  (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
10
10
  const useStore = backendIsLocalFileBackend(ctx);
11
11
  const store = useStore ? getTaskStore(ctx) : null;
12
- const task = useStore
13
- ? await store.get(opts.taskId)
14
- : await loadTaskFromContext({ ctx, taskId: opts.taskId });
12
+ const task = useStore ? null : await loadTaskFromContext({ ctx, taskId: opts.taskId });
15
13
  const at = nowIso();
16
14
  await (useStore
17
- ? store.patch(opts.taskId, () => ({
18
- appendComments: [{ author: opts.author, body: opts.body }],
19
- appendEvents: [
20
- {
21
- type: "comment",
22
- at,
23
- author: opts.author,
24
- body: opts.body,
25
- },
26
- ],
27
- docMeta: {
28
- touch: true,
15
+ ? store.mutate(opts.taskId, (current) => [
16
+ appendTaskCommentIntent({ author: opts.author, body: opts.body }),
17
+ appendTaskEventIntent({
18
+ type: "comment",
19
+ at,
20
+ author: opts.author,
21
+ body: opts.body,
22
+ }),
23
+ touchTaskDocMetaIntent({
29
24
  updatedBy: opts.author,
30
- version: normalizeTaskDocVersion(task.doc_version),
31
- },
32
- }))
25
+ version: normalizeTaskDocVersion(current.doc_version),
26
+ }),
27
+ ])
33
28
  : ctx.taskBackend.writeTask({
34
29
  ...task,
35
30
  comments: [
@@ -7,6 +7,7 @@ export type TaskDeriveParsed = {
7
7
  owner: string;
8
8
  priority: "low" | "normal" | "med" | "high";
9
9
  tags: string[];
10
+ verify: string[];
10
11
  };
11
12
  export declare const taskDeriveSpec: CommandSpec<TaskDeriveParsed>;
12
13
  export declare function makeRunTaskDeriveHandler(getCtx: (cmd: string) => Promise<CommandContext>): (ctx: CommandCtx, p: TaskDeriveParsed) => Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"derive.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/derive.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,gBAAgB,CAqExD,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IACzE,KAAK,UAAU,EAAE,GAAG,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC,CAarE"}
1
+ {"version":3,"file":"derive.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/derive.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,gBAAgB,CAiFxD,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IACzE,KAAK,UAAU,EAAE,GAAG,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC,CAcrE"}
@@ -45,13 +45,24 @@ export const taskDeriveSpec = {
45
45
  minCount: 1,
46
46
  description: "Repeatable. Adds a tag (must provide at least one).",
47
47
  },
48
+ {
49
+ kind: "string",
50
+ name: "verify",
51
+ valueHint: "<command>",
52
+ repeatable: true,
53
+ description: "Repeatable. Verification commands/checks to run for this task.",
54
+ },
48
55
  ],
49
56
  examples: [
50
57
  {
51
- cmd: 'agentplane task derive 202602070101-ABCD --title "Implement X" --description "Do the thing" --owner CODER --tag code',
52
- why: "Create an implementation task derived from a spike.",
58
+ cmd: 'agentplane task derive 202602070101-ABCD --title "Implement X" --description "Do the thing" --owner CODER --tag code --verify "bun test"',
59
+ why: "Create an implementation task derived from a spike with seeded verify steps.",
53
60
  },
54
61
  ],
62
+ notes: [
63
+ "Derived tasks default to doc_version=3 and seed the README v3 section contract automatically.",
64
+ "For verify-required primary tags, this command seeds a default ## Verify Steps acceptance contract in README.",
65
+ ],
55
66
  validateRaw: (raw) => {
56
67
  const tags = toStringList(raw.opts.tag);
57
68
  if (tags.some((t) => t.trim() === "")) {
@@ -69,6 +80,7 @@ export const taskDeriveSpec = {
69
80
  owner: raw.opts.owner,
70
81
  priority: raw.opts.priority ?? "med",
71
82
  tags: toStringList(raw.opts.tag),
83
+ verify: (raw.opts.verify ?? []),
72
84
  }),
73
85
  };
74
86
  export function makeRunTaskDeriveHandler(getCtx) {
@@ -83,6 +95,7 @@ export function makeRunTaskDeriveHandler(getCtx) {
83
95
  owner: p.owner,
84
96
  priority: p.priority,
85
97
  tags: p.tags,
98
+ verify: p.verify,
86
99
  });
87
100
  };
88
101
  }
@@ -9,5 +9,6 @@ export declare function cmdTaskDerive(opts: {
9
9
  owner: string;
10
10
  priority: "low" | "normal" | "med" | "high";
11
11
  tags: string[];
12
+ verify: string[];
12
13
  }): Promise<number>;
13
14
  //# sourceMappingURL=derive.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../../src/commands/task/derive.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAepF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiElB"}
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../../src/commands/task/derive.ts"],"names":[],"mappings":"AAKA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAuBpF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsGlB"}
@@ -1,8 +1,10 @@
1
+ import { setMarkdownSection, taskDocToSectionMap } from "@agentplaneorg/core";
1
2
  import { mapBackendError } from "../../cli/error-map.js";
2
- import { unknownEntityMessage } from "../../cli/output.js";
3
+ import { unknownEntityMessage, warnMessage } from "../../cli/output.js";
3
4
  import { CliError } from "../../shared/errors.js";
4
5
  import { loadCommandContext } from "../shared/task-backend.js";
5
- import { extractTaskObservationSection, normalizeTaskDocVersion, nowIso, toStringArray, } from "./shared.js";
6
+ import { buildDefaultVerifyStepsSection, defaultTaskDocV3, TASK_DOC_VERSION_V3, } from "./doc-template.js";
7
+ import { extractTaskObservationSection, normalizeTaskDocVersion, nowIso, requiresVerifyStepsByPrimary, resolvePrimaryTag, toStringArray, warnIfUnknownOwner, } from "./shared.js";
6
8
  function normalizeOneLine(text, maxChars) {
7
9
  const normalized = text.trim().replaceAll(/\s+/g, " ");
8
10
  if (normalized.length <= maxChars)
@@ -45,6 +47,25 @@ export async function cmdTaskDerive(opts) {
45
47
  description = `${description} (derived from spike ${opts.spikeId})`;
46
48
  if (excerpt)
47
49
  description = `${description} [spike_notes: ${excerpt}]`;
50
+ let doc = defaultTaskDocV3({ title: opts.title, description });
51
+ const spikeTag = (ctx.config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
52
+ const primary = resolvePrimaryTag(opts.tags, ctx);
53
+ if (primary.usedFallback) {
54
+ process.stderr.write(`${warnMessage(`primary tag not found in task tags; using fallback primary=${primary.primary}`)}\n`);
55
+ }
56
+ const requiresVerifySteps = requiresVerifyStepsByPrimary(opts.tags, ctx.config);
57
+ await warnIfUnknownOwner(ctx, opts.owner);
58
+ if (requiresVerifySteps) {
59
+ doc = setMarkdownSection(doc, "Verify Steps", buildDefaultVerifyStepsSection({
60
+ primary: primary.primary,
61
+ verifyCommands: opts.verify,
62
+ }));
63
+ process.stderr.write(`${warnMessage("task requires Verify Steps by primary tag; seeded a default ## Verify Steps section in README (review and refine before approval/start)")}\n`);
64
+ }
65
+ const hasSpike = opts.tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
66
+ if (hasSpike && requiresVerifySteps) {
67
+ process.stderr.write(`${warnMessage("spike is combined with a primary tag that requires verify steps; consider splitting spike vs implementation tasks")}\n`);
68
+ }
48
69
  const at = nowIso();
49
70
  await ctx.taskBackend.writeTask({
50
71
  id: taskId,
@@ -55,9 +76,11 @@ export async function cmdTaskDerive(opts) {
55
76
  owner: opts.owner,
56
77
  tags: opts.tags,
57
78
  depends_on: [opts.spikeId],
58
- verify: [],
79
+ verify: opts.verify,
59
80
  comments: [],
60
- doc_version: 2,
81
+ doc,
82
+ sections: taskDocToSectionMap(doc),
83
+ doc_version: TASK_DOC_VERSION_V3,
61
84
  doc_updated_at: at,
62
85
  doc_updated_by: opts.owner,
63
86
  id_source: "generated",
@@ -1 +1 @@
1
- {"version":3,"file":"doc.d.ts","sourceRoot":"","sources":["../../../src/commands/task/doc.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AA6CpF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgLlB;AAED,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,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4ClB"}
1
+ {"version":3,"file":"doc.d.ts","sourceRoot":"","sources":["../../../src/commands/task/doc.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AA6CpF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgLlB;AAED,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,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyDlB"}
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { ensureDocSections, normalizeDocSectionName, normalizeTaskDoc, parseDocSections, setMarkdownSection, } from "@agentplaneorg/core";
3
+ import { normalizeDocSectionName, normalizeTaskDoc, parseDocSections, renderTaskDocFromSections, setMarkdownSection, taskDocToSectionMap, ensureDocSections, } from "@agentplaneorg/core";
4
4
  import { mapBackendError, mapCoreError } from "../../cli/error-map.js";
5
5
  import { infoMessage, unknownEntityMessage, backendNotSupportedMessage, warnMessage, } from "../../cli/output.js";
6
6
  import { CliError } from "../../shared/errors.js";
@@ -222,19 +222,30 @@ export async function cmdTaskDocShow(opts) {
222
222
  const useStore = backendIsLocalFileBackend(ctx);
223
223
  const storeTask = useStore ? await getTaskStore(ctx).get(opts.taskId) : null;
224
224
  const doc = useStore
225
- ? String(storeTask.doc ?? "")
225
+ ? storeTask?.sections && Object.keys(storeTask.sections).length > 0
226
+ ? renderTaskDocFromSections(storeTask.sections)
227
+ : String(storeTask?.doc ?? "")
226
228
  : ((await backend.getTaskDoc(opts.taskId)) ?? "");
229
+ const canonicalSections = new Map();
230
+ for (const [title, text] of Object.entries(useStore && storeTask?.sections && Object.keys(storeTask.sections).length > 0
231
+ ? storeTask.sections
232
+ : taskDocToSectionMap(doc))) {
233
+ canonicalSections.set(normalizeDocSectionName(title), {
234
+ title,
235
+ lines: String(text ?? "").split("\n"),
236
+ });
237
+ }
227
238
  if (opts.section) {
228
239
  const sectionKey = normalizeDocSectionName(opts.section);
229
- const { sections } = parseDocSections(doc);
230
- const entry = sections.get(sectionKey);
240
+ const entry = canonicalSections.get(sectionKey);
231
241
  const content = entry?.lines ?? [];
232
- if (content.length > 0) {
242
+ if (content.some((line) => line.trim().length > 0)) {
233
243
  process.stdout.write(`${content.join("\n").trimEnd()}\n`);
234
244
  return 0;
235
245
  }
236
246
  if (!opts.quiet) {
237
247
  process.stdout.write(`${infoMessage(`section has no content: ${opts.section}`)}\n`);
248
+ return 0;
238
249
  }
239
250
  return 0;
240
251
  }
@@ -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;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"}
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;AAgGnC,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,CA8YlB"}
@@ -9,7 +9,7 @@ import { buildGitCommitEnv, cmdCommit, commitFromComment } from "../guard/index.
9
9
  import { ensureActionApproved } from "../shared/approval-requirements.js";
10
10
  import { ensureReconciledBeforeMutation } from "../shared/reconcile-check.js";
11
11
  import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
12
- import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
12
+ import { appendTaskCommentIntent, appendTaskEventIntent, backendIsLocalFileBackend, getTaskStore, mutateTaskStore, setTaskFieldsIntent, touchTaskDocMetaIntent, } from "../shared/task-store.js";
13
13
  import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
14
14
  import { appendTaskEvent, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, ensureAgentFilledRequiredDocSections, ensureVerificationSatisfiedIfRequired, normalizeTaskDocVersion, nowIso, readCommitInfo, readHeadCommit, requireStructuredComment, resolvePrimaryTag, toStringArray, } from "./shared.js";
15
15
  async function clearDirectWorkLockIfMatches(opts) {
@@ -175,24 +175,24 @@ export async function cmdFinish(opts) {
175
175
  let primaryTag = null;
176
176
  for (const taskId of opts.taskIds) {
177
177
  const task = useStore ? await store.get(taskId) : await loadTaskFromContext({ ctx, taskId });
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
- });
187
- if (taskId === primaryTaskId &&
188
- (opts.commitFromComment || statusCommitRequested) &&
189
- primaryStatusFrom === null) {
190
- primaryStatusFrom = String(task.status || "TODO").toUpperCase();
191
- primaryTag = resolvePrimaryTag(toStringArray(task.tags), ctx).primary;
178
+ if (!useStore) {
179
+ assertTaskCanFinish({
180
+ task,
181
+ config: ctx.config,
182
+ taskCount: opts.taskIds.length,
183
+ isMetaTask: taskId === metaTaskId,
184
+ resultProvided,
185
+ resultSummary,
186
+ force: opts.force,
187
+ });
188
+ if (taskId === primaryTaskId && (opts.commitFromComment || statusCommitRequested)) {
189
+ primaryStatusFrom = String(task.status || "TODO").toUpperCase();
190
+ primaryTag = resolvePrimaryTag(toStringArray(task.tags), ctx).primary;
191
+ }
192
192
  }
193
193
  const at = nowIso();
194
194
  if (useStore) {
195
- await store.patch(taskId, (current) => {
195
+ await mutateTaskStore(store, taskId, (current) => {
196
196
  assertTaskCanFinish({
197
197
  task: current,
198
198
  config: ctx.config,
@@ -203,31 +203,32 @@ export async function cmdFinish(opts) {
203
203
  force: opts.force,
204
204
  });
205
205
  const currentStatus = String(current.status || "TODO").toUpperCase();
206
- return {
207
- task: {
206
+ if (taskId === primaryTaskId && (opts.commitFromComment || statusCommitRequested)) {
207
+ primaryStatusFrom = currentStatus;
208
+ primaryTag = resolvePrimaryTag(toStringArray(current.tags), ctx).primary;
209
+ }
210
+ return [
211
+ setTaskFieldsIntent({
208
212
  status: "DONE",
209
213
  commit: { hash: commitInfo.hash, message: commitInfo.message },
210
214
  ...(taskId === metaTaskId && resultSummary ? { result_summary: resultSummary } : {}),
211
215
  ...(taskId === metaTaskId && riskLevel ? { risk_level: riskLevel } : {}),
212
216
  ...(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,
217
+ }),
218
+ appendTaskCommentIntent({ author: opts.author, body: opts.body }),
219
+ appendTaskEventIntent({
220
+ type: "status",
221
+ at,
222
+ author: opts.author,
223
+ from: currentStatus,
224
+ to: "DONE",
225
+ note: opts.body,
226
+ }),
227
+ touchTaskDocMetaIntent({
227
228
  updatedBy: opts.author,
228
229
  version: normalizeTaskDocVersion(current.doc_version),
229
- },
230
- };
230
+ }),
231
+ ];
231
232
  });
232
233
  }
233
234
  else {
@@ -314,16 +315,15 @@ export async function cmdFinish(opts) {
314
315
  // Refresh task commit metadata to this hash and amend the same commit in local mode so
315
316
  // "task done" metadata does not require a manual follow-up close commit.
316
317
  await (useStore
317
- ? store.patch(primaryTaskId, (current) => ({
318
- task: {
318
+ ? mutateTaskStore(store, primaryTaskId, (current) => [
319
+ setTaskFieldsIntent({
319
320
  commit: { hash: committed.hash, message: committed.message },
320
- },
321
- docMeta: {
322
- touch: true,
321
+ }),
322
+ touchTaskDocMetaIntent({
323
323
  updatedBy: opts.author,
324
324
  version: normalizeTaskDocVersion(current.doc_version),
325
- },
326
- }))
325
+ }),
326
+ ])
327
327
  : (async () => {
328
328
  const taskAfterCommit = await loadTaskFromContext({ ctx, taskId: primaryTaskId });
329
329
  const updatedAfterCommit = {
@@ -1,3 +1,18 @@
1
+ import { resolveProject, type AgentplaneConfig } from "@agentplaneorg/core";
2
+ import { type CommandContext } from "../shared/task-backend.js";
3
+ export type TaskDocMigrationResult = {
4
+ changed: number;
5
+ changedPaths: string[];
6
+ };
7
+ export declare function migrateTaskDocsInWorkspace(opts: {
8
+ cwd: string;
9
+ rootOverride?: string | null;
10
+ all: boolean;
11
+ taskIds: string[];
12
+ resolvedProject?: Awaited<ReturnType<typeof resolveProject>>;
13
+ config?: AgentplaneConfig;
14
+ ctx?: CommandContext;
15
+ }): Promise<TaskDocMigrationResult>;
1
16
  export declare function cmdTaskMigrateDoc(opts: {
2
17
  cwd: string;
3
18
  rootOverride?: string;
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"migrate-doc.d.ts","sourceRoot":"","sources":["../../../src/commands/task/migrate-doc.ts"],"names":[],"mappings":"AAGA,OAAO,EAUL,cAAc,EAGd,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAanC,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AA+TF,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA4DlC;AAED,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,CAoBlB"}
@@ -1,12 +1,13 @@
1
1
  import { readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { atomicWriteFile, ensureDocSections, extractTaskDoc, loadConfig, mergeTaskDoc, normalizeTaskDoc, parseTaskReadme, renderTaskReadme, resolveProject, setMarkdownSection, } from "@agentplaneorg/core";
3
+ import { atomicWriteFile, ensureDocSections, extractTaskDoc, loadConfig, mergeTaskDoc, normalizeTaskDoc, parseTaskReadme, renderTaskDocFromSections, renderTaskReadme, resolveProject, setMarkdownSection, taskDocToSectionMap, } from "@agentplaneorg/core";
4
4
  import { mapCoreError } from "../../cli/error-map.js";
5
5
  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 { exportTaskProjectionSnapshot, loadCommandContext } from "../shared/task-backend.js";
9
+ import { execFileAsync, gitEnv } from "../shared/git.js";
10
+ import { exportTaskProjectionSnapshot, loadCommandContext, } from "../shared/task-backend.js";
10
11
  import { extractDocSection, extractTaskObservationSection, decodeEscapedTaskTextNewlines, normalizeTaskDocVersion, normalizeVerificationSectionLayout, } from "./shared/docs.js";
11
12
  import { defaultTaskDocV3 } from "./doc-template.js";
12
13
  const V3_CANONICAL_ORDER = [
@@ -22,6 +23,21 @@ const HUMAN_TEXT_SECTIONS = new Set(["summary", "context", "scope", "plan", "fin
22
23
  function isRecord(value) {
23
24
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
24
25
  }
26
+ function normalizeRevision(value) {
27
+ return Number.isInteger(value) && typeof value === "number" && value > 0 ? value : null;
28
+ }
29
+ function normalizeCanonicalSections(value) {
30
+ if (!isRecord(value))
31
+ return null;
32
+ const out = {};
33
+ for (const [title, text] of Object.entries(value)) {
34
+ const normalizedTitle = title.trim();
35
+ if (!normalizedTitle || typeof text !== "string")
36
+ continue;
37
+ out[normalizedTitle] = text.replaceAll("\r\n", "\n").trimEnd();
38
+ }
39
+ return Object.keys(out).length > 0 ? out : null;
40
+ }
25
41
  function normalizeSectionKey(section) {
26
42
  return section.trim().replaceAll(/\s+/g, " ").toLowerCase();
27
43
  }
@@ -177,9 +193,12 @@ async function migrateTaskReadmeDoc(opts) {
177
193
  ensurePlanApprovalFrontmatter(frontmatter);
178
194
  ensureVerificationFrontmatter(frontmatter);
179
195
  normalizeFrontmatterNoteTimestamps(frontmatter);
196
+ const canonicalSections = normalizeCanonicalSections(frontmatter.sections);
180
197
  const required = opts.config.tasks.doc.required_sections;
181
198
  const extracted = extractTaskDoc(parsed.body);
182
- const baseDoc = extracted || parsed.body;
199
+ const baseDoc = canonicalSections === null
200
+ ? extracted || parsed.body
201
+ : renderTaskDocFromSections(canonicalSections);
183
202
  let nextDoc = normalizeTaskDoc(ensureDocSections(baseDoc, required));
184
203
  const docVersion = normalizeTaskDocVersion(frontmatter.doc_version);
185
204
  if (docVersion === 2) {
@@ -202,6 +221,8 @@ async function migrateTaskReadmeDoc(opts) {
202
221
  }
203
222
  }
204
223
  const nextBody = extracted ? mergeTaskDoc(parsed.body, nextDoc) : nextDoc;
224
+ frontmatter.sections = taskDocToSectionMap(nextDoc);
225
+ frontmatter.revision = normalizeRevision(frontmatter.revision) ?? 1;
205
226
  const rendered = renderTaskReadme(frontmatter, nextBody);
206
227
  const next = rendered.endsWith("\n") ? rendered : `${rendered}\n`;
207
228
  if (next === original)
@@ -226,48 +247,118 @@ async function resolveReadmePaths(opts) {
226
247
  }
227
248
  return out;
228
249
  }
229
- export async function cmdTaskMigrateDoc(opts) {
230
- const params = { all: opts.all, quiet: opts.quiet, taskIds: opts.taskIds };
250
+ async function exportProjectionSnapshotIfChanged(opts) {
251
+ if (!(opts.ctx.taskBackend.exportProjectionSnapshot || opts.ctx.taskBackend.exportTasksJson)) {
252
+ return [];
253
+ }
254
+ const relOutputPath = opts.tasksPath.replaceAll("\\", "/");
255
+ const outputPath = path.join(opts.resolvedGitRoot, relOutputPath);
256
+ let before = null;
257
+ try {
258
+ before = await readFile(outputPath, "utf8");
259
+ }
260
+ catch {
261
+ before = null;
262
+ }
263
+ await exportTaskProjectionSnapshot({ ctx: opts.ctx, outputPath });
264
+ let after = null;
231
265
  try {
232
- const resolved = await resolveProject({
266
+ after = await readFile(outputPath, "utf8");
267
+ }
268
+ catch {
269
+ after = null;
270
+ }
271
+ if (before === after)
272
+ return [];
273
+ return (await canStageGitPath(opts.resolvedGitRoot, relOutputPath)) ? [relOutputPath] : [];
274
+ }
275
+ async function canStageGitPath(gitRoot, relPath) {
276
+ try {
277
+ await execFileAsync("git", ["ls-files", "--error-unmatch", "--", relPath], {
278
+ cwd: gitRoot,
279
+ env: gitEnv(),
280
+ });
281
+ return true;
282
+ }
283
+ catch {
284
+ // Continue below: untracked paths may still be stageable when they are not ignored.
285
+ }
286
+ try {
287
+ await execFileAsync("git", ["check-ignore", "--quiet", "--", relPath], {
288
+ cwd: gitRoot,
289
+ env: gitEnv(),
290
+ });
291
+ return false;
292
+ }
293
+ catch {
294
+ return true;
295
+ }
296
+ }
297
+ export async function migrateTaskDocsInWorkspace(opts) {
298
+ const resolved = opts.resolvedProject ??
299
+ (await resolveProject({
233
300
  cwd: opts.cwd,
234
301
  rootOverride: opts.rootOverride ?? null,
235
- });
236
- const loaded = await loadConfig(resolved.agentplaneDir);
237
- const ctx = await loadCommandContext({
302
+ }));
303
+ let config = opts.config;
304
+ if (!config) {
305
+ const loadedConfig = await loadConfig(resolved.agentplaneDir);
306
+ config = loadedConfig.config;
307
+ }
308
+ const ctx = opts.ctx ??
309
+ (await loadCommandContext({
238
310
  cwd: opts.cwd,
239
311
  rootOverride: opts.rootOverride ?? null,
240
312
  resolvedProject: resolved,
241
- config: loaded.config,
242
- });
243
- const tasksDir = path.join(resolved.gitRoot, loaded.config.paths.workflow_dir);
244
- const readmePaths = await resolveReadmePaths({ tasksDir, params });
245
- if (!params.all) {
246
- for (const readmePath of readmePaths) {
247
- if (!(await fileExists(readmePath))) {
248
- const taskId = path.basename(path.dirname(readmePath));
249
- throw new CliError({
250
- exitCode: exitCodeForError("E_IO"),
251
- code: "E_IO",
252
- message: `Task README not found: ${taskId}`,
253
- });
254
- }
255
- }
256
- }
257
- let changed = 0;
313
+ config,
314
+ }));
315
+ const params = { all: opts.all, quiet: false, taskIds: opts.taskIds };
316
+ const tasksDir = path.join(resolved.gitRoot, config.paths.workflow_dir);
317
+ const readmePaths = await resolveReadmePaths({ tasksDir, params });
318
+ if (!opts.all) {
258
319
  for (const readmePath of readmePaths) {
259
- const res = await migrateTaskReadmeDoc({ readmePath, config: loaded.config });
260
- if (res.changed)
261
- changed += 1;
320
+ if (!(await fileExists(readmePath))) {
321
+ const taskId = path.basename(path.dirname(readmePath));
322
+ throw new CliError({
323
+ exitCode: exitCodeForError("E_IO"),
324
+ code: "E_IO",
325
+ message: `Task README not found: ${taskId}`,
326
+ });
327
+ }
262
328
  }
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 });
329
+ }
330
+ let changed = 0;
331
+ const changedPaths = [];
332
+ for (const readmePath of readmePaths) {
333
+ const res = await migrateTaskReadmeDoc({ readmePath, config });
334
+ if (!res.changed)
335
+ continue;
336
+ changed += 1;
337
+ const relReadmePath = path.relative(resolved.gitRoot, readmePath).replaceAll("\\", "/");
338
+ if (await canStageGitPath(resolved.gitRoot, relReadmePath)) {
339
+ changedPaths.push(relReadmePath);
268
340
  }
341
+ }
342
+ if (changed > 0) {
343
+ changedPaths.push(...(await exportProjectionSnapshotIfChanged({
344
+ ctx,
345
+ resolvedGitRoot: resolved.gitRoot,
346
+ tasksPath: config.paths.tasks_path,
347
+ })));
348
+ }
349
+ return { changed, changedPaths: [...new Set(changedPaths)] };
350
+ }
351
+ export async function cmdTaskMigrateDoc(opts) {
352
+ const params = { all: opts.all, quiet: opts.quiet, taskIds: opts.taskIds };
353
+ try {
354
+ const result = await migrateTaskDocsInWorkspace({
355
+ cwd: opts.cwd,
356
+ rootOverride: opts.rootOverride ?? null,
357
+ all: params.all,
358
+ taskIds: params.taskIds,
359
+ });
269
360
  if (!params.quiet) {
270
- process.stdout.write(`${successMessage("migrated task docs", undefined, `changed=${changed}`)}\n`);
361
+ process.stdout.write(`${successMessage("migrated task docs", undefined, `changed=${result.changed}`)}\n`);
271
362
  }
272
363
  return 0;
273
364
  }
@@ -1 +1 @@
1
- {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAcpF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAmDF,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,aAAa,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgFlB"}
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAcpF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAmDF,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,aAAa,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmFlB"}
@@ -1,4 +1,4 @@
1
- import { setMarkdownSection } from "@agentplaneorg/core";
1
+ import { setMarkdownSection, taskDocToSectionMap } from "@agentplaneorg/core";
2
2
  import { mapBackendError } from "../../cli/error-map.js";
3
3
  import { backendNotSupportedMessage, warnMessage } from "../../cli/output.js";
4
4
  import { CliError } from "../../shared/errors.js";
@@ -73,6 +73,7 @@ export async function runTaskNewParsed(opts) {
73
73
  status: "TODO",
74
74
  priority: p.priority,
75
75
  owner: p.owner,
76
+ revision: 1,
76
77
  tags: p.tags,
77
78
  depends_on: p.dependsOn,
78
79
  verify: p.verify,
@@ -107,6 +108,7 @@ export async function runTaskNewParsed(opts) {
107
108
  if (hasSpike && hasImplementationTags) {
108
109
  process.stderr.write(`${warnMessage("spike is combined with a primary tag that requires verify steps; consider splitting spike vs implementation tasks")}\n`);
109
110
  }
111
+ task.sections = taskDocToSectionMap(task.doc ?? "");
110
112
  await ctx.taskBackend.writeTask(task);
111
113
  process.stdout.write(`${taskId}\n`);
112
114
  return 0;