agentplane 0.2.25 → 0.3.1

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 (221) hide show
  1. package/README.md +3 -1
  2. package/assets/AGENTS.md +123 -526
  3. package/assets/agents/UPGRADER.json +10 -9
  4. package/assets/framework.manifest.json +112 -7
  5. package/assets/policy/check-routing.mjs +180 -0
  6. package/assets/policy/dod.code.md +25 -0
  7. package/assets/policy/dod.core.md +32 -0
  8. package/assets/policy/dod.docs.md +32 -0
  9. package/assets/policy/examples/migration-note.md +6 -0
  10. package/assets/policy/examples/pr-note.md +16 -0
  11. package/assets/policy/examples/unit-test-pattern.md +19 -0
  12. package/assets/policy/governance.md +37 -0
  13. package/assets/policy/incidents.md +36 -0
  14. package/assets/policy/security.must.md +7 -0
  15. package/assets/policy/workflow.branch_pr.md +34 -0
  16. package/assets/policy/workflow.direct.md +46 -0
  17. package/assets/policy/workflow.md +9 -0
  18. package/assets/policy/workflow.release.md +31 -0
  19. package/assets/policy/workflow.upgrade.md +20 -0
  20. package/bin/agentplane.js +47 -57
  21. package/bin/dist-guard.js +124 -0
  22. package/dist/.build-manifest.json +11 -0
  23. package/dist/agents/agents-template.d.ts +7 -0
  24. package/dist/agents/agents-template.d.ts.map +1 -1
  25. package/dist/agents/agents-template.js +41 -2
  26. package/dist/backends/task-backend/local-backend.d.ts +2 -0
  27. package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
  28. package/dist/backends/task-backend/local-backend.js +12 -1
  29. package/dist/backends/task-backend/redmine/mapping.d.ts.map +1 -1
  30. package/dist/backends/task-backend/redmine/mapping.js +26 -1
  31. package/dist/backends/task-backend/redmine-backend.d.ts +4 -0
  32. package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
  33. package/dist/backends/task-backend/redmine-backend.js +92 -9
  34. package/dist/backends/task-backend/shared/types.d.ts +1 -0
  35. package/dist/backends/task-backend/shared/types.d.ts.map +1 -1
  36. package/dist/backends/task-index.d.ts.map +1 -1
  37. package/dist/backends/task-index.js +8 -1
  38. package/dist/cli/command-guide.d.ts.map +1 -1
  39. package/dist/cli/command-guide.js +39 -17
  40. package/dist/cli/command-snippets.d.ts +24 -0
  41. package/dist/cli/command-snippets.d.ts.map +1 -0
  42. package/dist/cli/command-snippets.js +23 -0
  43. package/dist/cli/reason-codes.d.ts +9 -0
  44. package/dist/cli/reason-codes.d.ts.map +1 -0
  45. package/dist/cli/reason-codes.js +79 -0
  46. package/dist/cli/recipes-bundled.d.ts +1 -0
  47. package/dist/cli/recipes-bundled.d.ts.map +1 -1
  48. package/dist/cli/recipes-bundled.js +4 -1
  49. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  50. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  51. package/dist/cli/run-cli/command-catalog.js +40 -1
  52. package/dist/cli/run-cli/commands/config.d.ts +5 -0
  53. package/dist/cli/run-cli/commands/config.d.ts.map +1 -1
  54. package/dist/cli/run-cli/commands/config.js +86 -1
  55. package/dist/cli/run-cli/commands/core.d.ts.map +1 -1
  56. package/dist/cli/run-cli/commands/core.js +57 -2
  57. package/dist/cli/run-cli/commands/ide.d.ts.map +1 -1
  58. package/dist/cli/run-cli/commands/ide.js +8 -3
  59. package/dist/cli/run-cli/commands/init/recipes.d.ts +5 -1
  60. package/dist/cli/run-cli/commands/init/recipes.d.ts.map +1 -1
  61. package/dist/cli/run-cli/commands/init/recipes.js +24 -4
  62. package/dist/cli/run-cli/commands/init/ui.d.ts.map +1 -1
  63. package/dist/cli/run-cli/commands/init/ui.js +1 -2
  64. package/dist/cli/run-cli/commands/init/write-agents.d.ts +2 -0
  65. package/dist/cli/run-cli/commands/init/write-agents.d.ts.map +1 -1
  66. package/dist/cli/run-cli/commands/init/write-agents.js +24 -5
  67. package/dist/cli/run-cli/commands/init/write-workflow.d.ts +12 -0
  68. package/dist/cli/run-cli/commands/init/write-workflow.d.ts.map +1 -0
  69. package/dist/cli/run-cli/commands/init/write-workflow.js +58 -0
  70. package/dist/cli/run-cli/commands/init.d.ts +4 -1
  71. package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
  72. package/dist/cli/run-cli/commands/init.js +126 -48
  73. package/dist/cli/run-cli.d.ts.map +1 -1
  74. package/dist/cli/run-cli.js +195 -8
  75. package/dist/commands/backend/sync.command.d.ts.map +1 -1
  76. package/dist/commands/backend/sync.command.js +7 -6
  77. package/dist/commands/backend.d.ts.map +1 -1
  78. package/dist/commands/backend.js +2 -0
  79. package/dist/commands/doctor.run.d.ts.map +1 -1
  80. package/dist/commands/doctor.run.js +107 -16
  81. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  82. package/dist/commands/guard/impl/commands.js +12 -6
  83. package/dist/commands/recipes/impl/commands/install.d.ts.map +1 -1
  84. package/dist/commands/recipes/impl/commands/install.js +36 -13
  85. package/dist/commands/recipes/impl/scenario.d.ts.map +1 -1
  86. package/dist/commands/recipes/impl/scenario.js +25 -0
  87. package/dist/commands/recipes/impl/types.d.ts +4 -0
  88. package/dist/commands/recipes/impl/types.d.ts.map +1 -1
  89. package/dist/commands/release/apply.command.d.ts.map +1 -1
  90. package/dist/commands/release/apply.command.js +9 -4
  91. package/dist/commands/release/plan.command.d.ts.map +1 -1
  92. package/dist/commands/release/plan.command.js +9 -3
  93. package/dist/commands/scenario/impl/commands.d.ts.map +1 -1
  94. package/dist/commands/scenario/impl/commands.js +74 -3
  95. package/dist/commands/scenario/impl/report.d.ts +8 -0
  96. package/dist/commands/scenario/impl/report.d.ts.map +1 -1
  97. package/dist/commands/scenario/impl/report.js +1 -0
  98. package/dist/commands/shared/reconcile-check.d.ts +7 -0
  99. package/dist/commands/shared/reconcile-check.d.ts.map +1 -0
  100. package/dist/commands/shared/reconcile-check.js +60 -0
  101. package/dist/commands/sync.command.d.ts.map +1 -1
  102. package/dist/commands/sync.command.js +9 -2
  103. package/dist/commands/task/add.d.ts.map +1 -1
  104. package/dist/commands/task/add.js +32 -0
  105. package/dist/commands/task/doc.command.d.ts.map +1 -1
  106. package/dist/commands/task/doc.command.js +1 -0
  107. package/dist/commands/task/finish.d.ts.map +1 -1
  108. package/dist/commands/task/finish.js +11 -1
  109. package/dist/commands/task/list.d.ts.map +1 -1
  110. package/dist/commands/task/list.js +2 -1
  111. package/dist/commands/task/list.spec.d.ts.map +1 -1
  112. package/dist/commands/task/list.spec.js +7 -0
  113. package/dist/commands/task/new.d.ts.map +1 -1
  114. package/dist/commands/task/new.js +41 -4
  115. package/dist/commands/task/next.d.ts.map +1 -1
  116. package/dist/commands/task/next.js +2 -1
  117. package/dist/commands/task/next.spec.d.ts.map +1 -1
  118. package/dist/commands/task/next.spec.js +7 -0
  119. package/dist/commands/task/plan.d.ts.map +1 -1
  120. package/dist/commands/task/plan.js +7 -1
  121. package/dist/commands/task/search.d.ts.map +1 -1
  122. package/dist/commands/task/search.js +2 -1
  123. package/dist/commands/task/search.spec.d.ts.map +1 -1
  124. package/dist/commands/task/search.spec.js +7 -0
  125. package/dist/commands/task/shared.d.ts +14 -0
  126. package/dist/commands/task/shared.d.ts.map +1 -1
  127. package/dist/commands/task/shared.js +58 -1
  128. package/dist/commands/task/start-ready.js +1 -1
  129. package/dist/commands/task/verify-record.d.ts.map +1 -1
  130. package/dist/commands/task/verify-record.js +2 -0
  131. package/dist/commands/upgrade.command.d.ts.map +1 -1
  132. package/dist/commands/upgrade.command.js +2 -2
  133. package/dist/commands/upgrade.d.ts.map +1 -1
  134. package/dist/commands/upgrade.js +263 -294
  135. package/dist/commands/workflow-build.command.d.ts +8 -0
  136. package/dist/commands/workflow-build.command.d.ts.map +1 -0
  137. package/dist/commands/workflow-build.command.js +103 -0
  138. package/dist/commands/workflow-playbook.command.d.ts +10 -0
  139. package/dist/commands/workflow-playbook.command.d.ts.map +1 -0
  140. package/dist/commands/workflow-playbook.command.js +173 -0
  141. package/dist/commands/workflow-restore.command.d.ts +5 -0
  142. package/dist/commands/workflow-restore.command.d.ts.map +1 -0
  143. package/dist/commands/workflow-restore.command.js +30 -0
  144. package/dist/commands/workflow.command.d.ts +6 -0
  145. package/dist/commands/workflow.command.d.ts.map +1 -0
  146. package/dist/commands/workflow.command.js +36 -0
  147. package/dist/harness/dynamic-tool-contract.d.ts +29 -0
  148. package/dist/harness/dynamic-tool-contract.d.ts.map +1 -0
  149. package/dist/harness/dynamic-tool-contract.js +86 -0
  150. package/dist/harness/hooks-lifecycle.d.ts +27 -0
  151. package/dist/harness/hooks-lifecycle.d.ts.map +1 -0
  152. package/dist/harness/hooks-lifecycle.js +67 -0
  153. package/dist/harness/index.d.ts +9 -0
  154. package/dist/harness/index.d.ts.map +1 -0
  155. package/dist/harness/index.js +8 -0
  156. package/dist/harness/reconcile.d.ts +37 -0
  157. package/dist/harness/reconcile.d.ts.map +1 -0
  158. package/dist/harness/reconcile.js +42 -0
  159. package/dist/harness/retry-policy.d.ts +31 -0
  160. package/dist/harness/retry-policy.d.ts.map +1 -0
  161. package/dist/harness/retry-policy.js +33 -0
  162. package/dist/harness/scheduler.d.ts +18 -0
  163. package/dist/harness/scheduler.d.ts.map +1 -0
  164. package/dist/harness/scheduler.js +55 -0
  165. package/dist/harness/state-machine.d.ts +17 -0
  166. package/dist/harness/state-machine.d.ts.map +1 -0
  167. package/dist/harness/state-machine.js +70 -0
  168. package/dist/harness/token-accounting.d.ts +19 -0
  169. package/dist/harness/token-accounting.d.ts.map +1 -0
  170. package/dist/harness/token-accounting.js +77 -0
  171. package/dist/harness/workspace-safety.d.ts +14 -0
  172. package/dist/harness/workspace-safety.d.ts.map +1 -0
  173. package/dist/harness/workspace-safety.js +62 -0
  174. package/dist/recipes/bundled-recipes.d.ts +4 -0
  175. package/dist/recipes/bundled-recipes.d.ts.map +1 -1
  176. package/dist/recipes/bundled-recipes.js +11 -0
  177. package/dist/shared/errors.d.ts +6 -0
  178. package/dist/shared/errors.d.ts.map +1 -1
  179. package/dist/shared/errors.js +1 -0
  180. package/dist/shared/policy-gateway.d.ts +15 -0
  181. package/dist/shared/policy-gateway.d.ts.map +1 -0
  182. package/dist/shared/policy-gateway.js +49 -0
  183. package/dist/shared/protected-paths.d.ts.map +1 -1
  184. package/dist/shared/protected-paths.js +1 -0
  185. package/dist/shared/runtime-artifacts.d.ts +2 -2
  186. package/dist/shared/runtime-artifacts.d.ts.map +1 -1
  187. package/dist/shared/runtime-artifacts.js +4 -0
  188. package/dist/workflow-runtime/build.d.ts +4 -0
  189. package/dist/workflow-runtime/build.d.ts.map +1 -0
  190. package/dist/workflow-runtime/build.js +126 -0
  191. package/dist/workflow-runtime/enforcement.d.ts +3 -0
  192. package/dist/workflow-runtime/enforcement.d.ts.map +1 -0
  193. package/dist/workflow-runtime/enforcement.js +10 -0
  194. package/dist/workflow-runtime/file-ops.d.ts +11 -0
  195. package/dist/workflow-runtime/file-ops.d.ts.map +1 -0
  196. package/dist/workflow-runtime/file-ops.js +248 -0
  197. package/dist/workflow-runtime/fix.d.ts +9 -0
  198. package/dist/workflow-runtime/fix.d.ts.map +1 -0
  199. package/dist/workflow-runtime/fix.js +107 -0
  200. package/dist/workflow-runtime/index.d.ts +11 -0
  201. package/dist/workflow-runtime/index.d.ts.map +1 -0
  202. package/dist/workflow-runtime/index.js +10 -0
  203. package/dist/workflow-runtime/markdown.d.ts +10 -0
  204. package/dist/workflow-runtime/markdown.d.ts.map +1 -0
  205. package/dist/workflow-runtime/markdown.js +147 -0
  206. package/dist/workflow-runtime/observability.d.ts +12 -0
  207. package/dist/workflow-runtime/observability.d.ts.map +1 -0
  208. package/dist/workflow-runtime/observability.js +14 -0
  209. package/dist/workflow-runtime/paths.d.ts +3 -0
  210. package/dist/workflow-runtime/paths.d.ts.map +1 -0
  211. package/dist/workflow-runtime/paths.js +11 -0
  212. package/dist/workflow-runtime/template.d.ts +7 -0
  213. package/dist/workflow-runtime/template.d.ts.map +1 -0
  214. package/dist/workflow-runtime/template.js +94 -0
  215. package/dist/workflow-runtime/types.d.ts +68 -0
  216. package/dist/workflow-runtime/types.d.ts.map +1 -0
  217. package/dist/workflow-runtime/types.js +1 -0
  218. package/dist/workflow-runtime/validate.d.ts +8 -0
  219. package/dist/workflow-runtime/validate.d.ts.map +1 -0
  220. package/dist/workflow-runtime/validate.js +331 -0
  221. package/package.json +3 -3
