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
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { parseTaskReadme, renderTaskDocFromSections } from "@agentplaneorg/core";
4
5
  import { renderDiagnosticFinding } from "../../shared/diagnostics.js";
5
6
  import { resolvePolicyGatewayForRepo } from "../../shared/policy-gateway.js";
6
7
  import { GitContext } from "../shared/git-context.js";
@@ -84,7 +85,7 @@ async function readTaskDocSnapshotsFromProjection(ctx) {
84
85
  return null;
85
86
  }
86
87
  }
87
- function buildTaskReadmeMigrationFindings(tasks) {
88
+ export function buildTaskReadmeMigrationFindings(tasks) {
88
89
  if (tasks.length === 0)
89
90
  return [];
90
91
  const legacy = tasks.filter((task) => task.doc_version !== 3);
@@ -138,7 +139,7 @@ function buildTaskReadmeMigrationFindings(tasks) {
138
139
  }),
139
140
  ];
140
141
  }
141
- async function checkTaskReadmeMigrationState(repoRoot, ctx) {
142
+ export async function checkTaskReadmeMigrationState(repoRoot, ctx) {
142
143
  const projectionTasks = await readTaskDocSnapshotsFromProjection(ctx);
143
144
  const tasks = projectionTasks && projectionTasks.length > 0
144
145
  ? projectionTasks
@@ -197,6 +198,129 @@ async function checkDoneTaskReadmeArchiveDrift(repoRoot, ctx) {
197
198
  }),
198
199
  ];
199
200
  }
