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
@@ -0,0 +1,31 @@
1
+ # Workflow: release
2
+
3
+ Use this module when task touches release/version/publish flows.
4
+
5
+ ## Required sequence
6
+
7
+ 1. CHECKPOINT A: confirm clean tracked tree and approved scope.
8
+ 2. CHECKPOINT B: generate release plan and freeze version/tag target.
9
+ 3. Generate release notes with complete human-readable coverage of all task-level changes.
10
+ 4. Run release prepublish checks.
11
+ 5. CHECKPOINT C: apply release and push/tag only after all gates pass.
12
+ 6. Record release evidence (commands, outputs, resulting version/tag).
13
+
14
+ ## Command contract
15
+
16
+ ```bash
17
+ git status --short --untracked-files=no
18
+ agentplane task plan set <task-id> --text "Release plan: version=<v>, tag=<t>, scope=<...>" --updated-by <ROLE>
19
+ agentplane task plan approve <task-id> --by ORCHESTRATOR
20
+ agentplane release plan --patch
21
+ agentplane release apply --push --yes
22
+ agentplane verify <task-id> --ok|--rework --by <ROLE> --note "Release checks: ..."
23
+ agentplane finish <task-id> --author <ROLE> --body "Verified: release" --result "Release <v> published" --commit <git-rev> --close-commit
24
+ ```
25
+
26
+ ## Constraints
27
+
28
+ - MUST NOT perform irreversible release actions before explicit approval.
29
+ - MUST NOT skip parity/version checks.
30
+ - MUST NOT bypass required notes validation.
31
+ - MUST stop and request re-approval if release scope/tag/version changes.
@@ -0,0 +1,20 @@
1
+ # Workflow: upgrade
2
+
3
+ Use this module when task runs `agentplane upgrade` or touches `.agentplane/.upgrade/**`.
4
+
5
+ ## Required sequence
6
+
7
+ 1. Run upgrade command and capture run directory.
8
+ 2. Read upgrade review report:
9
+ - agent mode: `.agentplane/.upgrade/agent/<runId>/review.json`
10
+ - auto mode: `.agentplane/.upgrade/last-review.json`
11
+ 3. Apply upgrade as replace-all for managed files (`agentplane upgrade --auto`), excluding task data paths.
12
+ 4. For `.agentplane/policy/incidents.md`, keep existing local content and append incoming policy content (never replace non-empty local incidents file).
13
+ 5. Ensure the upgrade produced a dedicated upgrade commit with version in commit message.
14
+ 6. Verify policy/agent consistency and routing checks.
15
+ 7. Record run path and reviewed files in task notes.
16
+
17
+ ## Minimum verification
18
+
19
+ - `node .agentplane/policy/check-routing.mjs`
20
+ - `agentplane agents`
package/bin/agentplane.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import path from "node:path";
3
- import { readdir, stat } from "node:fs/promises";
3
+ import { stat } from "node:fs/promises";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { distExists, isPackageBuildFresh } from "./dist-guard.js";
5
6
 
