agentplane 0.2.6 → 0.2.13

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 (166) hide show
  1. package/README.md +11 -0
  2. package/assets/AGENTS.md +35 -0
  3. package/assets/agents/CODER.json +0 -1
  4. package/assets/agents/INTEGRATOR.json +0 -1
  5. package/assets/agents/ORCHESTRATOR.json +1 -2
  6. package/assets/agents/PLANNER.json +1 -3
  7. package/assets/agents/TESTER.json +0 -1
  8. package/assets/agents/UPGRADER.json +17 -15
  9. package/dist/cli/archive.d.ts.map +1 -1
  10. package/dist/cli/archive.js +61 -36
  11. package/dist/cli/command-guide.d.ts.map +1 -1
  12. package/dist/cli/command-guide.js +5 -3
  13. package/dist/cli/run-cli/command-catalog.d.ts +4 -1
  14. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  15. package/dist/cli/run-cli/command-catalog.js +44 -26
  16. package/dist/cli/run-cli/commands/config.d.ts +5 -4
  17. package/dist/cli/run-cli/commands/config.d.ts.map +1 -1
  18. package/dist/cli/run-cli/commands/config.js +47 -58
  19. package/dist/cli/run-cli/commands/core.d.ts +2 -1
  20. package/dist/cli/run-cli/commands/core.d.ts.map +1 -1
  21. package/dist/cli/run-cli/commands/core.js +187 -51
  22. package/dist/cli/run-cli/commands/ide.d.ts +3 -1
  23. package/dist/cli/run-cli/commands/ide.d.ts.map +1 -1
  24. package/dist/cli/run-cli/commands/ide.js +7 -12
  25. package/dist/cli/run-cli/commands/init/ide-sync.d.ts.map +1 -1
  26. package/dist/cli/run-cli/commands/init/ide-sync.js +10 -1
  27. package/dist/cli/run-cli/commands/init/write-agents.d.ts.map +1 -1
  28. package/dist/cli/run-cli/commands/init/write-agents.js +4 -24
  29. package/dist/cli/run-cli/commands/init/write-gitignore.d.ts +5 -0
  30. package/dist/cli/run-cli/commands/init/write-gitignore.d.ts.map +1 -0
  31. package/dist/cli/run-cli/commands/init/write-gitignore.js +48 -0
  32. package/dist/cli/run-cli/commands/init.d.ts +1 -0
  33. package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
  34. package/dist/cli/run-cli/commands/init.js +34 -8
  35. package/dist/cli/run-cli/commands/wrap-command.d.ts +6 -0
  36. package/dist/cli/run-cli/commands/wrap-command.d.ts.map +1 -0
  37. package/dist/cli/run-cli/commands/wrap-command.js +17 -0
  38. package/dist/cli/run-cli/registry.run.d.ts +6 -2
  39. package/dist/cli/run-cli/registry.run.d.ts.map +1 -1
  40. package/dist/cli/run-cli/registry.run.js +7 -2
  41. package/dist/cli/run-cli.d.ts.map +1 -1
  42. package/dist/cli/run-cli.js +96 -75
  43. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  44. package/dist/cli/run-cli.test-helpers.js +99 -3
  45. package/dist/cli/spec/parse-utils.d.ts +11 -0
  46. package/dist/cli/spec/parse-utils.d.ts.map +1 -0
  47. package/dist/cli/spec/parse-utils.js +28 -0
  48. package/dist/commands/block.command.d.ts +3 -18
  49. package/dist/commands/block.command.d.ts.map +1 -1
  50. package/dist/commands/block.command.js +2 -143
  51. package/dist/commands/block.run.d.ts +5 -0
  52. package/dist/commands/block.run.d.ts.map +1 -0
  53. package/dist/commands/block.run.js +22 -0
  54. package/dist/commands/block.spec.d.ts +17 -0
  55. package/dist/commands/block.spec.d.ts.map +1 -0
  56. package/dist/commands/block.spec.js +115 -0
  57. package/dist/commands/doctor.command.d.ts +2 -7
  58. package/dist/commands/doctor.command.d.ts.map +1 -1
  59. package/dist/commands/doctor.command.js +2 -137
  60. package/dist/commands/doctor.run.d.ts +4 -0
  61. package/dist/commands/doctor.run.d.ts.map +1 -0
  62. package/dist/commands/doctor.run.js +174 -0
  63. package/dist/commands/doctor.spec.d.ts +7 -0
  64. package/dist/commands/doctor.spec.d.ts.map +1 -0
  65. package/dist/commands/doctor.spec.js +20 -0
  66. package/dist/commands/finish.command.d.ts +3 -27
  67. package/dist/commands/finish.command.d.ts.map +1 -1
  68. package/dist/commands/finish.command.js +2 -237
  69. package/dist/commands/finish.run.d.ts +5 -0
  70. package/dist/commands/finish.run.d.ts.map +1 -0
  71. package/dist/commands/finish.run.js +40 -0
  72. package/dist/commands/finish.spec.d.ts +26 -0
  73. package/dist/commands/finish.spec.d.ts.map +1 -0
  74. package/dist/commands/finish.spec.js +193 -0
  75. package/dist/commands/recipes/install.command.d.ts +2 -11
  76. package/dist/commands/recipes/install.command.d.ts.map +1 -1
  77. package/dist/commands/recipes/install.command.js +2 -161
  78. package/dist/commands/recipes/install.run.d.ts +4 -0
  79. package/dist/commands/recipes/install.run.d.ts.map +1 -0
  80. package/dist/commands/recipes/install.run.js +23 -0
  81. package/dist/commands/recipes/install.spec.d.ts +11 -0
  82. package/dist/commands/recipes/install.spec.d.ts.map +1 -0
  83. package/dist/commands/recipes/install.spec.js +140 -0
  84. package/dist/commands/release/apply.command.d.ts +11 -0
  85. package/dist/commands/release/apply.command.d.ts.map +1 -0
  86. package/dist/commands/release/apply.command.js +343 -0
  87. package/dist/commands/release/plan.command.d.ts +12 -0
  88. package/dist/commands/release/plan.command.d.ts.map +1 -0
  89. package/dist/commands/release/plan.command.js +206 -0
  90. package/dist/commands/release/release.command.d.ts +5 -0
  91. package/dist/commands/release/release.command.d.ts.map +1 -0
  92. package/dist/commands/release/release.command.js +18 -0
  93. package/dist/commands/shared/git-context.d.ts +3 -0
  94. package/dist/commands/shared/git-context.d.ts.map +1 -1
  95. package/dist/commands/shared/git-context.js +10 -0
  96. package/dist/commands/shared/task-backend.d.ts +1 -0
  97. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  98. package/dist/commands/start.command.d.ts +3 -18
  99. package/dist/commands/start.command.d.ts.map +1 -1
  100. package/dist/commands/start.command.js +2 -143
  101. package/dist/commands/start.run.d.ts +5 -0
  102. package/dist/commands/start.run.d.ts.map +1 -0
  103. package/dist/commands/start.run.js +22 -0
  104. package/dist/commands/start.spec.d.ts +17 -0
  105. package/dist/commands/start.spec.d.ts.map +1 -0
  106. package/dist/commands/start.spec.js +115 -0
  107. package/dist/commands/task/add.command.d.ts.map +1 -1
  108. package/dist/commands/task/add.command.js +1 -7
  109. package/dist/commands/task/derive.command.d.ts.map +1 -1
  110. package/dist/commands/task/derive.command.js +1 -7
  111. package/dist/commands/task/finish.d.ts.map +1 -1
  112. package/dist/commands/task/finish.js +34 -2
  113. package/dist/commands/task/list.command.d.ts +3 -8
  114. package/dist/commands/task/list.command.d.ts.map +1 -1
  115. package/dist/commands/task/list.command.js +2 -67
  116. package/dist/commands/task/list.run.d.ts +5 -0
  117. package/dist/commands/task/list.run.d.ts.map +1 -0
  118. package/dist/commands/task/list.run.js +10 -0
  119. package/dist/commands/task/list.spec.d.ts +7 -0
  120. package/dist/commands/task/list.spec.d.ts.map +1 -0
  121. package/dist/commands/task/list.spec.js +51 -0
  122. package/dist/commands/task/next.command.d.ts +3 -8
  123. package/dist/commands/task/next.command.d.ts.map +1 -1
  124. package/dist/commands/task/next.command.js +2 -89
  125. package/dist/commands/task/next.run.d.ts +5 -0
  126. package/dist/commands/task/next.run.d.ts.map +1 -0
  127. package/dist/commands/task/next.run.js +11 -0
  128. package/dist/commands/task/next.spec.d.ts +7 -0
  129. package/dist/commands/task/next.spec.d.ts.map +1 -0
  130. package/dist/commands/task/next.spec.js +69 -0
  131. package/dist/commands/task/search.command.d.ts +3 -10
  132. package/dist/commands/task/search.command.d.ts.map +1 -1
  133. package/dist/commands/task/search.command.js +2 -101
  134. package/dist/commands/task/search.run.d.ts +5 -0
  135. package/dist/commands/task/search.run.d.ts.map +1 -0
  136. package/dist/commands/task/search.run.js +13 -0
  137. package/dist/commands/task/search.spec.d.ts +9 -0
  138. package/dist/commands/task/search.spec.d.ts.map +1 -0
  139. package/dist/commands/task/search.spec.js +79 -0
  140. package/dist/commands/task/set-status.command.d.ts.map +1 -1
  141. package/dist/commands/task/set-status.command.js +1 -7
  142. package/dist/commands/task/shared.d.ts.map +1 -1
  143. package/dist/commands/task/shared.js +15 -8
  144. package/dist/commands/task/show.command.d.ts +3 -7
  145. package/dist/commands/task/show.command.d.ts.map +1 -1
  146. package/dist/commands/task/show.command.js +2 -19
  147. package/dist/commands/task/show.run.d.ts +5 -0
  148. package/dist/commands/task/show.run.d.ts.map +1 -0
  149. package/dist/commands/task/show.run.js +11 -0
  150. package/dist/commands/task/show.spec.d.ts +6 -0
  151. package/dist/commands/task/show.spec.d.ts.map +1 -0
  152. package/dist/commands/task/show.spec.js +8 -0
  153. package/dist/commands/task/update.command.d.ts.map +1 -1
  154. package/dist/commands/task/update.command.js +1 -7
  155. package/dist/commands/upgrade.d.ts.map +1 -1
  156. package/dist/commands/upgrade.js +171 -32
  157. package/dist/commands/verify.command.d.ts +3 -15
  158. package/dist/commands/verify.command.d.ts.map +1 -1
  159. package/dist/commands/verify.command.js +2 -113
  160. package/dist/commands/verify.run.d.ts +5 -0
  161. package/dist/commands/verify.run.d.ts.map +1 -0
  162. package/dist/commands/verify.run.js +17 -0
  163. package/dist/commands/verify.spec.d.ts +14 -0
  164. package/dist/commands/verify.spec.d.ts.map +1 -0
  165. package/dist/commands/verify.spec.js +96 -0
  166. package/package.json +1 -1