201
+ function normalizeTaskBodyForComparison(text) {
202
+ return text.replaceAll("\r\n", "\n").trim();
203
+ }
204
+ function normalizeCanonicalSections(value) {
205
+ if (!value || typeof value !== "object" || Array.isArray(value))
206
+ return null;
207
+ const sections = {};
208
+ for (const [key, entry] of Object.entries(value)) {
209
+ const title = key.trim();
210
+ if (!title || typeof entry !== "string")
211
+ continue;
212
+ sections[title] = entry;
213
+ }
214
+ return Object.keys(sections).length > 0 ? sections : null;
215
+ }
216
+ async function checkTaskProjectionDrift(repoRoot, ctx) {
217
+ const workflowDir = path.join(repoRoot, ctx?.config.paths.workflow_dir ?? ".agentplane/tasks");
218
+ let entries;
219
+ try {
220
+ entries = await fs.readdir(workflowDir, { withFileTypes: true });
221
+ }
222
+ catch {
223
+ return [];
224
+ }
225
+ const drifted = [];
226
+ for (const entry of entries) {
227
+ if (!entry.isDirectory())
228
+ continue;
229
+ const readmePath = path.join(workflowDir, entry.name, "README.md");
230
+ let text = "";
231
+ try {
232
+ text = await fs.readFile(readmePath, "utf8");
233
+ }
234
+ catch {
235
+ continue;
236
+ }
237
+ let parsed;
238
+ try {
239
+ parsed = parseTaskReadme(text);
240
+ }
241
+ catch {
242
+ continue;
243
+ }
244
+ const sections = normalizeCanonicalSections(parsed.frontmatter.sections);
245
+ if (!sections)
246
+ continue;
247
+ const renderedBody = renderTaskDocFromSections(sections);
248
+ if (normalizeTaskBodyForComparison(parsed.body) === normalizeTaskBodyForComparison(renderedBody)) {
249
+ continue;
250
+ }
251
+ const taskId = typeof parsed.frontmatter.id === "string" && parsed.frontmatter.id.trim()
252
+ ? parsed.frontmatter.id.trim()
253
+ : entry.name;
254
+ const status = typeof parsed.frontmatter.status === "string" && parsed.frontmatter.status.trim()
255
+ ? parsed.frontmatter.status.trim().toUpperCase()
256
+ : "UNKNOWN";
257
+ drifted.push({ id: taskId, status });
258
+ }
259
+ if (drifted.length === 0)
260
+ return [];
261
+ const activeCount = drifted.filter((task) => task.status !== "DONE").length;
262
+ const examples = drifted
263
+ .slice(0, 5)
264
+ .map((task) => `${task.id}[${task.status}]`)
265
+ .join(", ");
266
+ return [
267
+ renderDiagnosticFinding({
268
+ severity: "WARN",
269
+ state: "task README projection drift detected",
270
+ likelyCause: "canonical frontmatter.sections no longer match the rendered task body on disk",
271
+ nextAction: {
272
+ command: "agentplane task normalize",
273
+ reason: "re-render task README bodies from canonical one-file task state",
274
+ },
275
+ details: [
276
+ `Drifted task READMEs: ${drifted.length}; active drifted tasks: ${activeCount}`,
277
+ examples ? `Examples: ${examples}` : "Examples unavailable.",
278
+ ],
279
+ }),
280
+ ];
281
+ }
282
+ function checkBackendReadiness(ctx) {
283
+ if (ctx?.backendId !== "redmine")
284
+ return [];
285
+ const { supports_task_revisions, supports_revision_guarded_writes } = ctx.taskBackend.capabilities;
286
+ if (supports_task_revisions === supports_revision_guarded_writes &&
287
+ supports_task_revisions === true) {
288
+ return [];
289
+ }
290
+ if (supports_task_revisions === false && supports_revision_guarded_writes === false) {
291
+ return [
292
+ renderDiagnosticFinding({
293
+ severity: "WARN",
294
+ state: "Redmine backend is running in partial compatibility mode without canonical_state support",
295
+ likelyCause: "AGENTPLANE_REDMINE_CUSTOM_FIELDS_CANONICAL_STATE is not configured, so Redmine cannot round-trip the full canonical task state or guard writes by remote revision",
296
+ nextAction: {
297
+ command: "agentplane backend inspect redmine --yes",
298
+ reason: "inspect visible Redmine custom fields first, then wire AGENTPLANE_REDMINE_CUSTOM_FIELDS_CANONICAL_STATE to the correct field id",
299
+ },
300
+ details: [
301
+ `Backend config: ${ctx.backendConfigPath}`,
302
+ "Current capability flags: supports_task_revisions=false; supports_revision_guarded_writes=false",
303
+ "Legacy doc field syncing can still work, but the backend remains partial-compatibility only.",
304
+ ],
305
+ }),
306
+ ];
307
+ }
308
+ return [
309
+ renderDiagnosticFinding({
310
+ severity: "WARN",
311
+ state: "Redmine backend capability contract is internally inconsistent",
312
+ likelyCause: "backend capability flags diverged, so doctor cannot rely on a single revision-guard readiness state",
313
+ nextAction: {
314
+ command: "inspect Redmine backend capability wiring and rerun agentplane doctor",
315
+ reason: "restore a coherent readiness contract before relying on guarded remote writes",
316
+ },
317
+ details: [
318
+ `Backend config: ${ctx.backendConfigPath}`,
319
+ `Current capability flags: supports_task_revisions=${String(supports_task_revisions)}; supports_revision_guarded_writes=${String(supports_revision_guarded_writes)}`,
320
+ ],
321
+ }),
322
+ ];
323
+ }
200
324
  export async function checkWorkspace(repoRoot, opts) {
201
325
  const problems = [];
202
326
  const requiredFiles = [path.join(repoRoot, ".agentplane", "config.json")];
@@ -243,6 +367,6 @@ export async function checkWorkspace(repoRoot, opts) {
243
367
  if (!hasJson) {
244
368
  problems.push("No agent profiles found in .agentplane/agents (*.json expected).");
245
369
  }
246
- problems.push(...(await checkTaskReadmeMigrationState(repoRoot, opts?.ctx)), ...(await checkDoneTaskReadmeArchiveDrift(repoRoot, opts?.ctx)));
370
+ problems.push(...checkBackendReadiness(opts?.ctx), ...(await checkTaskReadmeMigrationState(repoRoot, opts?.ctx)), ...(await checkDoneTaskReadmeArchiveDrift(repoRoot, opts?.ctx)), ...(await checkTaskProjectionDrift(repoRoot, opts?.ctx)));
247
371
  return problems;
248
372
  }
@@ -1 +1 @@
1
- {"version":3,"file":"commit.command.d.ts","sourceRoot":"","sources":["../../../src/commands/guard/commit.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,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,iBAAiB,CA0G1D,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC1E,KAAK,UAAU,EAAE,GAAG,iBAAiB,KAAG,OAAO,CAAC,MAAM,CAAC,CA2BtE"}
1
+ {"version":3,"file":"commit.command.d.ts","sourceRoot":"","sources":["../../../src/commands/guard/commit.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,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,iBAAiB,CAmI1D,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC1E,KAAK,UAAU,EAAE,GAAG,iBAAiB,KAAG,OAAO,CAAC,MAAM,CAAC,CA2BtE"}
@@ -34,23 +34,38 @@ export const guardCommitSpec = {
34
34
  kind: "boolean",
35
35
  name: "allow-tasks",
36
36
  default: false,
37
- description: "Allow task workflow artifacts (tasks/ and .agentplane/tasks/).",
37
+ description: "Allow the tasks export snapshot plus artifacts under the active task subtree; standalone path scope.",
38
38
  },
39
39
  {
40
40
  kind: "boolean",
41
41
  name: "allow-base",
42
42
  default: false,
43
- description: "Allow base branch edits.",
43
+ description: "Allow base branch edits; branch override only, not a path allowlist.",
44
44
  },
45
45
  {
46
46
  kind: "boolean",
47
47
  name: "allow-policy",
48
48
  default: false,
49
- description: "Allow policy edits (e.g. AGENTS.md).",
49
+ description: "Allow policy edits (e.g. AGENTS.md); standalone path scope.",
50
+ },
51
+ {
52
+ kind: "boolean",
53
+ name: "allow-config",
54
+ default: false,
55
+ description: "Allow config edits; standalone path scope.",
56
+ },
57
+ {
58
+ kind: "boolean",
59
+ name: "allow-hooks",
60
+ default: false,
61
+ description: "Allow hooks edits; standalone path scope.",
62
+ },
63
+ {
64
+ kind: "boolean",
65
+ name: "allow-ci",
66
+ default: false,
67
+ description: "Allow CI workflow edits; standalone path scope.",
50
68
  },
51
- { kind: "boolean", name: "allow-config", default: false, description: "Allow config edits." },
52
- { kind: "boolean", name: "allow-hooks", default: false, description: "Allow hooks edits." },
53
- { kind: "boolean", name: "allow-ci", default: false, description: "Allow CI workflow edits." },
54
69
  {
55
70
  kind: "boolean",
56
71
  name: "require-clean",
@@ -64,6 +79,15 @@ export const guardCommitSpec = {
64
79
  cmd: 'agentplane guard commit 202602030608-F1Q8AB -m "✨ F1Q8AB task: implement allowlist guard" --allow packages/agentplane',
65
80
  why: "Validate staged changes are covered by allowlist and policy.",
66
81
  },
82
+ {
83
+ cmd: 'agentplane guard commit 202602030608-F1Q8AB -m "✨ F1Q8AB task: update publish workflow" --allow-ci',
84
+ why: "Validate already staged CI-only changes without a redundant explicit workflow path prefix.",
85
+ },
86
+ ],
87
+ notes: [
88
+ "Protected path-scoped overrides can stand alone without a duplicate explicit prefix: `--allow-tasks`, `--allow-policy`, `--allow-config`, `--allow-hooks`, and `--allow-ci` each admit their own path family.",
89
+ "`agentplane guard commit` remains staged-only: it validates the current index and never auto-stages files for you.",
90
+ "`--allow-base` is different: it only overrides base-branch protection and never selects file paths by itself.",
67
91
  ],
68
92
  validateRaw: (raw) => {
69
93
  const msg = typeof raw.opts.message === "string" ? raw.opts.message.trim() : "";
@@ -13,6 +13,15 @@ export declare function stageAllowlist(opts: {
13
13
  ctx: CommandContext;
14
14
  allow: string[];
15
15
  allowTasks: boolean;
16
+ allowPolicy?: boolean;
17
+ allowConfig?: boolean;
18
+ allowHooks?: boolean;
19
+ allowCI?: boolean;
16
20
  tasksPath: string;
21
+ workflowDir?: string;
22
+ taskId?: string;
23
+ allowTaskOnly?: boolean;
24
+ emptyAllowMessage?: string;
25
+ noMatchMessage?: string;
17
26
  }): Promise<string[]>;
18
27
  //# sourceMappingURL=allow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"allow.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/allow.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAkBvF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe9D;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOpB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBhB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,EAAE,cAAc,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAkDpB"}
1
+ {"version":3,"file":"allow.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/allow.ts"],"names":[],"mappings":"AAUA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAkBvF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe9D;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOpB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBhB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,EAAE,cAAc,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAoEpB"}
@@ -2,6 +2,7 @@ import { resolveProject } from "@agentplaneorg/core";
2
2
  import { exitCodeForError } from "../../../cli/exit-codes.js";
3
3
  import { gitPathIsUnderPrefix, normalizeGitPathPrefix } from "../../../shared/git-path.js";
4
4
  import { CliError } from "../../../shared/errors.js";
5
+ import { protectedPathAllowPrefixes, taskArtifactPrefixes, } from "../../../shared/protected-paths.js";
5
6
  import { GitContext } from "../../shared/git-context.js";
6
7
  import { loadCommandContext } from "../../shared/task-backend.js";
7
8
  function normalizeAllowPrefixes(prefixes) {
@@ -75,28 +76,43 @@ export async function stageAllowlist(opts) {
75
76
  });
76
77
  }
77
78
  const allow = normalizeAllowPrefixes(opts.allow);
78
- if (allow.length === 0) {
79
+ if (allow.includes(".")) {
79
80
  throw new CliError({
80
81
  exitCode: 2,
81
82
  code: "E_USAGE",
82
- message: "Provide at least one allowed prefix",
83
+ message: "Repo-wide allowlist ('.') is not allowed; choose minimal prefixes (tip: `agentplane guard suggest-allow --format args`).",
83
84
  });
84
85
  }
85
- if (allow.includes(".")) {
86
+ const taskAllow = taskArtifactPrefixes({
87
+ tasksPath: opts.tasksPath,
88
+ workflowDir: opts.workflowDir,
89
+ taskId: opts.taskId,
90
+ });
91
+ const protectedAllow = protectedPathAllowPrefixes({
92
+ tasksPath: opts.tasksPath,
93
+ workflowDir: opts.workflowDir,
94
+ taskId: opts.taskId,
95
+ allowTasks: opts.allowTasks,
96
+ allowPolicy: opts.allowPolicy,
97
+ allowConfig: opts.allowConfig,
98
+ allowHooks: opts.allowHooks,
99
+ allowCI: opts.allowCI,
100
+ });
101
+ const effectiveAllow = normalizeAllowPrefixes([...allow, ...protectedAllow]);
102
+ if (effectiveAllow.length === 0 ||
103
+ (allow.length === 0 && protectedAllow.length === 0 && opts.allowTaskOnly !== true)) {
86
104
  throw new CliError({
87
105
  exitCode: 2,
88
106
  code: "E_USAGE",
89
- message: "Repo-wide allowlist ('.') is not allowed; choose minimal prefixes (tip: `agentplane guard suggest-allow --format args`).",
107
+ message: opts.emptyAllowMessage ?? "Provide at least one allowed prefix",
90
108
  });
91
109
  }
92
- const denied = new Set();
93
- if (!opts.allowTasks)
94
- denied.add(opts.tasksPath);
110
+ const denied = opts.allowTasks ? [] : taskAllow;
95
111
  const staged = [];
96
112
  for (const filePath of changed) {
97
- if (denied.has(filePath))
113
+ if (denied.some((prefix) => gitPathIsUnderPrefix(filePath, prefix)))
98
114
  continue;
99
- if (allow.some((prefix) => gitPathIsUnderPrefix(filePath, prefix))) {
115
+ if (effectiveAllow.some((prefix) => gitPathIsUnderPrefix(filePath, prefix))) {
100
116
  staged.push(filePath);
101
117
  }
102
118
  }
@@ -105,7 +121,7 @@ export async function stageAllowlist(opts) {
105
121
  throw new CliError({
106
122
  exitCode: 2,
107
123
  code: "E_USAGE",
108
- message: "No changes matched allowed prefixes (update --commit-allow)",
124
+ message: opts.noMatchMessage ?? "No changes matched allowed prefixes (update --commit-allow)",
109
125
  });
110
126
  }
111
127
  // `git add <pathspec>` is not reliable for staging deletes/renames across versions/configs.
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commands.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAQvF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAuExE,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBlB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBlB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgKlB"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commands.ts"],"names":[],"mappings":"AAKA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAQvF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAuMxE,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBlB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBlB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuMlB"}
@@ -1,15 +1,30 @@
1
1
  import { mapCoreError } from "../../../cli/error-map.js";
2
- import { successMessage } from "../../../cli/output.js";
2
+ import { infoMessage, successMessage } from "../../../cli/output.js";
3
+ import { stripAnsi } from "../../../cli/shared/ansi.js";
3
4
  import { withDiagnosticContext } from "../../../shared/diagnostics.js";
4
5
  import { CliError } from "../../../shared/errors.js";
5
6
  import { loadCommandContext } from "../../shared/task-backend.js";
6
7
  import { loadTaskFromContext } from "../../shared/task-backend.js";
7
8
  import { execFileAsync, gitEnv } from "../../shared/git.js";
8
9
  import { ensureReconciledBeforeMutation } from "../../shared/reconcile-check.js";
9
- import { suggestAllowPrefixes } from "./allow.js";
10
+ import { stageAllowlist, suggestAllowPrefixes } from "./allow.js";
10
11
  import { buildCloseCommitMessage, taskReadmePathForTask } from "./close-message.js";
11
12
  import { buildGitCommitEnv } from "./env.js";
12
13
  import { guardCommitCheck } from "./policy.js";
14
+ const COMMIT_FAILURE_SIGNAL_PATTERNS = [
15
+ /Code style issues found/i,
16
+ /Run Prettier with --write/i,
17
+ /\bESLint\b/i,
18
+ /\b[0-9]+\s+problems?\b/i,
19
+ /\berror\b/i,
20
+ /\bfailed\b/i,
21
+ /✖/,
22
+ ];
23
+ const FORMATTER_SIGNAL_PATTERNS = [
24
+ /Code style issues found/i,
25
+ /Run Prettier with --write/i,
26
+ ];
27
+ const ESLINT_SIGNAL_PATTERNS = [/\bESLint\b/i, /\b[0-9]+\s+problems?\b/i];
13
28
  function readText(value) {
14
29
  if (typeof value === "string")
15
30
  return value;
@@ -21,16 +36,108 @@ function summarizeOutput(raw) {
21
36
  const lines = raw
22
37
  .replaceAll("\r\n", "\n")
23
38
  .split("\n")
24
- .map((line) => line.trimEnd())
39
+ .map((line) => stripAnsi(line).trimEnd())
25
40
  .filter((line) => line.trim().length > 0)
26
41
  .map((line) => (line.length > 180 ? `${line.slice(0, 180)} [truncated]` : line));
27
42
  if (lines.length <= 12)
28
43
  return lines;
29
- const head = lines.slice(0, 6);
30
- const tail = lines.slice(-6);
31
- return [...head, `[${lines.length - 12} lines omitted]`, ...tail];
44
+ const selected = new Set();
45
+ for (let index = 0; index < Math.min(6, lines.length); index += 1) {
46
+ selected.add(index);
47
+ }
48
+ for (let index = Math.max(lines.length - 6, 0); index < lines.length; index += 1) {
49
+ selected.add(index);
50
+ }
51
+ for (const [index, line] of lines.entries()) {
52
+ if (selected.has(index))
53
+ continue;
54
+ if (COMMIT_FAILURE_SIGNAL_PATTERNS.some((pattern) => pattern.test(line))) {
55
+ selected.add(index);
56
+ }
57
+ }
58
+ const ordered = [...selected].toSorted((a, b) => a - b);
59
+ const summary = [];
60
+ let previous = -1;
61
+ for (const index of ordered) {
62
+ if (previous >= 0 && index - previous > 1) {
63
+ summary.push(`[${index - previous - 1} lines omitted]`);
64
+ }
65
+ summary.push(lines[index] ?? "");
66
+ previous = index;
67
+ }
68
+ return summary;
69
+ }
70
+ function detectCommitFailureSignal(output) {
71
+ if (FORMATTER_SIGNAL_PATTERNS.some((pattern) => pattern.test(output))) {
72
+ return "formatter";
73
+ }
74
+ if (ESLINT_SIGNAL_PATTERNS.some((pattern) => pattern.test(output))) {
75
+ return "eslint";
76
+ }
77
+ return null;
78
+ }
79
+ function commitFailureDiagnostic(phase, output) {
80
+ const signal = detectCommitFailureSignal(output);
81
+ if (signal === "formatter") {
82
+ return {
83
+ state: phase === "close_commit"
84
+ ? "git rejected the generated close commit"
85
+ : "git rejected the requested task-scoped commit",
86
+ likelyCause: phase === "close_commit"
87
+ ? "a formatting check in the pre-commit path rejected the deterministic close commit after the task README was staged"
88
+ : "a formatting check in the pre-commit path rejected the staged task-scoped commit",
89
+ nextAction: {
90
+ command: "bun run format",
91
+ reason: "apply formatter fixes before retrying the commit flow",
92
+ reasonCode: "git_pre_commit_format",
93
+ },
94
+ };
95
+ }
96
+ if (signal === "eslint") {
97
+ return {
98
+ state: phase === "close_commit"
99
+ ? "git rejected the generated close commit"
100
+ : "git rejected the requested task-scoped commit",
101
+ likelyCause: phase === "close_commit"
102
+ ? "a lint check in the pre-commit path rejected the deterministic close commit after the task README was staged"
103
+ : "a lint check in the pre-commit path rejected the staged task-scoped commit",
104
+ nextAction: {
105
+ command: "bun run lint:core",
106
+ reason: "rerun lint and fix the reported error before retrying the commit flow",
107
+ reasonCode: "git_pre_commit_lint",
108
+ },
109
+ };
110
+ }
111
+ if (phase === "close_commit") {
112
+ return {
113
+ state: "git rejected the generated close commit",
114
+ likelyCause: "a hook or commit policy blocked the deterministic task close commit after the task README was staged",
115
+ nextAction: {
116
+ command: "git status --short --untracked-files=no",
117
+ reason: "inspect the staged close-commit payload before fixing the hook or policy failure",
118
+ reasonCode: "git_close_commit_blocked",
119
+ },
120
+ };
121
+ }
122
+ return {
123
+ state: "git rejected the requested task-scoped commit",
124
+ likelyCause: "a hook or commit policy blocked the staged changes after guard validation passed",
125
+ nextAction: {
126
+ command: "git status --short --untracked-files=no",
127
+ reason: "inspect the staged task-scoped payload before fixing the hook or policy failure",
128
+ reasonCode: "git_task_commit_blocked",
129
+ },
130
+ };
32
131
  }
33
- function asCommitFailure(err) {
132
+ function hasExplicitCommitScope(opts) {
133
+ return (opts.allow.some((prefix) => prefix.trim().length > 0) ||
134
+ opts.allowTasks ||
135
+ opts.allowPolicy ||
136
+ opts.allowConfig ||
137
+ opts.allowHooks ||
138
+ opts.allowCI);
139
+ }
140
+ function asCommitFailure(err, phase) {
34
141
  if (err instanceof Error) {
35
142
  const e = err;
36
143
  const cmd = typeof e.cmd === "string" ? e.cmd : "";
@@ -51,15 +158,7 @@ function asCommitFailure(err) {
51
158
  exitCode: 5,
52
159
  code: "E_GIT",
53
160
  message: lines.join("\n"),
54
- context: withDiagnosticContext({ command: "commit" }, {
55
- state: "git rejected the generated close commit",
56
- likelyCause: "a hook or commit policy blocked the deterministic task close commit after the task README was staged",
57
- nextAction: {
58
- command: "git status --short --untracked-files=no",
59
- reason: "inspect the staged close-commit payload before fixing the hook or policy failure",
60
- reasonCode: "git_close_commit_blocked",
61
- },
62
- }),
161
+ context: withDiagnosticContext({ command: "commit" }, commitFailureDiagnostic(phase, output)),
63
162
  });
64
163
  }
65
164
  return null;
@@ -235,6 +334,35 @@ export async function cmdCommit(opts) {
235
334
  });
236
335
  }
237
336
  await ensureReconciledBeforeMutation({ ctx, command: "commit" });
337
+ let autoStaged = [];
338
+ const staged = await ctx.git.statusStagedPaths();
339
+ if (staged.length === 0) {
340
+ if (!hasExplicitCommitScope(opts)) {
341
+ throw new CliError({
342
+ exitCode: 2,
343
+ code: "E_USAGE",
344
+ message: "No staged files and no commit allowlist. Pass --allow <path-prefix>, use --allow-tasks for active task artifacts, or stage files manually.",
345
+ });
346
+ }
347
+ autoStaged = await stageAllowlist({
348
+ ctx,
349
+ allow: opts.allow,
350
+ allowTasks: opts.allowTasks,
351
+ allowPolicy: opts.allowPolicy,
352
+ allowConfig: opts.allowConfig,
353
+ allowHooks: opts.allowHooks,
354
+ allowCI: opts.allowCI,
355
+ tasksPath: ctx.config.paths.tasks_path,
356
+ workflowDir: ctx.config.paths.workflow_dir,
357
+ taskId: opts.taskId,
358
+ allowTaskOnly: true,
359
+ emptyAllowMessage: "No staged files and no commit allowlist. Pass --allow <path-prefix>, use --allow-tasks for active task artifacts, or stage files manually.",
360
+ noMatchMessage: "No changed files matched the commit allowlist (adjust --allow, protected allow flags, or --allow-tasks; otherwise stage files manually).",
361
+ });
362
+ if (!opts.quiet) {
363
+ process.stdout.write(`${infoMessage(`commit auto-staged ${autoStaged.length} path(s) from allowlist`)}\n`);
364
+ }
365
+ }
238
366
  await guardCommitCheck({
239
367
  ctx,
240
368
  cwd: opts.cwd,
@@ -263,14 +391,14 @@ export async function cmdCommit(opts) {
263
391
  await ctx.git.commit({ message: opts.message, env });
264
392
  if (!opts.quiet) {
265
393
  const { hash, subject } = await ctx.git.headHashSubject();
266
- process.stdout.write(`${successMessage("committed", `${hash?.slice(0, 12) ?? ""} ${subject ?? ""}`.trim())}\n`);
394
+ process.stdout.write(`${successMessage("committed", `${hash?.slice(0, 12) ?? ""} ${subject ?? ""}`.trim(), autoStaged.length > 0 ? `staged=${autoStaged.join(", ")}` : undefined)}\n`);
267
395
  }
268
396
  return 0;
269
397
  }
270
398
  catch (err) {
271
399
  if (err instanceof CliError)
272
400
  throw err;
273
- const commitFailure = asCommitFailure(err);
401
+ const commitFailure = asCommitFailure(err, opts.close ? "close_commit" : "task_commit");
274
402
  if (commitFailure)
275
403
  throw commitFailure;
276
404
  throw mapCoreError(err, { command: "commit", root: opts.rootOverride ?? null });
@@ -1 +1 @@
1
- {"version":3,"file":"comment-commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/comment-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAQ/E,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAiEvF,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAyF/D"}
1
+ {"version":3,"file":"comment-commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/comment-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAQ/E,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAiEvF,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA2F/D"}
@@ -68,6 +68,8 @@ export async function commitFromComment(opts) {
68
68
  allow: allowPrefixes,
69
69
  allowTasks: opts.allowTasks,
70
70
  tasksPath: opts.config.paths.tasks_path,
71
+ workflowDir: opts.config.paths.workflow_dir,
72
+ taskId: opts.taskId,
71
73
  });
72
74
  const message = deriveCommitMessageFromComment({
73
75
  taskId: opts.taskId,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/index.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,UAAU,mDAAoD,CAAC;AA8G5E,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8HlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/index.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,mDAAoD,CAAC;AA8G5E,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsGlB"}
@@ -5,13 +5,11 @@ import { evaluatePolicy } from "../../policy/evaluate.js";
5
5
  import { mapBackendError, mapCoreError } from "../../cli/error-map.js";
6
6
  import { fileExists } from "../../cli/fs-utils.js";
7
7
  import { infoMessage, successMessage } from "../../cli/output.js";
8
- import { resolveCommitEmojiForAgent } from "../../shared/agent-emoji.js";
9
8
  import { CliError } from "../../shared/errors.js";
10
9
  import { GitContext } from "../shared/git-context.js";
11
10
  import { throwIfPolicyDenied } from "../shared/policy-deny.js";
12
11
  import { gitCurrentBranch, gitRevParse } from "../shared/git-ops.js";
13
12
  import { isPathWithin } from "../shared/path.js";
14
- import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
15
13
  const HOOK_MARKER = "agentplane-hook";
16
14
  const SHIM_MARKER = "agentplane-hook-shim";
17
15
  export const HOOK_NAMES = ["commit-msg", "pre-commit", "pre-push"];
@@ -201,40 +199,15 @@ export async function cmdHooksRun(opts) {
201
199
  const loaded = await loadConfig(resolved.agentplaneDir);
202
200
  const taskId = (process.env.AGENTPLANE_TASK_ID ?? "").trim();
203
201
  const statusTo = (process.env.AGENTPLANE_STATUS_TO ?? "").trim().toUpperCase();
204
- const agentsDirAbs = path.join(resolved.gitRoot, loaded.config.paths.agents_dir);
205
- let agentId = (process.env.AGENTPLANE_AGENT_ID ?? "").trim();
206
- if (!agentId && loaded.config.workflow_mode === "direct" && taskId) {
207
- const lock = await readDirectWorkLock(resolved.agentplaneDir);
208
- const lockAgent = lock?.agent?.trim() ?? "";
209
- if (lock?.task_id === taskId && lockAgent)
210
- agentId = lockAgent;
211
- }
212
202
  const emoji = subject.split(/\s+/).find(Boolean) ?? "";
213
- if (taskId) {
214
- if (statusTo === "DONE") {
215
- if (emoji !== "✅") {
216
- throw new CliError({
217
- exitCode: 5,
218
- code: "E_GIT",
219
- message: "Finish commits must use a checkmark emoji.\n" +
220
- "Expected:\n" +
221
- " ✅ <TASK_SUFFIX> <scope>: <summary>",
222
- });
223
- }
224
- }
225
- else if (agentId) {
226
- const expectedEmoji = await resolveCommitEmojiForAgent({ agentsDirAbs, agentId });
227
- if (emoji !== expectedEmoji) {
228
- throw new CliError({
229
- exitCode: 5,
230
- code: "E_GIT",
231
- message: "Commit emoji does not match the executor agent policy.\n" +
232
- `executor_agent=${agentId}\n` +
233
- "Expected:\n" +
234
- ` ${expectedEmoji} <TASK_SUFFIX> <scope>: <summary>`,
235
- });
236
- }
237
- }
203
+ if (taskId && statusTo === "DONE" && emoji !== "✅") {
204
+ throw new CliError({
205
+ exitCode: 5,
206
+ code: "E_GIT",
207
+ message: "Finish commits must use a checkmark emoji.\n" +
208
+ "Expected:\n" +
209
+ " <TASK_SUFFIX> <scope>: <summary>",
210
+ });
238
211
  }
239
212
  const res = evaluatePolicy({
240
213
  action: "hook_commit_msg",
@@ -3,6 +3,10 @@ export declare function moveRecipeDir(opts: {
3
3
  from: string;
4
4
  to: string;
5
5
  }): Promise<void>;
6
+ export declare function validateRecipeAssets(opts: {
7
+ manifest: RecipeManifest;
8
+ recipeDir: string;
9
+ }): Promise<void>;
6
10
  export declare function applyRecipeAgents(opts: {
7
11
  manifest: RecipeManifest;
8
12
  recipeDir: string;