agentplane 0.3.2 → 0.3.4

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 (205) hide show
  1. package/assets/AGENTS.md +4 -4
  2. package/assets/agents/CODER.json +14 -10
  3. package/assets/agents/CREATOR.json +8 -6
  4. package/assets/agents/DOCS.json +9 -6
  5. package/assets/agents/INTEGRATOR.json +9 -6
  6. package/assets/agents/ORCHESTRATOR.json +8 -6
  7. package/assets/agents/PLANNER.json +8 -5
  8. package/assets/agents/REDMINE.json +6 -3
  9. package/assets/agents/REVIEWER.json +8 -6
  10. package/assets/agents/TESTER.json +8 -4
  11. package/assets/agents/UPDATER.json +6 -4
  12. package/assets/agents/UPGRADER.json +7 -6
  13. package/assets/policy/dod.code.md +2 -2
  14. package/assets/policy/dod.core.md +16 -2
  15. package/assets/policy/dod.docs.md +2 -2
  16. package/assets/policy/incidents.md +44 -1
  17. package/assets/policy/workflow.direct.md +8 -4
  18. package/bin/agentplane.js +59 -9
  19. package/bin/dist-guard.js +78 -10
  20. package/bin/runtime-context.d.ts +3 -0
  21. package/bin/runtime-context.js +13 -0
  22. package/bin/runtime-watch.d.ts +26 -0
  23. package/bin/runtime-watch.js +116 -0
  24. package/bin/stale-dist-policy.d.ts +6 -0
  25. package/bin/stale-dist-policy.js +44 -0
  26. package/dist/.build-manifest.json +2485 -5
  27. package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
  28. package/dist/backends/task-backend/local-backend.js +9 -12
  29. package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
  30. package/dist/backends/task-backend/redmine-backend.js +23 -18
  31. package/dist/backends/task-backend/shared/constants.d.ts +1 -0
  32. package/dist/backends/task-backend/shared/constants.d.ts.map +1 -1
  33. package/dist/backends/task-backend/shared/constants.js +1 -0
  34. package/dist/backends/task-backend/shared/doc.d.ts +1 -0
  35. package/dist/backends/task-backend/shared/doc.d.ts.map +1 -1
  36. package/dist/backends/task-backend/shared/doc.js +4 -1
  37. package/dist/backends/task-backend/shared/export.js +3 -3
  38. package/dist/cli/bootstrap-guide.d.ts +2 -3
  39. package/dist/cli/bootstrap-guide.d.ts.map +1 -1
  40. package/dist/cli/bootstrap-guide.js +16 -35
  41. package/dist/cli/command-guide.d.ts +14 -1
  42. package/dist/cli/command-guide.d.ts.map +1 -1
  43. package/dist/cli/command-guide.js +71 -47
  44. package/dist/cli/run-cli/catalog.d.ts +7 -0
  45. package/dist/cli/run-cli/catalog.d.ts.map +1 -0
  46. package/dist/cli/run-cli/catalog.js +22 -0
  47. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  48. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  49. package/dist/cli/run-cli/command-catalog.js +11 -0
  50. package/dist/cli/run-cli/commands/core.d.ts.map +1 -1
  51. package/dist/cli/run-cli/commands/core.js +37 -29
  52. package/dist/cli/run-cli/commands/init/write-config.d.ts.map +1 -1
  53. package/dist/cli/run-cli/commands/init/write-config.js +2 -0
  54. package/dist/cli/run-cli/commands/init/write-workflow.d.ts.map +1 -1
  55. package/dist/cli/run-cli/commands/init/write-workflow.js +6 -55
  56. package/dist/cli/run-cli/commands/init.js +5 -14
  57. package/dist/cli/run-cli/error-guidance.d.ts +9 -0
  58. package/dist/cli/run-cli/error-guidance.d.ts.map +1 -0
  59. package/dist/cli/run-cli/error-guidance.js +180 -0
  60. package/dist/cli/run-cli/globals.d.ts +22 -0
  61. package/dist/cli/run-cli/globals.d.ts.map +1 -0
  62. package/dist/cli/run-cli/globals.js +197 -0
  63. package/dist/cli/run-cli/update-warning.d.ts +6 -0
  64. package/dist/cli/run-cli/update-warning.d.ts.map +1 -0
  65. package/dist/cli/run-cli/update-warning.js +64 -0
  66. package/dist/cli/run-cli.d.ts.map +1 -1
  67. package/dist/cli/run-cli.js +5 -476
  68. package/dist/cli/spec/docs-render.d.ts.map +1 -1
  69. package/dist/cli/spec/docs-render.js +14 -1
  70. package/dist/commands/doctor/archive.d.ts +4 -0
  71. package/dist/commands/doctor/archive.d.ts.map +1 -0
  72. package/dist/commands/doctor/archive.js +211 -0
  73. package/dist/commands/doctor/fixes.d.ts +9 -0
  74. package/dist/commands/doctor/fixes.d.ts.map +1 -0
  75. package/dist/commands/doctor/fixes.js +40 -0
  76. package/dist/commands/doctor/layering.d.ts +2 -0
  77. package/dist/commands/doctor/layering.d.ts.map +1 -0
  78. package/dist/commands/doctor/layering.js +87 -0
  79. package/dist/commands/doctor/runtime.d.ts +4 -0
  80. package/dist/commands/doctor/runtime.d.ts.map +1 -0
  81. package/dist/commands/doctor/runtime.js +56 -0
  82. package/dist/commands/doctor/workflow.d.ts +6 -0
  83. package/dist/commands/doctor/workflow.d.ts.map +1 -0
  84. package/dist/commands/doctor/workflow.js +62 -0
  85. package/dist/commands/doctor/workspace.d.ts +2 -0
  86. package/dist/commands/doctor/workspace.d.ts.map +1 -0
  87. package/dist/commands/doctor/workspace.js +165 -0
  88. package/dist/commands/doctor.run.d.ts.map +1 -1
  89. package/dist/commands/doctor.run.js +16 -342
  90. package/dist/commands/doctor.spec.d.ts +1 -0
  91. package/dist/commands/doctor.spec.d.ts.map +1 -1
  92. package/dist/commands/doctor.spec.js +15 -1
  93. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  94. package/dist/commands/guard/impl/commands.js +19 -0
  95. package/dist/commands/release/apply.command.d.ts +2 -8
  96. package/dist/commands/release/apply.command.d.ts.map +1 -1
  97. package/dist/commands/release/apply.command.js +158 -387
  98. package/dist/commands/release/apply.mutation.d.ts +7 -0
  99. package/dist/commands/release/apply.mutation.d.ts.map +1 -0
  100. package/dist/commands/release/apply.mutation.js +107 -0
  101. package/dist/commands/release/apply.preflight.d.ts +25 -0
  102. package/dist/commands/release/apply.preflight.d.ts.map +1 -0
  103. package/dist/commands/release/apply.preflight.js +338 -0
  104. package/dist/commands/release/apply.reporting.d.ts +4 -0
  105. package/dist/commands/release/apply.reporting.d.ts.map +1 -0
  106. package/dist/commands/release/apply.reporting.js +24 -0
  107. package/dist/commands/release/apply.types.d.ts +46 -0
  108. package/dist/commands/release/apply.types.d.ts.map +1 -0
  109. package/dist/commands/release/apply.types.js +1 -0
  110. package/dist/commands/runtime.command.d.ts +28 -0
  111. package/dist/commands/runtime.command.d.ts.map +1 -0
  112. package/dist/commands/runtime.command.js +169 -0
  113. package/dist/commands/shared/task-store.d.ts.map +1 -1
  114. package/dist/commands/shared/task-store.js +7 -3
  115. package/dist/commands/task/add.d.ts.map +1 -1
  116. package/dist/commands/task/add.js +3 -33
  117. package/dist/commands/task/block.d.ts.map +1 -1
  118. package/dist/commands/task/block.js +2 -2
  119. package/dist/commands/task/close-duplicate.d.ts.map +1 -1
  120. package/dist/commands/task/close-duplicate.js +2 -2
  121. package/dist/commands/task/close-noop.d.ts.map +1 -1
  122. package/dist/commands/task/close-noop.js +2 -2
  123. package/dist/commands/task/comment.js +2 -2
  124. package/dist/commands/task/derive.d.ts.map +1 -1
  125. package/dist/commands/task/derive.js +3 -3
  126. package/dist/commands/task/doc-template.d.ts +10 -0
  127. package/dist/commands/task/doc-template.d.ts.map +1 -0
  128. package/dist/commands/task/doc-template.js +104 -0
  129. package/dist/commands/task/doc.d.ts.map +1 -1
  130. package/dist/commands/task/doc.js +36 -1
  131. package/dist/commands/task/finish.d.ts.map +1 -1
  132. package/dist/commands/task/finish.js +7 -4
  133. package/dist/commands/task/migrate-doc.command.d.ts.map +1 -1
  134. package/dist/commands/task/migrate-doc.command.js +5 -1
  135. package/dist/commands/task/migrate-doc.d.ts.map +1 -1
  136. package/dist/commands/task/migrate-doc.js +136 -2
  137. package/dist/commands/task/new.d.ts.map +1 -1
  138. package/dist/commands/task/new.js +4 -110
  139. package/dist/commands/task/new.spec.js +3 -3
  140. package/dist/commands/task/plan.d.ts.map +1 -1
  141. package/dist/commands/task/plan.js +5 -4
  142. package/dist/commands/task/scaffold.d.ts.map +1 -1
  143. package/dist/commands/task/scaffold.js +7 -52
  144. package/dist/commands/task/set-status.d.ts.map +1 -1
  145. package/dist/commands/task/set-status.js +2 -2
  146. package/dist/commands/task/shared/dependencies.d.ts +15 -0
  147. package/dist/commands/task/shared/dependencies.d.ts.map +1 -0
  148. package/dist/commands/task/shared/dependencies.js +143 -0
  149. package/dist/commands/task/shared/docs.d.ts +21 -0
  150. package/dist/commands/task/shared/docs.d.ts.map +1 -0
  151. package/dist/commands/task/shared/docs.js +121 -0
  152. package/dist/commands/task/shared/listing.d.ts +20 -0
  153. package/dist/commands/task/shared/listing.d.ts.map +1 -0
  154. package/dist/commands/task/shared/listing.js +127 -0
  155. package/dist/commands/task/shared/tags.d.ts +24 -0
  156. package/dist/commands/task/shared/tags.d.ts.map +1 -0
  157. package/dist/commands/task/shared/tags.js +177 -0
  158. package/dist/commands/task/shared/transitions.d.ts +42 -0
  159. package/dist/commands/task/shared/transitions.d.ts.map +1 -0
  160. package/dist/commands/task/shared/transitions.js +175 -0
  161. package/dist/commands/task/shared.d.ts +5 -106
  162. package/dist/commands/task/shared.d.ts.map +1 -1
  163. package/dist/commands/task/shared.js +5 -681
  164. package/dist/commands/task/start.d.ts.map +1 -1
  165. package/dist/commands/task/start.js +7 -5
  166. package/dist/commands/task/verify-record.d.ts.map +1 -1
  167. package/dist/commands/task/verify-record.js +9 -25
  168. package/dist/commands/task/verify-show.command.d.ts.map +1 -1
  169. package/dist/commands/task/verify-show.command.js +5 -1
  170. package/dist/commands/upgrade/apply.d.ts +44 -0
  171. package/dist/commands/upgrade/apply.d.ts.map +1 -0
  172. package/dist/commands/upgrade/apply.js +180 -0
  173. package/dist/commands/upgrade/report.d.ts +21 -0
  174. package/dist/commands/upgrade/report.d.ts.map +1 -0
  175. package/dist/commands/upgrade/report.js +81 -0
  176. package/dist/commands/upgrade/source.d.ts +35 -0
  177. package/dist/commands/upgrade/source.d.ts.map +1 -0
  178. package/dist/commands/upgrade/source.js +109 -0
  179. package/dist/commands/upgrade/types.d.ts +31 -0
  180. package/dist/commands/upgrade/types.d.ts.map +1 -0
  181. package/dist/commands/upgrade/types.js +1 -0
  182. package/dist/commands/upgrade.d.ts +1 -35
  183. package/dist/commands/upgrade.d.ts.map +1 -1
  184. package/dist/commands/upgrade.js +68 -332
  185. package/dist/commands/workflow-build.command.d.ts.map +1 -1
  186. package/dist/commands/workflow-build.command.js +9 -15
  187. package/dist/shared/diagnostics.d.ts +23 -0
  188. package/dist/shared/diagnostics.d.ts.map +1 -0
  189. package/dist/shared/diagnostics.js +57 -0
  190. package/dist/shared/errors.d.ts +2 -0
  191. package/dist/shared/errors.d.ts.map +1 -1
  192. package/dist/shared/errors.js +2 -0
  193. package/dist/shared/repo-cli-version.d.ts +13 -0
  194. package/dist/shared/repo-cli-version.d.ts.map +1 -0
  195. package/dist/shared/repo-cli-version.js +63 -0
  196. package/dist/shared/runtime-source.d.ts +33 -0
  197. package/dist/shared/runtime-source.d.ts.map +1 -0
  198. package/dist/shared/runtime-source.js +156 -0
  199. package/dist/shared/version-compare.d.ts +7 -0
  200. package/dist/shared/version-compare.d.ts.map +1 -0
  201. package/dist/shared/version-compare.js +30 -0
  202. package/dist/shared/workflow-artifacts.d.ts +37 -0
  203. package/dist/shared/workflow-artifacts.d.ts.map +1 -0
  204. package/dist/shared/workflow-artifacts.js +97 -0
  205. package/package.json +2 -2