@@ -0,0 +1,48 @@
1
+ import path from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ import { writeTextIfChanged } from "../../../../shared/write-if-changed.js";
4
+ async function readTextIfExists(filePath) {
5
+ try {
6
+ return await readFile(filePath, "utf8");
7
+ }
8
+ catch (err) {
9
+ const code = err?.code;
10
+ if (code === "ENOENT")
11
+ return null;
12
+ throw err;
13
+ }
14
+ }
15
+ const RUNTIME_IGNORE_LINES = [
16
+ "# agentplane: ignore runtime/transient workspace artifacts",
17
+ ".agentplane/worktrees",
18
+ ".agentplane/cache",
19
+ ".agentplane/recipes-cache",
20
+ ".agentplane/.upgrade",
21
+ ".agentplane/.release",
22
+ ".agentplane/upgrade",
23
+ ".agentplane/tasks.json",
24
+ ];
25
+ const AGENT_PROMPT_IGNORE_LINES = [
26
+ "# agentplane: ignore local agent prompts/templates",
27
+ "AGENTS.md",
28
+ ".agentplane/agents/",
29
+ ];
30
+ export async function ensureInitGitignore(opts) {
31
+ const gitignorePath = path.join(opts.gitRoot, ".gitignore");
32
+ const existing = (await readTextIfExists(gitignorePath)) ?? "";
33
+ const ensuredLines = opts.includeAgentPromptFiles
34
+ ? [...RUNTIME_IGNORE_LINES, ...AGENT_PROMPT_IGNORE_LINES]
35
+ : [...RUNTIME_IGNORE_LINES];
36
+ const existingLines = existing.split(/\r?\n/);
37
+ const existingSet = new Set(existingLines.map((line) => line.trimEnd()));
38
+ const missing = ensuredLines.filter((line) => !existingSet.has(line));
39
+ if (missing.length === 0)
40
+ return;
41
+ const nextLines = [...existingLines];
42
+ // Keep content stable and append a trailing newline.
43
+ if (nextLines.length > 0 && nextLines.at(-1) !== "")
44
+ nextLines.push("");
45
+ nextLines.push(...missing, "");
46
+ const nextText = `${nextLines.join("\n")}`.replaceAll(/\n{2,}$/g, "\n");
47
+ await writeTextIfChanged(gitignorePath, nextText);
48
+ }
@@ -4,6 +4,7 @@ type InitFlags = {
4
4
  workflow?: "direct" | "branch_pr";
5
5
  backend?: "local" | "redmine";
6
6
  hooks?: boolean;
7
+ gitignoreAgents?: boolean;
7
8
  requirePlanApproval?: boolean;
8
9
  requireNetworkApproval?: boolean;
9
10
  requireVerifyApproval?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/run-cli/commands/init.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAatE,KAAK,SAAS,GAAG;IACf,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAsBF,KAAK,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5D,eAAO,MAAM,QAAQ,EAAE,WAAW,CAAC,UAAU,CA0I5C,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,cAAc,CAAC,UAAU,CACmB,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/run-cli/commands/init.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAetE,KAAK,SAAS,GAAG;IACf,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAsBF,KAAK,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5D,eAAO,MAAM,QAAQ,EAAE,WAAW,CAAC,UAAU,CAsJ5C,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,cAAc,CAAC,UAAU,CACmB,CAAC"}
@@ -7,6 +7,7 @@ import { usageError } from "../../spec/errors.js";
7
7
  import { CliError } from "../../../shared/errors.js";
8
8
  import { getVersion } from "../../../meta/version.js";
9
9
  import { cmdHooksInstall, ensureInitCommit } from "../../../commands/workflow.js";
10
+ import { setPinnedBaseBranch } from "@agentplaneorg/core";
10
11
  import { resolveInitBaseBranchForInit } from "./init/base-branch.js";
11
12
  import { collectInitConflicts, handleInitConflicts } from "./init/conflicts.js";
12
13
  import { ensureGitRoot } from "./init/git.js";
@@ -14,6 +15,7 @@ import { maybeSyncIde } from "./init/ide-sync.js";
14
15
  import { maybeInstallBundledRecipes } from "./init/recipes.js";
15
16
  import { ensureAgentplaneDirs, writeBackendStubs, writeInitConfig } from "./init/write-config.js";
16
17
  import { ensureAgentsFiles } from "./init/write-agents.js";
18
+ import { ensureInitGitignore } from "./init/write-gitignore.js";
17
19
  function parseBooleanValueForInit(flag, value) {
18
20
  const normalized = value.trim().toLowerCase();
19
21
  if (["1", "true", "yes", "y", "on"].includes(normalized))
@@ -39,7 +41,7 @@ export const initSpec = {
39
41
  id: ["init"],
40
42
  group: "Setup",
41
43
  summary: "Initialize agentplane project files under .agentplane/.",
42
- description: "Creates .agentplane/ config, backend stubs, and agent templates in the target directory. If the target directory is not a git repository, it initializes one and writes an initial install commit. In interactive mode it prompts for missing inputs; use --yes for non-interactive mode.",
44
+ description: "Creates .agentplane/ config, backend stubs, and agent templates in the target directory. If the target directory is not a git repository, it initializes one and (by default) writes an initial install commit. Use --gitignore-agents to keep agent templates local (gitignored) and skip the install commit. In interactive mode it prompts for missing inputs; use --yes for non-interactive mode.",
43
45
  options: [
44
46
  {
45
47
  kind: "string",
@@ -112,6 +114,12 @@ export const initSpec = {
112
114
  default: false,
113
115
  description: "Non-interactive mode (do not prompt; use defaults for missing flags).",
114
116
  },
117
+ {
118
+ kind: "boolean",
119
+ name: "gitignore-agents",
120
+ default: false,
121
+ description: "Add agent files (AGENTS.md and .agentplane/agents/) to .gitignore and skip the initial install commit.",
122
+ },
115
123
  ],
116
124
  examples: [
117
125
  { cmd: "agentplane init", why: "Interactive setup (prompts for missing values)." },
@@ -123,6 +131,10 @@ export const initSpec = {
123
131
  cmd: "agentplane init --force --yes",
124
132
  why: "Re-initialize, overwriting conflicts (non-interactive).",
125
133
  },
134
+ {
135
+ cmd: "agentplane init --yes --gitignore-agents",
136
+ why: "Initialize without committing and keep agent prompts/templates local (gitignored).",
137
+ },
126
138
  ],
127
139
  validateRaw: (raw) => {
128
140
  if (raw.extra.length > 0) {
@@ -157,6 +169,7 @@ export const initSpec = {
157
169
  force: raw.opts.force === true,
158
170
  backup: raw.opts.backup === true,
159
171
  yes: raw.opts.yes === true,
172
+ gitignoreAgents: raw.opts["gitignore-agents"] === true,
160
173
  };
161
174
  },
162
175
  validate: (p) => {
@@ -299,6 +312,17 @@ async function cmdInit(opts) {
299
312
  configPathAbs: configPath,
300
313
  backendPathAbs: backendPath,
301
314
  });
315
+ await ensureInitGitignore({
316
+ gitRoot: resolved.gitRoot,
317
+ includeAgentPromptFiles: flags.gitignoreAgents === true,
318
+ });
319
+ if (flags.gitignoreAgents) {
320
+ await setPinnedBaseBranch({
321
+ cwd: resolved.gitRoot,
322
+ rootOverride: resolved.gitRoot,
323
+ value: initBaseBranch,
324
+ });
325
+ }
302
326
  if (hooks) {
303
327
  await cmdHooksInstall({ cwd: opts.cwd, rootOverride: opts.rootOverride, quiet: true });
304
328
  }
@@ -310,13 +334,15 @@ async function cmdInit(opts) {
310
334
  });
311
335
  installPaths.push(...ideRes.installPaths);
312
336
  maybeInstallBundledRecipes(recipes);
313
- await ensureInitCommit({
314
- gitRoot: resolved.gitRoot,
315
- baseBranch: initBaseBranch,
316
- installPaths,
317
- version: getVersion(),
318
- skipHooks: hooks,
319
- });
337
+ if (!flags.gitignoreAgents) {
338
+ await ensureInitCommit({
339
+ gitRoot: resolved.gitRoot,
340
+ baseBranch: initBaseBranch,
341
+ installPaths,
342
+ version: getVersion(),
343
+ skipHooks: hooks,
344
+ });
345
+ }
320
346
  process.stdout.write(`${path.relative(resolved.gitRoot, resolved.agentplaneDir)}\n`);
321
347
  return 0;
322
348
  }
@@ -0,0 +1,6 @@
1
+ export declare function wrapCommand<T>(opts: {
2
+ command: string;
3
+ rootOverride?: string;
4
+ context?: Record<string, unknown>;
5
+ }, fn: () => Promise<T> | T): Promise<T>;
6
+ //# sourceMappingURL=wrap-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-command.d.ts","sourceRoot":"","sources":["../../../../src/cli/run-cli/commands/wrap-command.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,CAAC,EACjC,IAAI,EAAE;IACJ,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,EACD,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAYZ"}
@@ -0,0 +1,17 @@
1
+ import { mapCoreError } from "../../error-map.js";
2
+ import { CliError } from "../../../shared/errors.js";
3
+ export async function wrapCommand(opts, fn) {
4
+ try {
5
+ return await fn();
6
+ }
7
+ catch (err) {
8
+ if (err instanceof CliError)
9
+ throw err;
10
+ const context = opts.context ?? {};
11
+ throw mapCoreError(err, {
12
+ command: opts.command,
13
+ root: opts.rootOverride ?? null,
14
+ ...context,
15
+ });
16
+ }
17
+ }
@@ -1,4 +1,8 @@
1
1
  import { CommandRegistry } from "../spec/registry.js";
2
- import type { CommandContext } from "../../commands/shared/task-backend.js";
3
- export declare function buildRegistry(getCtx: (commandForErrorContext: string) => Promise<CommandContext>): CommandRegistry;
2
+ import { type RunDeps } from "./command-catalog.js";
3
+ export declare function buildRegistry(opts: {
4
+ getCtx: RunDeps["getCtx"];
5
+ getResolvedProject: RunDeps["getResolvedProject"];
6
+ getLoadedConfig: RunDeps["getLoadedConfig"];
7
+ }): CommandRegistry;
4
8
  //# sourceMappingURL=registry.run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.run.d.ts","sourceRoot":"","sources":["../../../src/cli/run-cli/registry.run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAMtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAE5E,wBAAgB,aAAa,CAC3B,MAAM,EAAE,CAAC,sBAAsB,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,GAClE,eAAe,CAcjB"}
1
+ {"version":3,"file":"registry.run.d.ts","sourceRoot":"","sources":["../../../src/cli/run-cli/registry.run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAItD,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE9D,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAClD,eAAe,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC7C,GAAG,eAAe,CAmBlB"}
@@ -2,10 +2,15 @@ import { CommandRegistry } from "../spec/registry.js";
2
2
  import { helpSpec, makeHelpHandler } from "../spec/help.js";
3
3
  import { makeHelpJsonFromSpecs } from "../../commands/docs/cli.command.js";
4
4
  import { COMMANDS } from "./command-catalog.js";
5
- export function buildRegistry(getCtx) {
5
+ export function buildRegistry(opts) {
6
6
  const registry = new CommandRegistry();
7
7
  const getHelpJsonForDocs = () => makeHelpJsonFromSpecs(registry.list().map((e) => e.spec));
8
- const deps = { getCtx, getHelpJsonForDocs };
8
+ const deps = {
9
+ getCtx: opts.getCtx,
10
+ getResolvedProject: opts.getResolvedProject,
11
+ getLoadedConfig: opts.getLoadedConfig,
12
+ getHelpJsonForDocs,
13
+ };
9
14
  for (const entry of COMMANDS) {
10
15
  let loaded = null;
11
16
  registry.register(entry.spec, async (ctx, parsed) => {
@@ -1 +1 @@
1
- {"version":3,"file":"run-cli.d.ts","sourceRoot":"","sources":["../../src/cli/run-cli.ts"],"names":[],"mappings":"AAwVA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoI5D"}
1
+ {"version":3,"file":"run-cli.d.ts","sourceRoot":"","sources":["../../src/cli/run-cli.ts"],"names":[],"mappings":"AAmWA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuK5D"}
@@ -1,6 +1,6 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
- import { loadConfig, resolveProject } from "@agentplaneorg/core";
3
+ import { loadConfig, resolveProject, } from "@agentplaneorg/core";
4
4
  import { mapCoreError } from "./error-map.js";
5
5
  import { exitCodeForError } from "./exit-codes.js";
6
6
  import { warnMessage } from "./output.js";
@@ -14,43 +14,35 @@ import { helpSpec } from "./spec/help.js";
14
14
  import { usageError } from "./spec/errors.js";
15
15
  import { suggestOne } from "./spec/suggest.js";
16
16
  import { COMMANDS } from "./run-cli/command-catalog.js";
17
+ const GLOBAL_FLAGS = [
18
+ { key: "help", forms: ["--help", "-h"], takesValue: false, scoped: false },
19
+ { key: "version", forms: ["--version", "-v"], takesValue: false, scoped: false },
20
+ { key: "noUpdateCheck", forms: ["--no-update-check"], takesValue: false, scoped: false },
21
+ { key: "allowNetwork", forms: ["--allow-network"], takesValue: false, scoped: true },
22
+ { key: "jsonErrors", forms: ["--json-errors"], takesValue: false, scoped: true },
23
+ { key: "root", forms: ["--root"], takesValue: true, scoped: false },
24
+ ];
25
+ const GLOBAL_FLAG_FORMS = new Map(GLOBAL_FLAGS.flatMap((def) => def.forms.map((form) => [form, def])));
17
26
  function prescanJsonErrors(argv) {
18
27
  // If parseGlobalArgs throws (e.g. missing --root value), we still want to honor
19
- // `--json` in the "scoped global" zone (before the command id).
28
+ // `--json-errors` in the "scoped global" zone (before the command id).
20
29
  let hasRest = false;
21
30
  for (let i = 0; i < argv.length; i++) {
22
31
  const arg = argv[i];
23
32
  if (!arg)
24
33
  continue;
25
- // Global flags that do not accept values.
26
- if (arg === "--help" || arg === "-h")
27
- continue;
28
- if (arg === "--version" || arg === "-v")
29
- continue;
30
- if (arg === "--no-update-check")
31
- continue;
32
- if (arg === "--allow-network")
33
- continue;
34
- // Scoped global: only before the command id.
35
- if (arg === "--json-errors") {
36
- if (!hasRest)
37
- return true;
38
- continue;
39
- }
40
- if (arg === "--json") {
41
- if (!hasRest)
42
- return true;
43
- continue;
34
+ const def = GLOBAL_FLAG_FORMS.get(arg);
35
+ if (!def) {
36
+ // First non-global token is treated as the start of the command id.
37
+ hasRest = true;
38
+ break;
44
39
  }
45
- // Global flags with values.
46
- if (arg === "--root") {
40
+ if (def.key === "jsonErrors" && !hasRest)
41
+ return true;
42
+ if (def.takesValue) {
47
43
  // Skip the value if present; do not throw on missing value here.
48
44
  i++;
49
- continue;
50
45
  }
51
- // First non-global token is treated as the start of the command id.
52
- hasRest = true;
53
- break;
54
46
  }
55
47
  return false;
56
48
  }
@@ -66,61 +58,56 @@ function parseGlobalArgs(argv) {
66
58
  const arg = argv[i];
67
59
  if (!arg)
68
60
  continue;
69
- if (arg === "--help" || arg === "-h") {
70
- help = true;
71
- continue;
72
- }
73
- if (arg === "--version" || arg === "-v") {
74
- version = true;
61
+ const def = GLOBAL_FLAG_FORMS.get(arg);
62
+ if (!def) {
63
+ rest.push(arg);
75
64
  continue;
76
65
  }
77
- if (arg === "--no-update-check") {
78
- noUpdateCheck = true;
66
+ // Scoped globals are only recognized before the command id token.
67
+ if (def.scoped && rest.length > 0) {
68
+ rest.push(arg);
79
69
  continue;
80
70
  }
81
- if (arg === "--allow-network") {
82
- // Scoped global: only treat `--allow-network` as a global approval if it appears
83
- // before the command id. This avoids accidental capture of command-specific flags.
84
- if (rest.length === 0) {
71
+ switch (def.key) {
72
+ case "help": {
73
+ help = true;
74
+ break;
75
+ }
76
+ case "version": {
77
+ version = true;
78
+ break;
79
+ }
80
+ case "noUpdateCheck": {
81
+ noUpdateCheck = true;
82
+ break;
83
+ }
84
+ case "allowNetwork": {
85
85
  allowNetwork = true;
86
- continue;
86
+ break;
87
87
  }
88
- rest.push(arg);
89
- continue;
90
- }
91
- if (arg === "--json-errors") {
92
- // Scoped global: only treat `--json-errors` as "JSON errors" if it appears
93
- // before the command id. This mirrors the existing `--json` behavior.
94
- if (rest.length === 0) {
88
+ case "jsonErrors": {
95
89
  jsonErrors = true;
96
- continue;
90
+ break;
97
91
  }
98
- rest.push(arg);
99
- continue;
100
- }
101
- if (arg === "--json") {
102
- // Scoped global: only treat `--json` as "JSON errors" if it appears
103
- // before the command id. This allows per-command `--json` (e.g. `help`).
104
- if (rest.length === 0) {
105
- jsonErrors = true;
106
- continue;
92
+ case "root": {
93
+ const next = argv[i + 1];
94
+ if (!next) {
95
+ throw new CliError({
96
+ exitCode: 2,
97
+ code: "E_USAGE",
98
+ message: "Missing value after --root (expected repository path)",
99
+ });
100
+ }
101
+ root = next;
102
+ i++;
103
+ break;
104
+ }
105
+ default: {
106
+ // Exhaustive by construction; keep a defensive fallback.
107
+ rest.push(arg);
108
+ break;
107
109
  }
108
- rest.push(arg);
109
- continue;
110
- }
111
- if (arg === "--root") {
112
- const next = argv[i + 1];
113
- if (!next)
114
- throw new CliError({
115
- exitCode: 2,
116
- code: "E_USAGE",
117
- message: "Missing value after --root (expected repository path)",
118
- });
119
- root = next;
120
- i++;
121
- continue;
122
110
  }
123
- rest.push(arg);
124
111
  }
125
112
  return { globals: { help, version, noUpdateCheck, root, jsonErrors, allowNetwork }, rest };
126
113
  }
@@ -318,7 +305,12 @@ export async function runCli(argv) {
318
305
  }
319
306
  const runCli2HelpFast = async (helpArgv) => {
320
307
  const { buildRegistry } = await import("./run-cli/registry.run.js");
321
- const registry = buildRegistry((_cmd) => Promise.reject(new Error("getCtx should not be called for help")));
308
+ const reject = (name) => (_cmd) => Promise.reject(new Error(`${name} should not be called for help`));
309
+ const registry = buildRegistry({
310
+ getCtx: reject("getCtx"),
311
+ getResolvedProject: reject("getResolvedProject"),
312
+ getLoadedConfig: reject("getLoadedConfig"),
313
+ });
322
314
  const match = registry.match(helpArgv);
323
315
  if (!match) {
324
316
  throw new CliError({
@@ -352,12 +344,37 @@ export async function runCli(argv) {
352
344
  if (resolved) {
353
345
  await loadDotEnv(resolved.gitRoot);
354
346
  }
347
+ let projectPromise = resolved
348
+ ? Promise.resolve(resolved)
349
+ : null;
350
+ const getResolvedProject = async (commandForErrorContext) => {
351
+ projectPromise ??= resolveProject({ cwd, rootOverride: globals.root ?? null });
352
+ try {
353
+ return await projectPromise;
354
+ }
355
+ catch (err) {
356
+ throw mapCoreError(err, { command: commandForErrorContext, root: globals.root ?? null });
357
+ }
358
+ };
359
+ let configPromise = null;
360
+ const getLoadedConfig = async (commandForErrorContext) => {
361
+ configPromise ??= (async () => {
362
+ const project = await getResolvedProject(commandForErrorContext);
363
+ return await loadConfig(project.agentplaneDir);
364
+ })();
365
+ try {
366
+ return await configPromise;
367
+ }
368
+ catch (err) {
369
+ throw mapCoreError(err, { command: commandForErrorContext, root: globals.root ?? null });
370
+ }
371
+ };
355
372
  // `require_network=true` means "no network without explicit approval".
356
373
  // Update-check is an optional network call, so it must be gated after config load.
357
374
  let skipUpdateCheckForPolicy = true;
358
375
  if (resolved && matched?.entry.needsConfig !== false) {
359
376
  try {
360
- const loaded = await loadConfig(resolved.agentplaneDir);
377
+ const loaded = await getLoadedConfig("update-check");
361
378
  const requireNetwork = loaded.config.agents?.approvals.require_network === true;
362
379
  const explicitlyApproved = globals.allowNetwork;
363
380
  skipUpdateCheckForPolicy = requireNetwork && !explicitlyApproved;
@@ -394,7 +411,11 @@ export async function runCli(argv) {
394
411
  };
395
412
  // cli2 command routing (single router).
396
413
  const { buildRegistry } = await import("./run-cli/registry.run.js");
397
- const registry = buildRegistry(getCtxOrThrow);
414
+ const registry = buildRegistry({
415
+ getCtx: getCtxOrThrow,
416
+ getResolvedProject,
417
+ getLoadedConfig,
418
+ });
398
419
  const match = registry.match(rest);
399
420
  if (match) {
400
421
  const tail = rest.slice(match.consumed);
@@ -1 +1 @@
1
- {"version":3,"file":"run-cli.test-helpers.d.ts","sourceRoot":"","sources":["../../src/cli/run-cli.test-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run-cli.test-helpers.d.ts","sourceRoot":"","sources":["../../src/cli/run-cli.test-helpers.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { promisify } from "node:util";
8
+ import { gzipSync } from "node:zlib";
8
9
  import { afterAll, beforeAll } from "vitest";
9
10
  import { defaultConfig } from "@agentplaneorg/core";
10
11
  import { runCli } from "./run-cli.js";
@@ -12,6 +13,10 @@ const execFileAsync = promisify(execFile);
12
13
  let agentplaneHome = null;
13
14
  const originalAgentplaneHome = process.env.AGENTPLANE_HOME;
14
15
  const originalNoUpdateCheck = process.env.AGENTPLANE_NO_UPDATE_CHECK;
16
+ const originalGitAuthorName = process.env.GIT_AUTHOR_NAME;
17
+ const originalGitAuthorEmail = process.env.GIT_AUTHOR_EMAIL;
18
+ const originalGitCommitterName = process.env.GIT_COMMITTER_NAME;
19
+ const originalGitCommitterEmail = process.env.GIT_COMMITTER_EMAIL;
15
20
  const originalStdoutWrite = process.stdout.write.bind(process.stdout);
16
21
  const originalStderrWrite = process.stderr.write.bind(process.stderr);
17
22
  let stdioSilenceDepth = 0;
@@ -24,6 +29,12 @@ async function ensureGitTemplateRoot() {
24
29
  gitTemplatePromise ??= (async () => {
25
30
  const root = await mkdtemp(path.join(os.tmpdir(), "agentplane-git-template-"));
26
31
  await execFileAsync("git", ["init", "-q"], { cwd: root });
32
+ // Tests must not rely on global git config. Configure author identity locally
33
+ // so any helper that creates commits works in CI.
34
+ await execFileAsync("git", ["config", "user.email", "agentplane-test@example.com"], {
35
+ cwd: root,
36
+ });
37
+ await execFileAsync("git", ["config", "user.name", "agentplane-test"], { cwd: root });
27
38
  return root;
28
39
  })();
29
40
  gitTemplateRoot = await gitTemplatePromise;
@@ -38,6 +49,11 @@ export function registerAgentplaneHome() {
38
49
  agentplaneHome = await mkdtemp(path.join(os.tmpdir(), "agentplane-home-"));
39
50
  process.env.AGENTPLANE_HOME = agentplaneHome;
40
51
  process.env.AGENTPLANE_NO_UPDATE_CHECK = "1";
52
+ // Keep tests hermetic: never rely on global git config for commit authorship.
53
+ process.env.GIT_AUTHOR_NAME ??= "agentplane-test";
54
+ process.env.GIT_AUTHOR_EMAIL ??= "agentplane-test@example.com";
55
+ process.env.GIT_COMMITTER_NAME ??= "agentplane-test";
56
+ process.env.GIT_COMMITTER_EMAIL ??= "agentplane-test@example.com";
41
57
  });
42
58
  afterAll(async () => {
43
59
  if (agentplaneHome) {
@@ -55,6 +71,22 @@ export function registerAgentplaneHome() {
55
71
  else {
56
72
  process.env.AGENTPLANE_NO_UPDATE_CHECK = originalNoUpdateCheck;
57
73
  }
74
+ if (originalGitAuthorName === undefined)
75
+ delete process.env.GIT_AUTHOR_NAME;
76
+ else
77
+ process.env.GIT_AUTHOR_NAME = originalGitAuthorName;
78
+ if (originalGitAuthorEmail === undefined)
79
+ delete process.env.GIT_AUTHOR_EMAIL;
80
+ else
81
+ process.env.GIT_AUTHOR_EMAIL = originalGitAuthorEmail;
82
+ if (originalGitCommitterName === undefined)
83
+ delete process.env.GIT_COMMITTER_NAME;
84
+ else
85
+ process.env.GIT_COMMITTER_NAME = originalGitCommitterName;
86
+ if (originalGitCommitterEmail === undefined)
87
+ delete process.env.GIT_COMMITTER_EMAIL;
88
+ else
89
+ process.env.GIT_COMMITTER_EMAIL = originalGitCommitterEmail;
58
90
  });
59
91
  }
60
92
  export function getAgentplaneHome() {
@@ -278,11 +310,75 @@ export async function createUnsafeRecipeArchive(opts) {
278
310
  const entryPath = opts.entryPath ?? "../evil.txt";
279
311
  await writeFile(path.join(baseDir, "evil.txt"), "evil", "utf8");
280
312
  const archivePath = opts.format === "zip" ? path.join(baseDir, "unsafe.zip") : path.join(baseDir, "unsafe.tar.gz");
281
- await (opts.format === "zip"
282
- ? execFileAsync("zip", ["-qr", archivePath, ".", entryPath], { cwd: recipeDir })
283
- : execFileAsync("tar", ["-czf", archivePath, "-C", recipeDir, ".", entryPath]));
313
+ if (opts.format === "zip") {
314
+ await execFileAsync("zip", ["-qr", archivePath, ".", entryPath], { cwd: recipeDir });
315
+ return archivePath;
316
+ }
317
+ // Build a deterministic tar.gz containing a traversal entry, without relying on system tar behavior.
318
+ // This keeps the archive validation tests stable across GNU tar / bsdtar.
319
+ const tar = buildTar([
320
+ {
321
+ name: "./manifest.json",
322
+ data: Buffer.from(JSON.stringify(manifest, null, 2) + "\n", "utf8"),
323
+ },
324
+ { name: entryPath, data: Buffer.from("evil\n", "utf8") },
325
+ ]);
326
+ const gz = gzipSync(tar);
327
+ await writeFile(archivePath, gz);
284
328
  return archivePath;
285
329
  }
330
+ function buildTar(entries) {
331
+ const out = [];
332
+ for (const ent of entries) {
333
+ const header = tarHeader({
334
+ name: ent.name,
335
+ size: ent.data.length,
336
+ mtime: 0,
337
+ typeflag: "0",
338
+ });
339
+ out.push(header, ent.data, zeroPadTo512(ent.data.length));
340
+ }
341
+ // Two empty blocks mark end-of-archive.
342
+ out.push(Buffer.alloc(1024, 0));
343
+ return Buffer.concat(out);
344
+ }
345
+ function zeroPadTo512(n) {
346
+ const rem = n % 512;
347
+ if (rem === 0)
348
+ return Buffer.alloc(0);
349
+ return Buffer.alloc(512 - rem, 0);
350
+ }
351
+ function tarHeader(opts) {
352
+ const buf = Buffer.alloc(512, 0);
353
+ writeTarString(buf, 0, 100, opts.name);
354
+ writeTarOctal(buf, 100, 8, 0o644); // mode
355
+ writeTarOctal(buf, 108, 8, 0); // uid
356
+ writeTarOctal(buf, 116, 8, 0); // gid
357
+ writeTarOctal(buf, 124, 12, opts.size);
358
+ writeTarOctal(buf, 136, 12, opts.mtime);
359
+ // checksum field must be treated as spaces for calculation
360
+ buf.fill(0x20, 148, 156);
361
+ writeTarString(buf, 156, 1, opts.typeflag);
362
+ writeTarString(buf, 257, 6, "ustar"); // magic
363
+ writeTarString(buf, 263, 2, "00"); // version
364
+ const sum = buf.reduce((acc, b) => acc + b, 0);
365
+ writeTarChecksum(buf, sum);
366
+ return buf;
367
+ }
368
+ function writeTarString(buf, offset, length, value) {
369
+ const b = Buffer.from(value, "utf8");
370
+ b.copy(buf, offset, 0, Math.min(length, b.length));
371
+ }
372
+ function writeTarOctal(buf, offset, length, value) {
373
+ const raw = Math.max(0, value).toString(8);
374
+ const padded = raw.padStart(length - 1, "0") + "\0";
375
+ writeTarString(buf, offset, length, padded);
376
+ }
377
+ function writeTarChecksum(buf, sum) {
378
+ // 6 digits, NUL, space (common convention).
379
+ const raw = Math.max(0, sum).toString(8).padStart(6, "0");
380
+ writeTarString(buf, 148, 8, `${raw}\0 `);
381
+ }
286
382
  export async function createUpgradeBundle(files) {
287
383
  const manifestUrl = new URL("../../assets/framework.manifest.json", import.meta.url);
288
384
  const manifestText = typeof files["framework.manifest.json"] === "string"
@@ -0,0 +1,11 @@
1
+ import type { CommandSpec } from "./spec.js";
2
+ export declare function asTrimmedString(value: unknown): string;
3
+ export declare function toStringList(value: unknown): string[];
4
+ export declare function assertNonEmptyStrings(opts: {
5
+ list: readonly string[];
6
+ flagName: string;
7
+ spec: CommandSpec<unknown>;
8
+ command: string;
9
+ message?: string;
10
+ }): void;
11
+ //# sourceMappingURL=parse-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-utils.d.ts","sourceRoot":"","sources":["../../../src/cli/spec/parse-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQtD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAGrD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAYP"}