@@ -15,15 +15,18 @@ import { helpSpec } from "./spec/help.js";
15
15
  import { usageError } from "./spec/errors.js";
16
16
  import { suggestOne } from "./spec/suggest.js";
17
17
  import { COMMANDS } from "./run-cli/command-catalog.js";
18
+ import { getReasonCodeMeta } from "./reason-codes.js";
18
19
  const GLOBAL_FLAGS = [
19
20
  { key: "help", forms: ["--help", "-h"], takesValue: false, scoped: false },
20
21
  { key: "version", forms: ["--version", "-v"], takesValue: false, scoped: false },
21
22
  { key: "noUpdateCheck", forms: ["--no-update-check"], takesValue: false, scoped: false },
22
23
  { key: "allowNetwork", forms: ["--allow-network"], takesValue: false, scoped: true },
23
24
  { key: "jsonErrors", forms: ["--json-errors"], takesValue: false, scoped: true },
25
+ { key: "outputMode", forms: ["--output"], takesValue: true, scoped: false },
24
26
  { key: "root", forms: ["--root"], takesValue: true, scoped: false },
25
27
  ];
26
28
  const GLOBAL_FLAG_FORMS = new Map(GLOBAL_FLAGS.flatMap((def) => def.forms.map((form) => [form, def])));