6
7
  async function exists(p) {
7
8
  try {
@@ -35,47 +36,18 @@ async function maybeWarnGlobalBinaryInRepoCheckout() {
35
36
  );
36
37
  }
37
38
 
38
- function isTestLikePath(absPath) {
39
- // The repo build does not emit test files to dist. If we treat test mtimes as
40
- // "src is newer than dist", we can block normal commits that only change tests.
41
- const normalized = absPath.replaceAll("\\", "/");
42
- if (normalized.includes("/__snapshots__/")) return true;
43
- if (normalized.endsWith(".snap")) return true;
44
- if (/\.(unit\.)?test\.[cm]?ts$/.test(normalized)) return true;
45
- if (/\.(unit\.)?test\.tsx$/.test(normalized)) return true;
39
+ function isHooksRunCommitMsgInvocation(argv) {
40
+ const args = argv.slice(2).map((value) => String(value ?? "").trim());
41
+ for (let i = 0; i < args.length; i += 1) {
42
+ if (args[i] !== "hooks") continue;
43
+ return args[i + 1] === "run" && args[i + 2] === "commit-msg";
44
+ }
46
45
  return false;
47
46
  }
48
47
 
49
- // Keep this file dependency-free and simple: rely on directory mtime scans below.
50
- async function newestMtimeMsInDir(dir) {
51
- let newest = 0;
52
- const stack = [dir];
53
- while (stack.length > 0) {
54
- const current = stack.pop();
55
- if (!current) continue;
56
- let entries;
57
- try {
58
- entries = await readdir(current, { withFileTypes: true });
59
- } catch {
60
- continue;
61
- }
62
- for (const entry of entries) {
63
- const abs = path.join(current, entry.name);
64
- if (entry.isDirectory()) {
65
- stack.push(abs);
66
- continue;
67
- }
68
- if (!entry.isFile()) continue;
69
- if (isTestLikePath(abs)) continue;
70
- try {
71
- const s = await stat(abs);
72
- if (s.mtimeMs > newest) newest = s.mtimeMs;
73
- } catch {
74
- // ignore
75
- }
76
- }
77
- }
78
- return newest;
48
+ function isPathInside(baseDir, targetPath) {
49
+ const rel = path.relative(path.resolve(baseDir), path.resolve(targetPath));
50
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
79
51
  }
80
52
 
81
53
  async function assertDistUpToDate() {
@@ -85,8 +57,7 @@ async function assertDistUpToDate() {
85
57
  if (!inRepo) return true;
86
58
 
87
59
  const allowStale = (process.env.AGENTPLANE_DEV_ALLOW_STALE_DIST ?? "").trim() === "1";
88
- const agentplaneDistDir = path.join(agentplaneRoot, "dist");
89
- if (!(await exists(agentplaneDistDir))) {
60
+ if (!(await distExists(agentplaneRoot))) {
90
61
  process.stderr.write(
91
62
  "error: agentplane dist is missing for this repo checkout.\n" +
92
63
  "Fix:\n" +
@@ -97,29 +68,48 @@ async function assertDistUpToDate() {
97
68
  return false;
98
69
  }
99
70
 
100
- const agentplaneSrcDir = path.join(agentplaneRoot, "src");
101
- const agentplaneSrcNewest = await newestMtimeMsInDir(agentplaneSrcDir);
102
- const agentplaneDistNewest = await newestMtimeMsInDir(agentplaneDistDir);
103
- const isStaleAgentplane = agentplaneSrcNewest > agentplaneDistNewest;
104
-
105
- // If we're in the monorepo, also check core dist because the CLI imports it.
106
71
  const repoRoot = path.resolve(agentplaneRoot, "..", "..");
107
72
  const coreRoot = path.join(repoRoot, "packages", "core");
108
- const coreSrcDir = path.join(coreRoot, "src");
109
- const coreDistDir = path.join(coreRoot, "dist");
110
- let isStaleCore = false;
111
- if ((await exists(coreSrcDir)) && (await exists(coreDistDir))) {
112
- const coreSrcNewest = await newestMtimeMsInDir(coreSrcDir);
113
- const coreDistNewest = await newestMtimeMsInDir(coreDistDir);
114
- isStaleCore = coreSrcNewest > coreDistNewest;
73
+ const checks = [
74
+ {
75
+ name: "agentplane",
76
+ root: agentplaneRoot,
77
+ watchedPaths: ["src", "bin/agentplane.js", "bin/dist-guard.js"],
78
+ },
79
+ ];
80
+ if (await exists(path.join(coreRoot, "src")))
81
+ checks.push({ name: "core", root: coreRoot, watchedPaths: ["src"] });
82
+
83
+ const staleReasons = [];
84
+ for (const check of checks) {
85
+ const result = await isPackageBuildFresh(check.root, { watchedPaths: check.watchedPaths });
86
+ if (!result.ok) {
87
+ const detail =
88
+ Array.isArray(result.changedPaths) && result.changedPaths.length > 0
89
+ ? `(${result.changedPaths.slice(0, 5).join(", ")}${result.changedPaths.length > 5 ? ", ..." : ""})`
90
+ : "";
91
+ staleReasons.push(`${check.name}:${result.reason}${detail}`);
92
+ }
115
93
  }
116
94
 
117
- if ((isStaleAgentplane || isStaleCore) && !allowStale) {
95
+ if (staleReasons.length > 0 && !allowStale) {
96
+ const runningInsideCheckout = isPathInside(repoRoot, process.cwd());
97
+ if (!runningInsideCheckout) {
98
+ process.stderr.write(
99
+ "warning: linked development binary has a stale build, but current working directory is outside the agentplane checkout.\n" +
100
+ "proceeding with existing dist output.\n" +
101
+ `detected: ${staleReasons.join(", ")}\n` +
102
+ "tip: rebuild (`bun run --filter=@agentplaneorg/core build && bun run --filter=agentplane build`) or reinstall from npm for stable global usage.\n",
103
+ );
104
+ return true;
105
+ }
106
+
118
107
  process.stderr.write(
119
- "error: refusing to run a stale repo build (dist is older than src).\n" +
108
+ "error: refusing to run a stale repo build (manifest/git quick-check failed).\n" +
120
109
  "Fix:\n" +
121
110
  " bun run --filter=@agentplaneorg/core build\n" +
122
111
  " bun run --filter=agentplane build\n" +
112
+ `Detected: ${staleReasons.join(", ")}\n` +
123
113
  "Override (not recommended): set AGENTPLANE_DEV_ALLOW_STALE_DIST=1\n",
124
114
  );
125
115
  process.exitCode = 2;
@@ -130,5 +120,5 @@ async function assertDistUpToDate() {
130
120
  }
131
121
 
132
122
  await maybeWarnGlobalBinaryInRepoCheckout();
133
- const ok = await assertDistUpToDate();
123
+ const ok = isHooksRunCommitMsgInvocation(process.argv) ? true : await assertDistUpToDate();
134
124
  if (ok) await import("../dist/cli.js");
@@ -0,0 +1,124 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { readFile, stat } from "node:fs/promises";
4
+
5
+ async function exists(p) {
6
+ try {
7
+ await stat(p);
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ async function readJsonIfExists(p) {
15
+ let raw = "";
16
+ try {
17
+ raw = await readFile(p, "utf8");
18
+ } catch {
19
+ return null;
20
+ }
21
+ try {
22
+ return JSON.parse(raw);
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ function resolveGitHead(cwd) {
29
+ try {
30
+ return execFileSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" }).trim() || null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ function listGitPaths(cwd, args) {
37
+ try {
38
+ const out = execFileSync("git", args, { cwd, encoding: "utf8" });
39
+ return out
40
+ .split(/\r?\n/u)
41
+ .map((line) => line.trim())
42
+ .filter(Boolean);
43
+ } catch {
44
+ return [];
45
+ }
46
+ }
47
+
48
+ function uniqueSorted(values) {
49
+ return [...new Set(values)].toSorted((a, b) => a.localeCompare(b));
50
+ }
51
+
52
+ function workingTreeChangedPaths(cwd, watchedPaths) {
53
+ const lines = listGitPaths(cwd, [
54
+ "status",
55
+ "--porcelain",
56
+ "--untracked-files=all",
57
+ "--",
58
+ ...watchedPaths,
59
+ ]);
60
+ return uniqueSorted(lines.map((line) => line.slice(3).trim()).filter(Boolean));
61
+ }
62
+
63
+ function committedChangedPathsSince(cwd, fromGitHead, watchedPaths) {
64
+ if (!fromGitHead) return [];
65
+ return uniqueSorted(
66
+ listGitPaths(cwd, ["diff", "--name-only", `${fromGitHead}..HEAD`, "--", ...watchedPaths]),
67
+ );
68
+ }
69
+
70
+ async function fileMtimeMs(p) {
71
+ try {
72
+ const s = await stat(p);
73
+ if (!s.isFile()) return null;
74
+ return s.mtimeMs;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ export async function isPackageBuildFresh(packageRoot, options = {}) {
81
+ const watchedPaths = options.watchedPaths ?? ["src"];
82
+ const manifestPath = path.join(packageRoot, "dist", ".build-manifest.json");
83
+ const manifest = await readJsonIfExists(manifestPath);
84
+ if (!manifest || manifest.schema_version !== 1) {
85
+ return { ok: false, reason: "manifest_missing", changedPaths: [] };
86
+ }
87
+
88
+ const currentHead = resolveGitHead(packageRoot);
89
+ const changedPaths = uniqueSorted([
90
+ ...committedChangedPathsSince(packageRoot, manifest.git_head, watchedPaths),
91
+ ...workingTreeChangedPaths(packageRoot, watchedPaths),
92
+ ]);
93
+
94
+ if (changedPaths.length > 0) {
95
+ return { ok: false, reason: "watched_paths_changed", changedPaths };
96
+ }
97
+
98
+ const srcCliMtimeMs = await fileMtimeMs(path.join(packageRoot, "src", "cli.ts"));
99
+ const srcIndexMtimeMs = await fileMtimeMs(path.join(packageRoot, "src", "index.ts"));
100
+ if (
101
+ typeof manifest.src_cli_mtime_ms === "number" &&
102
+ typeof srcCliMtimeMs === "number" &&
103
+ srcCliMtimeMs > manifest.src_cli_mtime_ms
104
+ ) {
105
+ return { ok: false, reason: "src_cli_newer_than_manifest", changedPaths: [] };
106
+ }
107
+ if (
108
+ typeof manifest.src_index_mtime_ms === "number" &&
109
+ typeof srcIndexMtimeMs === "number" &&
110
+ srcIndexMtimeMs > manifest.src_index_mtime_ms
111
+ ) {
112
+ return { ok: false, reason: "src_index_newer_than_manifest", changedPaths: [] };
113
+ }
114
+
115
+ if (manifest.git_head && currentHead && manifest.git_head !== currentHead) {
116
+ return { ok: true, reason: "fresh_after_non_runtime_head_change", changedPaths: [] };
117
+ }
118
+
119
+ return { ok: true, reason: "fresh", changedPaths: [] };
120
+ }
121
+
122
+ export async function distExists(packageRoot) {
123
+ return await exists(path.join(packageRoot, "dist"));
124
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "schema_version": 1,
3
+ "package_dir": "/home/runner/work/agentplane/agentplane/packages/agentplane",
4
+ "generated_at": "2026-03-06T15:25:37.651Z",
5
+ "git_head": "1be96fc47916b76925f9bc59ce7d24176216a87f",
6
+ "src_cli_mtime_ms": 1772810682734.0005,
7
+ "src_index_mtime_ms": null,
8
+ "dist_cli_mtime_ms": 1772810737168.81,
9
+ "dist_index_mtime_ms": null,
10
+ "tsbuildinfo_mtime_ms": 1772810737234.8098
11
+ }
@@ -1,10 +1,17 @@
1
+ import { type PolicyGatewayFlavor } from "../shared/policy-gateway.js";
1
2
  export type WorkflowMode = "direct" | "branch_pr";
2
3
  type AgentTemplate = {
3
4
  fileName: string;
4
5
  contents: string;
5
6
  };
7
+ export type PolicyTemplate = {
8
+ relativePath: string;
9
+ contents: string;
10
+ };
6
11
  export declare function loadAgentsTemplate(): Promise<string>;
7
12
  export declare function loadAgentTemplates(): Promise<AgentTemplate[]>;
13
+ export declare function loadPolicyTemplates(): Promise<PolicyTemplate[]>;
8
14
  export declare function filterAgentsByWorkflow(template: string, workflow: WorkflowMode): string;
15
+ export declare function loadPolicyGatewayTemplate(flavor: PolicyGatewayFlavor): Promise<string>;
9
16
  export {};
10
17
  //# sourceMappingURL=agents-template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agents-template.d.ts","sourceRoot":"","sources":["../../src/agents/agents-template.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElD,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAwC5D,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAG1D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAanE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,MAAM,CASvF"}
1
+ {"version":3,"file":"agents-template.d.ts","sourceRoot":"","sources":["../../src/agents/agents-template.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAC;AAUrC,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElD,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAwCxE,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE1D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAanE;AAsBD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAerE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,MAAM,CASvF;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAI5F"}
@@ -1,8 +1,10 @@
1
1
  import { readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { policyGatewayFileName, renderPolicyGatewayTemplateText, } from "../shared/policy-gateway.js";
4
5
  const AGENTS_TEMPLATE_URL = new URL("../../assets/AGENTS.md", import.meta.url);
5
6
  const AGENTS_DIR_URL = new URL("../../assets/agents/", import.meta.url);
7
+ const POLICY_DIR_URL = new URL("../../assets/policy/", import.meta.url);
6
8
  const HEADING_RE = /^(#+)\s+(.*)$/;
7
9
  function ensureTrailingNewline(text) {
8
10
  return text.endsWith("\n") ? text : `${text}\n`;
@@ -41,8 +43,7 @@ function removeSections(lines, titles) {
41
43
  return lines.filter((_line, index) => !shouldRemove.has(index));
42
44
  }
43
45
  export async function loadAgentsTemplate() {
44
- const text = await readFile(AGENTS_TEMPLATE_URL, "utf8");
45
- return ensureTrailingNewline(text.trimEnd());
46
+ return loadPolicyGatewayTemplate("codex");
46
47
  }
47
48
  export async function loadAgentTemplates() {
48
49
  const dirPath = fileURLToPath(AGENTS_DIR_URL);
@@ -56,6 +57,39 @@ export async function loadAgentTemplates() {
56
57
  }
57
58
  return templates;
58
59
  }
60
+ async function listFilesRecursive(dirPath, relPrefix = "") {
61
+ const entries = await readdir(dirPath, { withFileTypes: true });
62
+ const sorted = entries.toSorted((a, b) => a.name.localeCompare(b.name));
63
+ const files = [];
64
+ for (const entry of sorted) {
65
+ // Ignore editor/OS hidden metadata files.
66
+ if (entry.name.startsWith("."))
67
+ continue;
68
+ const absPath = path.join(dirPath, entry.name);
69
+ const relPath = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
70
+ if (entry.isDirectory()) {
71
+ files.push(...(await listFilesRecursive(absPath, relPath)));
72
+ continue;
73
+ }
74
+ if (entry.isFile())
75
+ files.push(relPath);
76
+ }
77
+ return files;
78
+ }
79
+ export async function loadPolicyTemplates() {
80
+ const dirPath = fileURLToPath(POLICY_DIR_URL);
81
+ const relFiles = await listFilesRecursive(dirPath);
82
+ const templates = [];
83
+ for (const relFile of relFiles) {
84
+ const filePath = path.join(dirPath, relFile);
85
+ const contents = await readFile(filePath, "utf8");
86
+ templates.push({
87
+ relativePath: relFile.replaceAll("\\", "/"),
88
+ contents: ensureTrailingNewline(contents.trimEnd()),
89
+ });
90
+ }
91
+ return templates;
92
+ }
59
93
  export function filterAgentsByWorkflow(template, workflow) {
60
94
  const lines = template.replaceAll("\r\n", "\n").split("\n");
61
95
  const removeTitles = workflow === "direct"
@@ -64,3 +98,8 @@ export function filterAgentsByWorkflow(template, workflow) {
64
98
  const filtered = removeSections(lines, removeTitles);
65
99
  return ensureTrailingNewline(filtered.join("\n").trimEnd());
66
100
  }
101
+ export async function loadPolicyGatewayTemplate(flavor) {
102
+ const text = await readFile(AGENTS_TEMPLATE_URL, "utf8");
103
+ const rendered = renderPolicyGatewayTemplateText(text, policyGatewayFileName(flavor));
104
+ return ensureTrailingNewline(rendered.trimEnd());
105
+ }
@@ -3,6 +3,7 @@ export declare class LocalBackend implements TaskBackend {
3
3
  id: string;
4
4
  root: string;
5
5
  updatedBy: string;
6
+ private lastListWarnings;
6
7
  constructor(settings?: {
7
8
  dir?: string;
8
9
  updatedBy?: string;
@@ -12,6 +13,7 @@ export declare class LocalBackend implements TaskBackend {
12
13
  attempts: number;
13
14
  }): Promise<string>;
14
15
  listTasks(): Promise<TaskData[]>;
16
+ getLastListWarnings(): string[];
15
17
  getTask(taskId: string): Promise<TaskData | null>;
16
18
  getTasks(taskIds: string[]): Promise<(TaskData | null)[]>;
17
19
  getTaskDoc(taskId: string): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"local-backend.d.ts","sourceRoot":"","sources":["../../../src/backends/task-backend/local-backend.ts"],"names":[],"mappings":"AAqBA,OAAO,EAiBL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACd,MAAM,aAAa,CAAC;AAErB,qBAAa,YAAa,YAAW,WAAW;IAC9C,EAAE,SAAW;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;gBAEN,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAKrD,cAAc,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB3E,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAgHhC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAoBjD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC;IAKzD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3C,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAyExC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB1E,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvE,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5C,cAAc,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAoF/D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzD"}
1
+ {"version":3,"file":"local-backend.d.ts","sourceRoot":"","sources":["../../../src/backends/task-backend/local-backend.ts"],"names":[],"mappings":"AAqBA,OAAO,EAiBL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACd,MAAM,aAAa,CAAC;AAErB,qBAAa,YAAa,YAAW,WAAW;IAC9C,EAAE,SAAW;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,gBAAgB,CAAgB;gBAE5B,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAKrD,cAAc,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB3E,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAwHtC,mBAAmB,IAAI,MAAM,EAAE;IAIzB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAoBjD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC;IAKzD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3C,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAyExC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB1E,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvE,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5C,cAAc,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAoF/D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzD"}
@@ -9,6 +9,7 @@ export class LocalBackend {
9
9
  id = "local";
10
10
  root;
11
11
  updatedBy;
12
+ lastListWarnings = [];
12
13
  constructor(settings) {
13
14
  this.root = path.resolve(settings?.dir ?? ".agentplane/tasks");
14
15
  this.updatedBy = settings?.updatedBy ?? DEFAULT_DOC_UPDATED_BY;
@@ -38,6 +39,7 @@ export class LocalBackend {
38
39
  }
39
40
  async listTasks() {
40
41
  const tasks = [];
42
+ const warnings = [];
41
43
  const entries = await readdir(this.root, { withFileTypes: true }).catch(() => []);
42
44
  const indexPath = resolveTaskIndexPath(this.root);
43
45
  const cachedIndex = await loadTaskIndex(indexPath);
@@ -65,6 +67,7 @@ export class LocalBackend {
65
67
  stats = await stat(readme);
66
68
  }
67
69
  catch {
70
+ warnings.push(`skip:${dirName}: missing_or_unreadable_readme`);
68
71
  return null;
69
72
  }
70
73
  if (!stats.isFile())
@@ -79,6 +82,7 @@ export class LocalBackend {
79
82
  text = await readFile(readme, "utf8");
80
83
  }
81
84
  catch {
85
+ warnings.push(`skip:${dirName}: unreadable_readme`);
82
86
  return null;
83
87
  }
84
88
  let parsed;
@@ -86,11 +90,14 @@ export class LocalBackend {
86
90
  parsed = parseTaskReadme(text);
87
91
  }
88
92
  catch {
93
+ warnings.push(`skip:${dirName}: invalid_readme_frontmatter`);
89
94
  return null;
90
95
  }
91
96
  const fm = parsed.frontmatter;
92
- if (!isRecord(fm) || Object.keys(fm).length === 0)
97
+ if (!isRecord(fm) || Object.keys(fm).length === 0) {
98
+ warnings.push(`skip:${dirName}: empty_or_invalid_frontmatter`);
93
99
  return null;
100
+ }
94
101
  const taskId = (typeof fm.id === "string" ? fm.id : dirName).trim();
95
102
  const task = taskRecordToData({
96
103
  id: taskId,
@@ -141,8 +148,12 @@ export class LocalBackend {
141
148
  // Best-effort cache; ignore failures.
142
149
  }
143
150
  }
151
+ this.lastListWarnings = warnings;
144
152
  return tasks;
145
153
  }
154
+ getLastListWarnings() {
155
+ return [...this.lastListWarnings];
156
+ }
146
157
  async getTask(taskId) {
147
158
  const readme = taskReadmePath(this.root, taskId);
148
159
  let text = "";
@@ -1 +1 @@
1
- {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../../src/backends/task-backend/redmine/mapping.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,cAAc,CAAC;AAMtB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIhE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GAAG,QAAQ,GAAG,IAAI,CAwElB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,IAAI,EAAE,QAAQ,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7F,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyC1B"}
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../../src/backends/task-backend/redmine/mapping.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,cAAc,CAAC;AAMtB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIhE;AA2BD,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GAAG,QAAQ,GAAG,IAAI,CAwElB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,IAAI,EAAE,QAAQ,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7F,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyC1B"}
@@ -23,6 +23,31 @@ export function doneRatioForStatus(status) {
23
23
  return 100;
24
24
  return 0;
25
25
  }
26
+ function inferStatusFromIssue(issue) {
27
+ const statusVal = isRecord(issue.status) ? issue.status : null;
28
+ const statusName = toStringSafe(statusVal?.name).trim().toLowerCase();
29
+ const doneRatio = typeof issue.done_ratio === "number" ? issue.done_ratio : null;
30
+ const isClosed = statusVal?.is_closed === true;
31
+ // Redmine defaults (without explicit status_map) commonly use these ids.
32
+ const statusId = typeof statusVal?.id === "number" ? statusVal.id : null;
33
+ if (statusId === 2)
34
+ return "DOING";
35
+ if (statusId === 3 || statusId === 5 || statusId === 6)
36
+ return "DONE";
37
+ if (isClosed || (doneRatio !== null && doneRatio >= 100))
38
+ return "DONE";
39
+ if (statusName.includes("progress") || statusName.includes("doing"))
40
+ return "DOING";
41
+ if (statusName.includes("done") ||
42
+ statusName.includes("closed") ||
43
+ statusName.includes("resolved") ||
44
+ statusName.includes("complete")) {
45
+ return "DONE";
46
+ }
47
+ if (doneRatio !== null && doneRatio > 0)
48
+ return "DOING";
49
+ return "TODO";
50
+ }
26
51
  export function issueToTask(opts) {
27
52
  const taskId = opts.taskIdOverride ?? customFieldValue(opts.issue, opts.customFields.task_id);
28
53
  if (!taskId)
@@ -31,7 +56,7 @@ export function issueToTask(opts) {
31
56
  const statusId = statusVal && typeof statusVal.id === "number" ? statusVal.id : null;
32
57
  const status = statusId !== null && opts.reverseStatus.has(statusId)
33
58
  ? opts.reverseStatus.get(statusId)
34
- : "TODO";
59
+ : inferStatusFromIssue(opts.issue);
35
60
  const verifyVal = customFieldValue(opts.issue, opts.customFields.verify);
36
61
  const commitVal = customFieldValue(opts.issue, opts.customFields.commit);
37
62
  const docVal = customFieldValue(opts.issue, opts.customFields.doc);
@@ -25,6 +25,7 @@ export declare class RedmineBackend implements TaskBackend {
25
25
  cache: LocalBackend | null;
26
26
  issueCache: Map<string, Record<string, unknown>>;
27
27
  reverseStatus: Map<number, string>;
28
+ inferredStatusByTaskStatus: Map<string, number> | null;
28
29
  constructor(settings: RedmineSettings, opts: {
29
30
  cache?: LocalBackend | null;
30
31
  });
@@ -62,6 +63,9 @@ export declare class RedmineBackend implements TaskBackend {
62
63
  private setIssueCustomFieldValue;
63
64
  private listTasksRemote;
64
65
  private issueFromPayload;
66
+ private inferStatusIdForTaskStatus;
67
+ private loadInferredStatusByTaskStatus;
68
+ private selectInferredStatus;
65
69
  private findIssueByTaskId;
66
70
  private issueToTask;
67
71
  private taskToIssuePayload;
@@ -1 +1 @@
1
- {"version":3,"file":"redmine-backend.d.ts","sourceRoot":"","sources":["../../../src/backends/task-backend/redmine-backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redmine-backend.d.ts","sourceRoot":"","sources":["../../../src/backends/task-backend/redmine-backend.ts"],"names":[],"mappings":"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"}