@@ -1,299 +1,168 @@
1
- import { readFile, writeFile, readdir, mkdir } from "node:fs/promises";
2
1
  import path from "node:path";
3
- import { resolveProject, loadConfig } from "@agentplaneorg/core";
4
- import { usageError } from "../../cli/spec/errors.js";
2
+ import { loadConfig, resolveProject } from "@agentplaneorg/core";
5
3
  import { exitCodeForError } from "../../cli/exit-codes.js";
4
+ import { usageError } from "../../cli/spec/errors.js";
5
+ import { withDiagnosticContext } from "../../shared/diagnostics.js";
6
6
  import { CliError } from "../../shared/errors.js";
7
7
  import { execFileAsync, gitEnv } from "../shared/git.js";
8
8
  import { GitContext } from "../shared/git-context.js";
9
9
  import { ensureNetworkApproved } from "../shared/network-approval.js";
10
- async function fileExists(p) {
11
- try {
12
- await readFile(p, "utf8");
13
- return true;
14
- }
15
- catch {
16
- return false;
17
- }
18
- }
19
- async function readJsonFile(p) {
20
- return JSON.parse(await readFile(p, "utf8"));
21
- }
22
- function assertNonEmptyString(value, label) {
23
- if (typeof value !== "string" || !value.trim()) {
24
- throw new CliError({
25
- exitCode: exitCodeForError("E_VALIDATION"),
26
- code: "E_VALIDATION",
27
- message: `Invalid ${label} (expected non-empty string).`,
28
- });
29
- }
30
- return value.trim();
31
- }
32
- function parseVersionPlan(raw) {
33
- if (!raw || typeof raw !== "object") {
34
- throw new CliError({
35
- exitCode: exitCodeForError("E_VALIDATION"),
36
- code: "E_VALIDATION",
37
- message: "Invalid version.json (expected object).",
38
- });
39
- }
40
- const obj = raw;
41
- const bumpRaw = assertNonEmptyString(obj.bump, "bump");
42
- if (bumpRaw !== "patch" && bumpRaw !== "minor" && bumpRaw !== "major") {
10
+ import { cleanHookEnv, maybePersistExpectedCliVersion, maybeRefreshGeneratedReference, maybeUpdateBunLockfile, replaceAgentplanePackageMetadata, replacePackageVersionInFile, } from "./apply.mutation.js";
11
+ import { ensureCleanTrackedTree, ensureNpmVersionsAvailable, ensureRemoteExists, ensureRemoteTagDoesNotExist, ensureTagDoesNotExist, fileExists, loadReleasePlan, readCoreDependencyVersion, readPackageVersion, runReleasePrepublishGate, validateReleaseNotes, } from "./apply.preflight.js";
12
+ import { pushReleaseRefs, writeReleaseApplyReport } from "./apply.reporting.js";
13
+ async function resolveReleasePlanInputs(opts) {
14
+ const { planDir, plan, minBullets } = await loadReleasePlan({
15
+ gitRoot: opts.gitRoot,
16
+ planOverride: opts.planOverride,
17
+ });
18
+ if (!/^v\d+\.\d+\.\d+$/u.test(plan.nextTag)) {
43
19
  throw new CliError({
44
20
  exitCode: exitCodeForError("E_VALIDATION"),
45
21
  code: "E_VALIDATION",
46
- message: `Invalid bump in version.json: ${bumpRaw}`,
22
+ message: `Invalid nextTag in version.json (expected vX.Y.Z): ${plan.nextTag}`,
47
23
  });
48
24
  }
49
- const prevTagVal = obj.prevTag;
50
- const prevTag = prevTagVal === null ? null : typeof prevTagVal === "string" ? prevTagVal : null;
51
- const prevVersion = assertNonEmptyString(obj.prevVersion, "prevVersion");
52
- const nextTag = assertNonEmptyString(obj.nextTag, "nextTag");
53
- const nextVersion = assertNonEmptyString(obj.nextVersion, "nextVersion");
54
- return { prevTag, prevVersion, nextTag, nextVersion, bump: bumpRaw };
55
- }
56
- async function findLatestPlanDir(gitRoot) {
57
- const base = path.join(gitRoot, ".agentplane", ".release", "plan");
58
- const runNames = await readdir(base);
59
- const runs = runNames
60
- .map((s) => s.trim())
61
- .filter(Boolean)
62
- .toSorted();
63
- const latest = runs.at(-1);
64
- if (!latest) {
25
+ const notesPath = path.join(opts.gitRoot, "docs", "releases", `${plan.nextTag}.md`);
26
+ if (!(await fileExists(notesPath))) {
65
27
  throw new CliError({
66
28
  exitCode: exitCodeForError("E_IO"),
67
29
  code: "E_IO",
68
- message: "No release plan runs found under .agentplane/.release/plan/. Run `agentplane release plan` first.",
69
- });
70
- }
71
- return path.join(base, latest);
72
- }
73
- async function readPackageVersion(pkgJsonPath) {
74
- const raw = JSON.parse(await readFile(pkgJsonPath, "utf8"));
75
- const version = typeof raw.version === "string" ? raw.version.trim() : "";
76
- if (!version) {
77
- throw new CliError({
78
- exitCode: exitCodeForError("E_VALIDATION"),
79
- code: "E_VALIDATION",
80
- message: `Missing package.json version: ${pkgJsonPath}`,
81
- });
82
- }
83
- return version;
84
- }
85
- async function readCoreDependencyVersion(pkgJsonPath) {
86
- const raw = JSON.parse(await readFile(pkgJsonPath, "utf8"));
87
- const value = raw.dependencies?.["@agentplaneorg/core"];
88
- const version = typeof value === "string" ? value.trim() : "";
89
- if (!version) {
90
- throw new CliError({
91
- exitCode: exitCodeForError("E_VALIDATION"),
92
- code: "E_VALIDATION",
93
- message: `Missing dependency @agentplaneorg/core in ${pkgJsonPath}. ` +
94
- "Release parity requires packages/agentplane to pin @agentplaneorg/core to the same version.",
30
+ message: `Missing release notes: ${path.relative(opts.gitRoot, notesPath)}\n` +
31
+ "Write this file using a DOCS agent before applying the release.",
95
32
  });
96
33
  }
97
- return version;
34
+ await validateReleaseNotes(notesPath, minBullets);
35
+ return { planDir, plan, notesPath, minBullets };
98
36
  }
99
- async function replacePackageVersionInFile(pkgJsonPath, nextVersion) {
100
- const text = await readFile(pkgJsonPath, "utf8");
101
- const replaced = text.replace(/"version"\s*:\s*"[^"]*"/u, `"version": "${nextVersion}"`);
102
- if (replaced === text) {
37
+ async function ensureReleasePlanMatchesRepoState(opts) {
38
+ const [coreVersion, agentplaneVersion, coreDependencyVersion] = await Promise.all([
39
+ readPackageVersion(opts.corePkgPath),
40
+ readPackageVersion(opts.agentplanePkgPath),
41
+ readCoreDependencyVersion(opts.agentplanePkgPath),
42
+ ]);
43
+ if (coreVersion !== agentplaneVersion) {
103
44
  throw new CliError({
104
45
  exitCode: exitCodeForError("E_VALIDATION"),
105
46
  code: "E_VALIDATION",
106
- message: `Failed to update version in ${pkgJsonPath} (missing "version" field).`,
47
+ message: `Package versions must match before applying a release. ` +
48
+ `packages/core=${coreVersion} packages/agentplane=${agentplaneVersion}`,
107
49
  });
108
50
  }
109
- await writeFile(pkgJsonPath, replaced, "utf8");
110
- }
111
- async function replaceAgentplanePackageMetadata(pkgJsonPath, nextVersion) {
112
- const text = await readFile(pkgJsonPath, "utf8");
113
- const withVersion = text.replace(/"version"\s*:\s*"[^"]*"/u, `"version": "${nextVersion}"`);
114
- if (withVersion === text) {
51
+ if (coreDependencyVersion !== coreVersion) {
115
52
  throw new CliError({
116
53
  exitCode: exitCodeForError("E_VALIDATION"),
117
54
  code: "E_VALIDATION",
118
- message: `Failed to update version in ${pkgJsonPath} (missing "version" field).`,
55
+ message: "Release dependency parity check failed before apply. " +
56
+ `packages/agentplane dependency @agentplaneorg/core=${coreDependencyVersion} ` +
57
+ `must match packages/core version ${coreVersion}.`,
119
58
  });
120
59
  }
121
- const withDependency = withVersion.replace(/("@agentplaneorg\/core"\s*:\s*")[^"]*(")/u, `$1${nextVersion}$2`);
122
- if (withDependency === withVersion) {
60
+ await ensureCleanTrackedTree(opts.gitRoot);
61
+ await ensureTagDoesNotExist(opts.gitRoot, opts.plan.nextTag);
62
+ if (coreVersion !== opts.plan.prevVersion) {
123
63
  throw new CliError({
124
64
  exitCode: exitCodeForError("E_VALIDATION"),
125
65
  code: "E_VALIDATION",
126
- message: `Failed to update @agentplaneorg/core dependency in ${pkgJsonPath}. ` +
127
- "Ensure packages/agentplane/package.json declares this dependency.",
66
+ message: `Current version does not match the release-plan baseline. ` +
67
+ `current=${coreVersion} expected_prev=${opts.plan.prevVersion} expected_next=${opts.plan.nextVersion}\n` +
68
+ "Re-run `agentplane release plan` to generate a fresh plan for this repo state.",
69
+ context: withDiagnosticContext({ command: "release apply" }, {
70
+ state: "the repository version no longer matches the prepared release-plan baseline",
71
+ likelyCause: "package versions changed after the plan was generated, so continuing would apply the release over a partially drifted local state",
72
+ nextAction: {
73
+ command: "agentplane release plan",
74
+ reason: "generate a fresh release plan from the current repository state before applying the release",
75
+ reasonCode: "release_plan_drifted",
76
+ },
77
+ }),
128
78
  });
129
79
  }
130
- await writeFile(pkgJsonPath, withDependency, "utf8");
131
80
  }
132
- function cleanHookEnv() {
133
- const env = { ...gitEnv() };
134
- delete env.AGENTPLANE_TASK_ID;
135
- delete env.AGENTPLANE_STATUS_TO;
136
- delete env.AGENTPLANE_AGENT_ID;
137
- return env;
81
+ async function runPushPreflight(opts) {
82
+ const loaded = await loadConfig(opts.agentplaneDir);
83
+ await ensureNetworkApproved({
84
+ config: loaded.config,
85
+ yes: opts.yes,
86
+ reason: "release apply --push validates npm version availability and pushes over network",
87
+ interactive: Boolean(process.stdin.isTTY),
88
+ });
89
+ await ensureRemoteExists(opts.gitRoot, opts.remote);
90
+ await ensureRemoteTagDoesNotExist(opts.gitRoot, opts.remote, opts.nextTag);
91
+ await ensureNpmVersionsAvailable(opts.gitRoot, opts.nextVersion);
92
+ await runReleasePrepublishGate(opts.gitRoot);
93
+ return true;
138
94
  }
139
- async function validateReleaseNotes(notesPath, minBullets) {
140
- const content = await readFile(notesPath, "utf8");
141
- if (!/release\s+notes/i.test(content)) {
142
- throw new CliError({
143
- exitCode: exitCodeForError("E_VALIDATION"),
144
- code: "E_VALIDATION",
145
- message: `Release notes must include a "Release Notes" heading in ${notesPath}.`,
146
- });
147
- }
148
- const bulletCount = content.split(/\r?\n/u).filter((line) => /^\s*[-*]\s+\S+/u.test(line)).length;
149
- if (bulletCount < minBullets) {
150
- throw new CliError({
151
- exitCode: exitCodeForError("E_VALIDATION"),
152
- code: "E_VALIDATION",
153
- message: `Release notes must include at least ${minBullets} bullet points in ${notesPath}.`,
154
- });
95
+ async function applyReleaseMutation(opts) {
96
+ let releaseCommit = null;
97
+ await Promise.all([
98
+ replacePackageVersionInFile(opts.corePkgPath, opts.nextVersion),
99
+ replaceAgentplanePackageMetadata(opts.agentplanePkgPath, opts.nextVersion),
100
+ ]);
101
+ const expectedCliVersionPersisted = await maybePersistExpectedCliVersion(opts.agentplaneDir, opts.nextVersion);
102
+ await maybeUpdateBunLockfile(opts.gitRoot, fileExists);
103
+ const generatedReferenceExists = await maybeRefreshGeneratedReference(opts.gitRoot, fileExists);
104
+ const stagePaths = [
105
+ "packages/core/package.json",
106
+ "packages/agentplane/package.json",
107
+ path.relative(opts.gitRoot, opts.notesPath),
108
+ ];
109
+ if (expectedCliVersionPersisted) {
110
+ stagePaths.push(".agentplane/config.json");
155
111
  }
156
- if (/[\u0400-\u04FF]/u.test(content)) {
157
- throw new CliError({
158
- exitCode: exitCodeForError("E_VALIDATION"),
159
- code: "E_VALIDATION",
160
- message: `Release notes must be written in English (no Cyrillic) in ${notesPath}.`,
161
- });
112
+ if (generatedReferenceExists) {
113
+ stagePaths.push("docs/reference/generated-reference.mdx");
162
114
  }
163
- }
164
- async function maybeUpdateBunLockfile(gitRoot) {
165
- // GitHub publish uses `bun install --frozen-lockfile`, which will fail if the lockfile
166
- // needs regeneration after bumping workspace package versions.
167
- const bunLockPath = path.join(gitRoot, "bun.lock");
168
- const rootPkgPath = path.join(gitRoot, "package.json");
169
- if (!(await fileExists(bunLockPath)))
170
- return;
171
- if (!(await fileExists(rootPkgPath)))
172
- return;
173
- try {
174
- await execFileAsync("bun", ["install", "--ignore-scripts"], {
175
- cwd: gitRoot,
176
- env: process.env,
177
- maxBuffer: 50 * 1024 * 1024,
178
- });
115
+ if (await fileExists(path.join(opts.gitRoot, "bun.lock"))) {
116
+ stagePaths.push("bun.lock");
179
117
  }
180
- catch (err) {
181
- const e = err;
182
- throw new CliError({
183
- exitCode: exitCodeForError("E_IO"),
184
- code: "E_IO",
185
- message: "Failed to update bun.lock via `bun install --ignore-scripts`.\n" +
186
- "Fix:\n" +
187
- " 1) Run `bun install --ignore-scripts` manually\n" +
188
- " 2) Re-run `agentplane release apply`\n" +
189
- (e?.message ? `\nDetails:\n${e.message}` : ""),
190
- });
118
+ await opts.git.stage(stagePaths);
119
+ const staged = await opts.git.statusStagedPaths();
120
+ if (staged.length === 0) {
121
+ process.stdout.write("No changes to commit.\n");
122
+ return { releaseCommit };
191
123
  }
192
- }
193
- async function ensureCleanTrackedTree(gitRoot) {
194
- const { stdout } = await execFileAsync("git", ["status", "--short", "--untracked-files=no"], {
195
- cwd: gitRoot,
124
+ const subject = `✨ release: ${opts.nextTag}`;
125
+ await opts.git.commit({ message: subject, env: cleanHookEnv() });
126
+ const { stdout: headHash } = await execFileAsync("git", ["rev-parse", "HEAD"], {
127
+ cwd: opts.gitRoot,
196
128
  env: gitEnv(),
197
- maxBuffer: 10 * 1024 * 1024,
198
- });
199
- const dirty = String(stdout ?? "")
200
- .split(/\r?\n/u)
201
- .map((line) => line.trimEnd())
202
- .filter((line) => line.length > 0);
203
- if (dirty.length === 0)
204
- return;
205
- throw new CliError({
206
- exitCode: exitCodeForError("E_GIT"),
207
- code: "E_GIT",
208
- message: "Release apply requires a clean tracked working tree.\n" +
209
- `Found tracked changes:\n${dirty.map((line) => ` ${line}`).join("\n")}`,
210
129
  });
130
+ releaseCommit = { hash: String(headHash ?? "").trim(), subject };
131
+ return { releaseCommit };
211
132
  }
212
- async function ensureTagDoesNotExist(gitRoot, tag) {
213
- try {
214
- await execFileAsync("git", ["rev-parse", "-q", "--verify", `refs/tags/${tag}`], {
215
- cwd: gitRoot,
216
- env: gitEnv(),
217
- });
218
- throw new CliError({
219
- exitCode: exitCodeForError("E_GIT"),
220
- code: "E_GIT",
221
- message: `Tag already exists: ${tag}`,
222
- });
223
- }
224
- catch (err) {
225
- const code = err?.code;
226
- if (code !== 1)
227
- throw err;
228
- }
229
- }
230
- async function ensureNpmVersionsAvailable(gitRoot, version) {
231
- const scriptPath = path.join(gitRoot, "scripts", "check-npm-version-availability.mjs");
232
- try {
233
- await execFileAsync("node", [scriptPath, "--version", version], {
234
- cwd: gitRoot,
235
- env: process.env,
236
- maxBuffer: 10 * 1024 * 1024,
237
- });
238
- }
239
- catch (err) {
240
- const details = String(err?.stderr ?? "").trim();
241
- throw new CliError({
242
- exitCode: exitCodeForError("E_VALIDATION"),
243
- code: "E_VALIDATION",
244
- message: `Pre-publish npm check failed for version ${version}. ` +
245
- "Ensure this version is not already published for @agentplaneorg/core and agentplane." +
246
- (details ? `\n\n${details}` : ""),
247
- });
248
- }
249
- }
250
- async function runReleasePrepublishGate(gitRoot) {
251
- try {
252
- await execFileAsync("bun", ["run", "release:prepublish"], {
253
- cwd: gitRoot,
254
- env: {
255
- ...process.env,
256
- GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? "agentplane-release",
257
- GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? "agentplane-release@example.com",
258
- GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? "agentplane-release",
259
- GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? "agentplane-release@example.com",
260
- },
261
- maxBuffer: 200 * 1024 * 1024,
262
- });
263
- }
264
- catch (err) {
265
- const details = String(err?.stderr ??
266
- err?.stdout ??
267
- err?.message ??
268
- "").trim();
269
- throw new CliError({
270
- exitCode: exitCodeForError("E_VALIDATION"),
271
- code: "E_VALIDATION",
272
- message: "Release prepublish gate failed. `agentplane release apply --push` requires a successful local `bun run release:prepublish` run before pushing the release tag." +
273
- (details ? `\n\n${details}` : ""),
274
- });
275
- }
276
- }
277
- async function writeReleaseApplyReport(gitRoot, report) {
278
- const runId = new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
279
- const dir = path.join(gitRoot, ".agentplane", ".release", "apply");
280
- await mkdir(dir, { recursive: true });
281
- const reportPath = path.join(dir, `${runId}.json`);
282
- const latestPath = path.join(dir, "latest.json");
283
- const text = `${JSON.stringify(report, null, 2)}\n`;
284
- await writeFile(reportPath, text, "utf8");
285
- await writeFile(latestPath, text, "utf8");
286
- return reportPath;
287
- }
288
- export async function pushReleaseRefs(gitRoot, remote, tag) {
289
- await execFileAsync("git", ["push", "--no-verify", remote, "HEAD"], {
290
- cwd: gitRoot,
133
+ async function finalizeReleaseApply(opts) {
134
+ await execFileAsync("git", ["tag", opts.plan.nextTag], {
135
+ cwd: opts.gitRoot,
291
136
  env: gitEnv(),
292
137
  });
293
- await execFileAsync("git", ["push", "--no-verify", remote, tag], {
294
- cwd: gitRoot,
295
- env: gitEnv(),
138
+ process.stdout.write(`Release tag created: ${opts.plan.nextTag}\n`);
139
+ if (opts.push) {
140
+ await pushReleaseRefs(opts.gitRoot, opts.remote, opts.plan.nextTag);
141
+ process.stdout.write(`Pushed: ${opts.remote} HEAD + ${opts.plan.nextTag}\n`);
142
+ }
143
+ else {
144
+ process.stdout.write(`Next: git push <remote> HEAD && git push <remote> ${opts.plan.nextTag}\n`);
145
+ }
146
+ const reportPath = await writeReleaseApplyReport(opts.gitRoot, {
147
+ applied_at: new Date().toISOString(),
148
+ plan_dir: path.relative(opts.gitRoot, opts.planDir),
149
+ notes_path: path.relative(opts.gitRoot, opts.notesPath),
150
+ prev_version: opts.plan.prevVersion,
151
+ next_version: opts.plan.nextVersion,
152
+ prev_tag: opts.plan.prevTag,
153
+ next_tag: opts.plan.nextTag,
154
+ bump: opts.plan.bump,
155
+ checks: {
156
+ clean_tracked_tree: true,
157
+ tag_absent: true,
158
+ notes_validated: true,
159
+ npm_version_available_checked: opts.npmVersionChecked,
160
+ },
161
+ commit: opts.releaseCommit,
162
+ push: { requested: opts.push, remote: opts.remote, performed: opts.push },
296
163
  });
164
+ process.stdout.write(`Release report: ${path.relative(opts.gitRoot, reportPath)}\n`);
165
+ return 0;
297
166
  }
298
167
  export const releaseApplySpec = {
299
168
  id: ["release", "apply"],
@@ -383,21 +252,10 @@ export const runReleaseApply = async (ctx, flags) => {
383
252
  }
384
253
  const resolved = await resolveProject({ cwd: ctx.cwd, rootOverride: ctx.rootOverride ?? null });
385
254
  const gitRoot = resolved.gitRoot;
386
- const planDir = flags.plan ? path.resolve(gitRoot, flags.plan) : await findLatestPlanDir(gitRoot);
387
- const versionJsonPath = path.join(planDir, "version.json");
388
- if (!(await fileExists(versionJsonPath))) {
389
- throw new CliError({
390
- exitCode: exitCodeForError("E_IO"),
391
- code: "E_IO",
392
- message: `Missing version.json in plan dir: ${path.relative(gitRoot, versionJsonPath)}`,
393
- });
394
- }
395
- const plan = parseVersionPlan(await readJsonFile(versionJsonPath));
396
- const changesJsonPath = path.join(planDir, "changes.json");
397
- const changes = (await fileExists(changesJsonPath))
398
- ? await readJsonFile(changesJsonPath)
399
- : [];
400
- const minBullets = Math.max(1, Array.isArray(changes) ? changes.length : 0);
255
+ const { planDir, plan, notesPath } = await resolveReleasePlanInputs({
256
+ gitRoot,
257
+ planOverride: flags.plan,
258
+ });
401
259
  if ((plan.bump === "minor" || plan.bump === "major") && flags.yes !== true) {
402
260
  throw usageError({
403
261
  spec: releaseApplySpec,
@@ -405,132 +263,45 @@ export const runReleaseApply = async (ctx, flags) => {
405
263
  message: `Bump '${plan.bump}' requires explicit approval. Re-run with --yes.`,
406
264
  });
407
265
  }
408
- if (!/^v\d+\.\d+\.\d+$/u.test(plan.nextTag)) {
409
- throw new CliError({
410
- exitCode: exitCodeForError("E_VALIDATION"),
411
- code: "E_VALIDATION",
412
- message: `Invalid nextTag in version.json (expected vX.Y.Z): ${plan.nextTag}`,
413
- });
414
- }
415
- const notesPath = path.join(gitRoot, "docs", "releases", `${plan.nextTag}.md`);
416
- if (!(await fileExists(notesPath))) {
417
- throw new CliError({
418
- exitCode: exitCodeForError("E_IO"),
419
- code: "E_IO",
420
- message: `Missing release notes: ${path.relative(gitRoot, notesPath)}\n` +
421
- "Write this file using a DOCS agent before applying the release.",
422
- });
423
- }
424
- await validateReleaseNotes(notesPath, minBullets);
425
266
  const corePkgPath = path.join(gitRoot, "packages", "core", "package.json");
426
267
  const agentplanePkgPath = path.join(gitRoot, "packages", "agentplane", "package.json");
427
- const [coreVersion, agentplaneVersion, coreDependencyVersion] = await Promise.all([
428
- readPackageVersion(corePkgPath),
429
- readPackageVersion(agentplanePkgPath),
430
- readCoreDependencyVersion(agentplanePkgPath),
431
- ]);
432
- if (coreVersion !== agentplaneVersion) {
433
- throw new CliError({
434
- exitCode: exitCodeForError("E_VALIDATION"),
435
- code: "E_VALIDATION",
436
- message: `Package versions must match before applying a release. ` +
437
- `packages/core=${coreVersion} packages/agentplane=${agentplaneVersion}`,
438
- });
439
- }
440
- if (coreDependencyVersion !== coreVersion) {
441
- throw new CliError({
442
- exitCode: exitCodeForError("E_VALIDATION"),
443
- code: "E_VALIDATION",
444
- message: "Release dependency parity check failed before apply. " +
445
- `packages/agentplane dependency @agentplaneorg/core=${coreDependencyVersion} ` +
446
- `must match packages/core version ${coreVersion}.`,
447
- });
448
- }
449
- const git = new GitContext({ gitRoot });
450
- await ensureCleanTrackedTree(gitRoot);
451
- await ensureTagDoesNotExist(gitRoot, plan.nextTag);
268
+ await ensureReleasePlanMatchesRepoState({
269
+ gitRoot,
270
+ plan,
271
+ corePkgPath,
272
+ agentplanePkgPath,
273
+ });
452
274
  let npmVersionChecked = false;
453
275
  if (flags.push) {
454
- const loaded = await loadConfig(resolved.agentplaneDir);
455
- await ensureNetworkApproved({
456
- config: loaded.config,
276
+ npmVersionChecked = await runPushPreflight({
277
+ agentplaneDir: resolved.agentplaneDir,
278
+ gitRoot,
279
+ remote: flags.remote,
280
+ nextTag: plan.nextTag,
281
+ nextVersion: plan.nextVersion,
457
282
  yes: flags.yes,
458
- reason: "release apply --push validates npm version availability and pushes over network",
459
- interactive: Boolean(process.stdin.isTTY),
460
283
  });
461
- await runReleasePrepublishGate(gitRoot);
462
- await ensureNpmVersionsAvailable(gitRoot, plan.nextVersion);
463
- npmVersionChecked = true;
464
284
  }
465
- let releaseCommit = null;
466
- if (coreVersion === plan.prevVersion) {
467
- await Promise.all([
468
- replacePackageVersionInFile(corePkgPath, plan.nextVersion),
469
- replaceAgentplanePackageMetadata(agentplanePkgPath, plan.nextVersion),
470
- ]);
471
- }
472
- else if (coreVersion === plan.nextVersion) {
473
- await replaceAgentplanePackageMetadata(agentplanePkgPath, plan.nextVersion);
474
- }
475
- else {
476
- throw new CliError({
477
- exitCode: exitCodeForError("E_VALIDATION"),
478
- code: "E_VALIDATION",
479
- message: `Current version does not match plan. ` +
480
- `current=${coreVersion} expected_prev=${plan.prevVersion} expected_next=${plan.nextVersion}\n` +
481
- "Re-run `agentplane release plan` to generate a fresh plan for this repo state.",
482
- });
483
- }
484
- await maybeUpdateBunLockfile(gitRoot);
485
- const stagePaths = [
486
- "packages/core/package.json",
487
- "packages/agentplane/package.json",
488
- path.relative(gitRoot, notesPath),
489
- ];
490
- if (await fileExists(path.join(gitRoot, "bun.lock"))) {
491
- stagePaths.push("bun.lock");
492
- }
493
- await git.stage(stagePaths);
494
- const staged = await git.statusStagedPaths();
495
- if (staged.length === 0) {
496
- process.stdout.write("No changes to commit.\n");
497
- }
498
- else {
499
- const subject = `✨ release: ${plan.nextTag}`;
500
- await git.commit({ message: subject, env: cleanHookEnv() });
501
- const { stdout: headHash } = await execFileAsync("git", ["rev-parse", "HEAD"], {
502
- cwd: gitRoot,
503
- env: gitEnv(),
504
- });
505
- releaseCommit = { hash: String(headHash ?? "").trim(), subject };
506
- }
507
- await execFileAsync("git", ["tag", plan.nextTag], { cwd: gitRoot, env: gitEnv() });
508
- process.stdout.write(`Release tag created: ${plan.nextTag}\n`);
509
- if (flags.push) {
510
- await pushReleaseRefs(gitRoot, flags.remote, plan.nextTag);
511
- process.stdout.write(`Pushed: ${flags.remote} HEAD + ${plan.nextTag}\n`);
512
- }
513
- else {
514
- process.stdout.write(`Next: git push <remote> HEAD && git push <remote> ${plan.nextTag}\n`);
515
- }
516
- const reportPath = await writeReleaseApplyReport(gitRoot, {
517
- applied_at: new Date().toISOString(),
518
- plan_dir: path.relative(gitRoot, planDir),
519
- notes_path: path.relative(gitRoot, notesPath),
520
- prev_version: plan.prevVersion,
521
- next_version: plan.nextVersion,
522
- prev_tag: plan.prevTag,
523
- next_tag: plan.nextTag,
524
- bump: plan.bump,
525
- checks: {
526
- clean_tracked_tree: true,
527
- tag_absent: true,
528
- notes_validated: true,
529
- npm_version_available_checked: npmVersionChecked,
530
- },
531
- commit: releaseCommit,
532
- push: { requested: flags.push, remote: flags.remote, performed: flags.push },
285
+ const git = new GitContext({ gitRoot });
286
+ const { releaseCommit } = await applyReleaseMutation({
287
+ agentplaneDir: resolved.agentplaneDir,
288
+ gitRoot,
289
+ git,
290
+ notesPath,
291
+ corePkgPath,
292
+ agentplanePkgPath,
293
+ nextTag: plan.nextTag,
294
+ nextVersion: plan.nextVersion,
295
+ });
296
+ return await finalizeReleaseApply({
297
+ gitRoot,
298
+ planDir,
299
+ notesPath,
300
+ plan,
301
+ npmVersionChecked,
302
+ releaseCommit,
303
+ push: flags.push,
304
+ remote: flags.remote,
533
305
  });
534
- process.stdout.write(`Release report: ${path.relative(gitRoot, reportPath)}\n`);
535
- return 0;
536
306
  };
307
+ export { pushReleaseRefs } from "./apply.reporting.js";
@@ -0,0 +1,7 @@
1
+ export declare function replacePackageVersionInFile(pkgJsonPath: string, nextVersion: string): Promise<void>;
2
+ export declare function replaceAgentplanePackageMetadata(pkgJsonPath: string, nextVersion: string): Promise<void>;
3
+ export declare function maybeUpdateBunLockfile(gitRoot: string, fileExists: (p: string) => Promise<boolean>): Promise<void>;
4
+ export declare function maybeRefreshGeneratedReference(gitRoot: string, fileExists: (p: string) => Promise<boolean>): Promise<boolean>;
5
+ export declare function maybePersistExpectedCliVersion(agentplaneDir: string, nextVersion: string): Promise<boolean>;
6
+ export declare function cleanHookEnv(): NodeJS.ProcessEnv;
7
+ //# sourceMappingURL=apply.mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.mutation.d.ts","sourceRoot":"","sources":["../../../src/commands/release/apply.mutation.ts"],"names":[],"mappings":"AASA,wBAAsB,2BAA2B,CAC/C,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,gCAAgC,CACpD,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAC1C,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED,wBAAsB,8BAA8B,CAClD,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED,wBAAgB,YAAY,IAAI,MAAM,CAAC,UAAU,CAMhD"}