29
+ const HELP_TAIL_OPTIONS = new Set(["--compact", "--json"]);
27
30
  function prescanJsonErrors(argv) {
28
31
  // If parseGlobalArgs throws (e.g. missing --root value), we still want to honor
29
32
  // `--json-errors` in the "scoped global" zone (before the command id).
@@ -54,6 +57,7 @@ function parseGlobalArgs(argv) {
54
57
  let jsonErrors = false;
55
58
  let root;
56
59
  let allowNetwork = false;
60
+ let outputMode;
57
61
  const rest = [];
58
62
  for (let i = 0; i < argv.length; i++) {
59
63
  const arg = argv[i];
@@ -103,6 +107,27 @@ function parseGlobalArgs(argv) {
103
107
  i++;
104
108
  break;
105
109
  }
110
+ case "outputMode": {
111
+ const next = argv[i + 1];
112
+ if (!next) {
113
+ throw new CliError({
114
+ exitCode: 2,
115
+ code: "E_USAGE",
116
+ message: "Missing value after --output (expected text|json)",
117
+ });
118
+ }
119
+ const normalized = next.trim().toLowerCase();
120
+ if (normalized !== "text" && normalized !== "json") {
121
+ throw new CliError({
122
+ exitCode: 2,
123
+ code: "E_USAGE",
124
+ message: `Invalid value for --output: ${next} (expected text|json)`,
125
+ });
126
+ }
127
+ outputMode = normalized;
128
+ i++;
129
+ break;
130
+ }
106
131
  default: {
107
132
  // Exhaustive by construction; keep a defensive fallback.
108
133
  rest.push(arg);
@@ -110,7 +135,85 @@ function parseGlobalArgs(argv) {
110
135
  }
111
136
  }
112
137
  }
113
- return { globals: { help, version, noUpdateCheck, root, jsonErrors, allowNetwork }, rest };
138
+ return {
139
+ globals: { help, version, noUpdateCheck, root, jsonErrors, allowNetwork, outputMode },
140
+ rest,
141
+ };
142
+ }
143
+ const OUTPUT_MODE_ENV = "AGENTPLANE_OUTPUT";
144
+ function resolveOutputMode(modeFromFlag) {
145
+ if (modeFromFlag)
146
+ return modeFromFlag;
147
+ const fromEnv = process.env[OUTPUT_MODE_ENV]?.trim().toLowerCase();
148
+ if (!fromEnv || fromEnv === "text")
149
+ return "text";
150
+ if (fromEnv === "json")
151
+ return "json";
152
+ throw new CliError({
153
+ exitCode: 2,
154
+ code: "E_USAGE",
155
+ message: `Invalid ${OUTPUT_MODE_ENV}: ${fromEnv} (expected text|json)`,
156
+ });
157
+ }
158
+ function chunkToString(chunk, encoding) {
159
+ if (typeof chunk === "string")
160
+ return chunk;
161
+ if (chunk instanceof Uint8Array)
162
+ return Buffer.from(chunk).toString(encoding);
163
+ return String(chunk);
164
+ }
165
+ async function runWithOutputMode(opts) {
166
+ if (opts.mode === "text")
167
+ return await opts.run();
168
+ const stdoutWrite = process.stdout.write.bind(process.stdout);
169
+ const stderrWrite = process.stderr.write.bind(process.stderr);
170
+ let stdout = "";
171
+ let stderr = "";
172
+ process.stdout.write = ((chunk, ...rest) => {
173
+ const encoding = typeof rest[0] === "string" ? rest[0] : undefined;
174
+ stdout += chunkToString(chunk, encoding);
175
+ const callback = rest.find((item) => typeof item === "function");
176
+ callback?.(null);
177
+ return true;
178
+ });
179
+ process.stderr.write = ((chunk, ...rest) => {
180
+ const encoding = typeof rest[0] === "string" ? rest[0] : undefined;
181
+ stderr += chunkToString(chunk, encoding);
182
+ const callback = rest.find((item) => typeof item === "function");
183
+ callback?.(null);
184
+ return true;
185
+ });
186
+ try {
187
+ const exitCode = await opts.run();
188
+ let parsed;
189
+ const trimmedStdout = stdout.trim();
190
+ if (trimmedStdout.length > 0) {
191
+ try {
192
+ parsed = JSON.parse(trimmedStdout);
193
+ }
194
+ catch {
195
+ parsed = undefined;
196
+ }
197
+ }
198
+ const payload = {
199
+ schema_version: 1,
200
+ mode: "agent_json_v1",
201
+ command: opts.command,
202
+ ok: exitCode === 0,
203
+ exit_code: exitCode,
204
+ stdout: trimmedStdout,
205
+ stderr: stderr.trim(),
206
+ };
207
+ if (parsed !== undefined) {
208
+ payload.data = parsed;
209
+ }
210
+ stdoutWrite(`${JSON.stringify(payload, null, 2)}\n`);
211
+ return exitCode;
212
+ }
213
+ finally {
214
+ process.stdout.write = stdoutWrite;
215
+ process.stderr.write = stderrWrite;
216
+ }
114
217
  }
115
218
  function matchCommandCatalog(tokens) {
116
219
  let best = null;
@@ -135,8 +238,15 @@ function matchCommandCatalog(tokens) {
135
238
  }
136
239
  function writeError(err, jsonErrors) {
137
240
  const guidance = resolveErrorGuidance(err);
241
+ const contextReasonCode = typeof err.context?.reason_code === "string" ? String(err.context.reason_code) : undefined;
242
+ const reasonCode = contextReasonCode ?? guidance.nextAction?.reasonCode;
243
+ const reasonDecode = getReasonCodeMeta(reasonCode);
138
244
  if (jsonErrors) {
139
- process.stdout.write(`${formatJsonError(err, { hint: guidance.hint, nextAction: guidance.nextAction })}\n`);
245
+ process.stdout.write(`${formatJsonError(err, {
246
+ hint: guidance.hint,
247
+ nextAction: guidance.nextAction,
248
+ reasonDecode,
249
+ })}\n`);
140
250
  }
141
251
  else {
142
252
  const header = `error [${err.code}]`;
@@ -152,6 +262,10 @@ function writeError(err, jsonErrors) {
152
262
  if (guidance.nextAction) {
153
263
  process.stderr.write(`next_action: ${guidance.nextAction.command} (${guidance.nextAction.reason})\n`);
154
264
  }
265
+ if (reasonDecode) {
266
+ process.stderr.write(`reason_code: ${reasonDecode.code} [${reasonDecode.category}] ${reasonDecode.summary}\n`);
267
+ process.stderr.write(`reason_action: ${reasonDecode.action}\n`);
268
+ }
155
269
  }
156
270
  }
157
271
  const AGENTPLANE_HOME_ENV = "AGENTPLANE_HOME";
@@ -163,9 +277,20 @@ function resolveAgentplaneHome() {
163
277
  }
164
278
  function resolveErrorGuidance(err) {
165
279
  const command = typeof err.context?.command === "string" ? err.context.command : undefined;
280
+ const reasonCode = typeof err.context?.reason_code === "string" ? String(err.context.reason_code) : undefined;
166
281
  const usage = command ? `agentplane help ${command} --compact` : "agentplane help";
167
282
  switch (err.code) {
168
283
  case "E_USAGE": {
284
+ if (reasonCode === "sync_backend_mismatch") {
285
+ return {
286
+ hint: "Configured backend id mismatch. Check active backend and retry with a matching id.",
287
+ nextAction: {
288
+ command: "agentplane config show",
289
+ reason: "inspect active backend id before running sync",
290
+ reasonCode: "sync_backend_mismatch",
291
+ },
292
+ };
293
+ }
169
294
  return {
170
295
  hint: `See \`${usage}\` for usage.`,
171
296
  nextAction: {
@@ -176,6 +301,16 @@ function resolveErrorGuidance(err) {
176
301
  };
177
302
  }
178
303
  case "E_GIT": {
304
+ if (reasonCode === "reconcile_git_state_unreadable") {
305
+ return {
306
+ hint: "Reconcile check could not read git state.",
307
+ nextAction: {
308
+ command: "git status --short --untracked-files=no",
309
+ reason: "confirm repository state is readable before mutating commands",
310
+ reasonCode: "reconcile_git_state_unreadable",
311
+ },
312
+ };
313
+ }
179
314
  if (command?.startsWith("branch")) {
180
315
  return {
181
316
  hint: "Check git repo/branch; run `git branch` or pass --root <path>.",
@@ -236,6 +371,17 @@ function resolveErrorGuidance(err) {
236
371
  };
237
372
  }
238
373
  case "E_VALIDATION": {
374
+ if (reasonCode === "reconcile_task_scan_failed" ||
375
+ reasonCode === "reconcile_task_scan_incomplete") {
376
+ return {
377
+ hint: "Reconcile check failed due to task scan drift or parse/read errors.",
378
+ nextAction: {
379
+ command: "agentplane task list --strict-read",
380
+ reason: "surface task scan/read failures before retrying mutating commands",
381
+ reasonCode: reasonCode,
382
+ },
383
+ };
384
+ }
239
385
  return {
240
386
  hint: "Fix invalid config/input shape and rerun.",
241
387
  nextAction: {
@@ -361,10 +507,17 @@ export async function runCli(argv) {
361
507
  let jsonErrors = prescanJsonErrors(argv);
362
508
  try {
363
509
  const { globals, rest } = parseGlobalArgs(argv);
364
- jsonErrors = globals.jsonErrors;
510
+ const outputMode = resolveOutputMode(globals.outputMode);
511
+ jsonErrors = globals.jsonErrors || outputMode === "json";
365
512
  if (globals.version) {
366
- process.stdout.write(`${getVersion()}\n`);
367
- return 0;
513
+ return await runWithOutputMode({
514
+ mode: outputMode,
515
+ command: "version",
516
+ run: () => {
517
+ process.stdout.write(`${getVersion()}\n`);
518
+ return Promise.resolve(0);
519
+ },
520
+ });
368
521
  }
369
522
  const runCli2HelpFast = async (helpArgv) => {
370
523
  const { buildRegistry } = await import("./run-cli/registry.run.js");
@@ -384,12 +537,42 @@ export async function runCli(argv) {
384
537
  }
385
538
  const tail = helpArgv.slice(match.consumed);
386
539
  const parsed = parseCommandArgv(match.spec, tail).parsed;
387
- return await match.handler({ cwd: process.cwd(), rootOverride: globals.root }, parsed);
540
+ return await runWithOutputMode({
541
+ mode: outputMode,
542
+ command: match.spec.id.join(" "),
543
+ run: async () => {
544
+ return await match.handler({ cwd: process.cwd(), rootOverride: globals.root }, parsed);
545
+ },
546
+ });
388
547
  };
389
548
  // `--help` is treated as an alias for `help` and supports per-command help:
390
549
  // - agentplane --help
391
550
  // - agentplane <cmd...> --help [--compact|--json]
392
551
  if (globals.help) {
552
+ const matchedHelp = matchCommandCatalog(rest);
553
+ if (matchedHelp) {
554
+ const rawHelpTail = rest.slice(matchedHelp.consumed);
555
+ const commandFlags = new Set();
556
+ for (const opt of matchedHelp.entry.spec.options ?? []) {
557
+ const optRecord = opt;
558
+ const long = typeof optRecord.name === "string" ? optRecord.name.trim() : "";
559
+ if (long)
560
+ commandFlags.add(`--${long}`);
561
+ const short = typeof optRecord.alias === "string" ? optRecord.alias.trim() : "";
562
+ if (short)
563
+ commandFlags.add(`-${short}`);
564
+ }
565
+ const invalidHelpTail = rawHelpTail.filter((token) => token.startsWith("-") && !HELP_TAIL_OPTIONS.has(token) && !commandFlags.has(token));
566
+ if (invalidHelpTail.length > 0) {
567
+ throw usageError({
568
+ spec: helpSpec,
569
+ command: "help",
570
+ message: `Unsupported flag(s) after --help: ${invalidHelpTail.join(", ")}.`,
571
+ });
572
+ }
573
+ const helpTail = rawHelpTail.filter((token) => token.startsWith("-") && HELP_TAIL_OPTIONS.has(token));
574
+ return await runCli2HelpFast(["help", ...matchedHelp.entry.spec.id, ...helpTail]);
575
+ }
393
576
  return await runCli2HelpFast(["help", ...rest]);
394
577
  }
395
578
  if (rest.length === 0) {
@@ -451,7 +634,7 @@ export async function runCli(argv) {
451
634
  await maybeWarnOnUpdate({
452
635
  currentVersion: getVersion(),
453
636
  skip: globals.noUpdateCheck || skipUpdateCheckForPolicy || matched?.entry.needsConfig === false,
454
- jsonErrors: globals.jsonErrors,
637
+ jsonErrors,
455
638
  });
456
639
  let ctxPromise = null;
457
640
  const getCtx = async (commandForErrorContext) => {
@@ -493,7 +676,11 @@ export async function runCli(argv) {
493
676
  if (match) {
494
677
  const tail = rest.slice(match.consumed);
495
678
  const parsed = parseCommandArgv(match.spec, tail).parsed;
496
- return await match.handler({ cwd, rootOverride: globals.root }, parsed);
679
+ return await runWithOutputMode({
680
+ mode: outputMode,
681
+ command: match.spec.id.join(" "),
682
+ run: async () => await match.handler({ cwd, rootOverride: globals.root }, parsed),
683
+ });
497
684
  }
498
685
  const input = rest.join(" ");
499
686
  const fullCandidates = registry.list().map((e) => e.spec.id.join(" "));
@@ -1 +1 @@
1
- {"version":3,"file":"sync.command.d.ts","sourceRoot":"","sources":["../../../src/commands/backend/sync.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAwB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE7E,KAAK,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAQtD,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,iBAAiB,CAuC1D,CAAC;AAEF,iBAAS,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAUpF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,8BAEtF;AAED,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,CAStE"}
1
+ {"version":3,"file":"sync.command.d.ts","sourceRoot":"","sources":["../../../src/commands/backend/sync.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAItE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAwB,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE7E,KAAK,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAQtD,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,iBAAiB,CAuC1D,CAAC;AAEF,iBAAS,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAUpF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,8BAEtF;AAED,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,CAStE"}
@@ -1,3 +1,4 @@
1
+ import { COMMAND_SNIPPETS } from "../../cli/command-snippets.js";
1
2
  import { usageError } from "../../cli/spec/errors.js";
2
3
  import { suggestOne } from "../../cli/spec/suggest.js";
3
4
  import { cmdBackendSyncParsed } from "../backend.js";
@@ -7,7 +8,7 @@ export const backendSpec = {
7
8
  summary: "Backend-related operations.",
8
9
  description: "This is a command group. Use a subcommand such as `agentplane backend sync ...`.",
9
10
  args: [{ name: "cmd", required: false, variadic: true, valueHint: "<cmd>" }],
10
- examples: [{ cmd: "agentplane backend sync local --direction pull", why: "Sync the backend." }],
11
+ examples: [{ cmd: COMMAND_SNIPPETS.backendSync.pullLocal, why: "Sync the backend." }],
11
12
  parse: (raw) => ({ cmd: (raw.args.cmd ?? []) }),
12
13
  };
13
14
  export const backendSyncSpec = {
@@ -21,8 +22,8 @@ export const backendSyncSpec = {
21
22
  name: "direction",
22
23
  valueHint: "<push|pull>",
23
24
  choices: ["push", "pull"],
24
- required: true,
25
- description: "Sync direction.",
25
+ default: "push",
26
+ description: "Sync direction (default: push).",
26
27
  },
27
28
  {
28
29
  kind: "string",
@@ -36,15 +37,15 @@ export const backendSyncSpec = {
36
37
  { kind: "boolean", name: "quiet", default: false, description: "Reduce output noise." },
37
38
  ],
38
39
  examples: [
39
- { cmd: "agentplane backend sync local --direction pull", why: "Pull from backend." },
40
+ { cmd: COMMAND_SNIPPETS.backendSync.pullLocal, why: "Pull from backend." },
40
41
  {
41
- cmd: "agentplane backend sync redmine --direction push --yes",
42
+ cmd: COMMAND_SNIPPETS.backendSync.pushRedmineWithYes,
42
43
  why: "Push to a networked backend with explicit approval.",
43
44
  },
44
45
  ],
45
46
  parse: (raw) => ({
46
47
  backendId: String(raw.args.id),
47
- direction: raw.opts.direction,
48
+ direction: (raw.opts.direction ?? "push"),
48
49
  conflict: (raw.opts.conflict ?? "diff"),
49
50
  yes: raw.opts.yes === true,
50
51
  quiet: raw.opts.quiet === true,
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/commands/backend.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGnF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;IAC7D,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;IAC7D,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAwClB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwClB"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/commands/backend.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGnF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;IAC7D,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;IAC7D,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAyClB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyClB"}
@@ -15,6 +15,7 @@ export async function cmdBackendSyncParsed(opts) {
15
15
  exitCode: 2,
16
16
  code: "E_USAGE",
17
17
  message: `Configured backend is "${backendId}", not "${opts.flags.backendId}"`,
18
+ context: { command: "backend sync", reason_code: "sync_backend_mismatch" },
18
19
  });
19
20
  }
20
21
  if (!backend.sync) {
@@ -57,6 +58,7 @@ export async function cmdSyncParsed(opts) {
57
58
  exitCode: 2,
58
59
  code: "E_USAGE",
59
60
  message: `Configured backend is "${backendId}", not "${opts.flags.backendId}"`,
61
+ context: { command: "sync", reason_code: "sync_backend_mismatch" },
60
62
  });
61
63
  }
62
64
  if (!backend.sync) {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.run.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.run.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAsPrD,eAAO,MAAM,SAAS,EAAE,cAAc,CAAC,YAAY,CAwBlD,CAAC"}
1
+ {"version":3,"file":"doctor.run.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.run.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAM1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAyUrD,eAAO,MAAM,SAAS,EAAE,cAAc,CAAC,YAAY,CAmDlD,CAAC"}
@@ -3,8 +3,10 @@ import path from "node:path";
3
3
  import { resolveProject } from "@agentplaneorg/core";
4
4
  import { warnMessage, successMessage } from "../cli/output.js";
5
5
  import { RUNTIME_GITIGNORE_LINES } from "../shared/runtime-artifacts.js";
6
+ import { resolvePolicyGatewayForRepo } from "../shared/policy-gateway.js";
6
7
  import { execFileAsync, gitEnv } from "./shared/git.js";
7
8
  import { loadCommandContext } from "./shared/task-backend.js";
9
+ import { emitWorkflowEvent, isWorkflowEnforcementDisabled, resolveWorkflowPaths, safeAutofixWorkflowText, validateWorkflowAtPath, workflowEnforcementEnvHint, } from "../workflow-runtime/index.js";
8
10
  async function listTsFiles(rootDir) {
9
11
  const out = [];
10
12
  async function walk(absDir) {
@@ -57,15 +59,19 @@ async function isDirectory(absPath) {
57
59
  }
58
60
  async function checkWorkspace(repoRoot) {
59
61
  const problems = [];
60
- const requiredFiles = [
61
- path.join(repoRoot, "AGENTS.md"),
62
- path.join(repoRoot, ".agentplane", "config.json"),
63
- ];
62
+ const requiredFiles = [path.join(repoRoot, ".agentplane", "config.json")];
64
63
  for (const filePath of requiredFiles) {
65
64
  if (!(await pathExists(filePath))) {
66
65
  problems.push(`Missing required file: ${path.relative(repoRoot, filePath)}`);
67
66
  }
68
67
  }
68
+ const gateway = await resolvePolicyGatewayForRepo({
69
+ gitRoot: repoRoot,
70
+ fallbackFlavor: "codex",
71
+ });
72
+ if (!(await pathExists(gateway.absPath))) {
73
+ problems.push("Missing required policy gateway file: AGENTS.md or CLAUDE.md");
74
+ }
69
75
  const agentsDir = path.join(repoRoot, ".agentplane", "agents");
70
76
  if (!(await isDirectory(agentsDir))) {
71
77
  problems.push("Missing required directory: .agentplane/agents");
@@ -160,6 +166,75 @@ async function safeFixTaskIndex(repoRoot) {
160
166
  return { changed: false, note: "Skip: could not rebuild tasks index cache." };
161
167
  }
162
168
  }
169
+ async function checkWorkflowContract(repoRoot) {
170
+ const result = await validateWorkflowAtPath(repoRoot);
171
+ const findings = result.diagnostics.map((d) => `[${d.severity}] ${d.code} ${d.path}: ${d.message}`);
172
+ emitWorkflowEvent({
173
+ event: "workflow_doctor_check",
174
+ details: { ok: result.ok, findings: result.diagnostics.length },
175
+ });
176
+ return findings;
177
+ }
178
+ function findingSeverity(problem) {
179
+ const normalized = problem.trimStart();
180
+ if (normalized.startsWith("[WARN]"))
181
+ return "WARN";
182
+ if (normalized.startsWith("[INFO]"))
183
+ return "INFO";
184
+ if (normalized.startsWith("[ERROR]"))
185
+ return "ERROR";
186
+ return "ERROR";
187
+ }
188
+ async function safeFixWorkflow(repoRoot) {
189
+ const paths = resolveWorkflowPaths(repoRoot);
190
+ let current = "";
191
+ let sourcePath = paths.workflowPath;
192
+ try {
193
+ current = await fs.readFile(paths.workflowPath, "utf8");
194
+ }
195
+ catch {
196
+ try {
197
+ current = await fs.readFile(paths.legacyWorkflowPath, "utf8");
198
+ sourcePath = paths.legacyWorkflowPath;
199
+ }
200
+ catch {
201
+ return { changed: false, note: "Skip: workflow contract file not found." };
202
+ }
203
+ }
204
+ const fixed = safeAutofixWorkflowText(current);
205
+ if (fixed.diagnostics.some((d) => d.code === "WF_FIX_SKIPPED_UNSAFE")) {
206
+ const details = fixed.diagnostics.map((d) => `${d.path}`).join(", ");
207
+ return {
208
+ changed: false,
209
+ note: `Skip: unsafe workflow autofix required (unknown keys). Proposed manual review: ${details}`,
210
+ };
211
+ }
212
+ if (!fixed.changed) {
213
+ if (sourcePath === paths.workflowPath) {
214
+ return { changed: false, note: "OK: workflow contract already normalized." };
215
+ }
216
+ await fs.mkdir(path.dirname(paths.workflowPath), { recursive: true });
217
+ await fs.writeFile(paths.workflowPath, current, "utf8");
218
+ await fs.rm(paths.legacyWorkflowPath, { force: true });
219
+ await fs.mkdir(paths.workflowDir, { recursive: true });
220
+ await fs.copyFile(paths.workflowPath, paths.lastKnownGoodPath);
221
+ return {
222
+ changed: true,
223
+ note: "Fixed: moved legacy WORKFLOW.md into .agentplane and refreshed last-known-good.",
224
+ };
225
+ }
226
+ await fs.mkdir(path.dirname(paths.workflowPath), { recursive: true });
227
+ await fs.writeFile(paths.workflowPath, fixed.text, "utf8");
228
+ if (sourcePath === paths.legacyWorkflowPath) {
229
+ await fs.rm(paths.legacyWorkflowPath, { force: true });
230
+ }
231
+ await fs.mkdir(paths.workflowDir, { recursive: true });
232
+ await fs.copyFile(paths.workflowPath, paths.lastKnownGoodPath);
233
+ return {
234
+ changed: true,
235
+ note: "Fixed: normalized workflow contract and refreshed last-known-good.",
236
+ };
237
+ }
163
238
  async function checkDoneTaskCommitInvariants(repoRoot) {
164
239
  const tasksPath = path.join(repoRoot, ".agentplane", "tasks.json");
165
240
  let raw = "";
@@ -216,11 +291,11 @@ async function checkDoneTaskCommitInvariants(repoRoot) {
216
291
  continue;
217
292
  const subject = subjectByHash.get(hash) ?? "";
218
293
  if (!subject) {
219
- problems.push(`DONE task references unknown commit hash: ${id} -> ${hash}`);
294
+ problems.push(`[WARN] DONE task references unknown commit hash: ${id} -> ${hash}`);
220
295
  continue;
221
296
  }
222
297
  if (/\bclose:/iu.test(subject)) {
223
- problems.push(`DONE task implementation commit points to a close commit: ${id} -> ${hash} (${subject})`);
298
+ problems.push(`[WARN] DONE task implementation commit points to a close commit: ${id} -> ${hash} (${subject})`);
224
299
  }
225
300
  }
226
301
  return problems;
@@ -228,22 +303,38 @@ async function checkDoneTaskCommitInvariants(repoRoot) {
228
303
  export const runDoctor = async (ctx, p) => {
229
304
  const resolved = await resolveProject({ cwd: ctx.cwd, rootOverride: ctx.rootOverride ?? null });
230
305
  const repoRoot = resolved.gitRoot;
231
- const problems = await checkWorkspace(repoRoot);
232
- problems.push(...(await checkDoneTaskCommitInvariants(repoRoot)));
233
- if (p.dev) {
234
- problems.push(...(await checkLayering(repoRoot)));
235
- }
236
- if (problems.length > 0) {
237
- console.error(warnMessage(`doctor found ${problems.length} problem(s):`));
238
- for (const prob of problems)
239
- console.error(`- ${prob}`);
240
- return 1;
306
+ const runChecks = async () => {
307
+ const checks = await checkWorkspace(repoRoot);
308
+ checks.push(...(await checkDoneTaskCommitInvariants(repoRoot)));
309
+ if (!isWorkflowEnforcementDisabled()) {
310
+ checks.push(...(await checkWorkflowContract(repoRoot)));
311
+ }
312
+ if (p.dev) {
313
+ checks.push(...(await checkLayering(repoRoot)));
314
+ }
315
+ return checks;
316
+ };
317
+ if (isWorkflowEnforcementDisabled()) {
318
+ console.log(successMessage("doctor", undefined, `workflow contract checks disabled via ${workflowEnforcementEnvHint()}.`));
241
319
  }
242
320
  if (p.fix) {
243
321
  const fix = await safeFixGitignore(repoRoot);
244
322
  console.log(successMessage("doctor fix", undefined, fix.note));
245
323
  const idx = await safeFixTaskIndex(repoRoot);
246
324
  console.log(successMessage("doctor fix", undefined, idx.note));
325
+ const workflowFix = await safeFixWorkflow(repoRoot);
326
+ console.log(successMessage("doctor fix", undefined, workflowFix.note));
327
+ }
328
+ const problems = await runChecks();
329
+ const errors = problems.filter((problem) => findingSeverity(problem) === "ERROR");
330
+ if (problems.length > 0) {
331
+ const warningCount = problems.filter((problem) => findingSeverity(problem) === "WARN").length;
332
+ const infoCount = problems.filter((problem) => findingSeverity(problem) === "INFO").length;
333
+ console.error(warnMessage(`doctor findings: errors=${errors.length} warnings=${warningCount} info=${infoCount}`));
334
+ for (const prob of problems)
335
+ console.error(`- ${prob}`);
336
+ if (errors.length > 0)
337
+ return 1;
247
338
  }
248
339
  console.log(successMessage("doctor", undefined, "OK"));
249
340
  return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commands.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAOvF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAyDxE,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,CAS9E;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,CAgJlB"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commands.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAQvF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAyDxE,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,CAmJlB"}
@@ -4,6 +4,7 @@ import { CliError } from "../../../shared/errors.js";
4
4
  import { loadCommandContext } from "../../shared/task-backend.js";
5
5
  import { loadTaskFromContext } from "../../shared/task-backend.js";
6
6
  import { execFileAsync, gitEnv } from "../../shared/git.js";
7
+ import { ensureReconciledBeforeMutation } from "../../shared/reconcile-check.js";
7
8
  import { suggestAllowPrefixes } from "./allow.js";
8
9
  import { buildCloseCommitMessage, taskReadmePathForTask } from "./close-message.js";
9
10
  import { buildGitCommitEnv } from "./env.js";
@@ -111,7 +112,10 @@ export async function cmdGuardSuggestAllow(opts) {
111
112
  }
112
113
  export async function cmdGuardCommit(opts) {
113
114
  try {
114
- await guardCommitCheck(opts);
115
+ const ctx = opts.ctx ??
116
+ (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
117
+ await ensureReconciledBeforeMutation({ ctx, command: "guard commit" });
118
+ await guardCommitCheck({ ...opts, ctx });
115
119
  if (!opts.quiet)
116
120
  process.stdout.write("OK\n");
117
121
  return 0;
@@ -124,9 +128,12 @@ export async function cmdGuardCommit(opts) {
124
128
  }
125
129
  export async function cmdCommit(opts) {
126
130
  try {
131
+ const ctx = opts.ctx ??
132
+ (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
127
133
  if (opts.close) {
128
- const ctx = opts.ctx ??
129
- (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
134
+ if (!opts.closeCheckOnly) {
135
+ await ensureReconciledBeforeMutation({ ctx, command: "commit" });
136
+ }
130
137
  // Make the close commit deterministic: start from a clean index unless --unstage-others is used.
131
138
  let staged = await ctx.git.statusStagedPaths();
132
139
  if (staged.length > 0 && opts.closeUnstageOthers) {
@@ -208,8 +215,9 @@ export async function cmdCommit(opts) {
208
215
  message: "--auto-allow is disabled; pass explicit --allow <path-prefix>.",
209
216
  });
210
217
  }
218
+ await ensureReconciledBeforeMutation({ ctx, command: "commit" });
211
219
  await guardCommitCheck({
212
- ctx: opts.ctx,
220
+ ctx,
213
221
  cwd: opts.cwd,
214
222
  rootOverride: opts.rootOverride,
215
223
  taskId: opts.taskId,
@@ -224,8 +232,6 @@ export async function cmdCommit(opts) {
224
232
  requireClean: opts.requireClean,
225
233
  quiet: opts.quiet,
226
234
  });
227
- const ctx = opts.ctx ??
228
- (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
229
235
  const env = buildGitCommitEnv({
230
236
  taskId: opts.taskId,
231
237
  allowTasks: opts.allowTasks,
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../../../src/commands/recipes/impl/commands/install.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAM3E,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,GAAG,EAAE,OAAO,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAkMlB"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../../../src/commands/recipes/impl/commands/install.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAM3E,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,GAAG,EAAE,OAAO,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CA6NlB"}