agentplane 0.3.20 → 0.3.22

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 (33) hide show
  1. package/assets/policy/incidents.md +3 -0
  2. package/dist/.build-manifest.json +37 -32
  3. package/dist/cli/bootstrap-guide.d.ts +1 -0
  4. package/dist/cli/bootstrap-guide.d.ts.map +1 -1
  5. package/dist/cli/bootstrap-guide.js +3 -2
  6. package/dist/cli/command-guide.d.ts.map +1 -1
  7. package/dist/cli/command-guide.js +5 -4
  8. package/dist/cli/run-cli/commands/init/recipes.js +2 -2
  9. package/dist/cli.js +351 -336
  10. package/dist/commands/branch/work-start.hook-shim.d.ts.map +1 -1
  11. package/dist/commands/branch/work-start.hook-shim.js +16 -4
  12. package/dist/commands/doctor/hook-readiness.d.ts +2 -0
  13. package/dist/commands/doctor/hook-readiness.d.ts.map +1 -0
  14. package/dist/commands/doctor/hook-readiness.js +171 -0
  15. package/dist/commands/doctor/workspace.d.ts.map +1 -1
  16. package/dist/commands/doctor/workspace.js +2 -1
  17. package/dist/commands/guard/impl/commit.d.ts.map +1 -1
  18. package/dist/commands/guard/impl/commit.js +26 -0
  19. package/dist/commands/hooks/install.d.ts.map +1 -1
  20. package/dist/commands/hooks/install.js +16 -4
  21. package/dist/commands/hooks/run.pre-push.d.ts.map +1 -1
  22. package/dist/commands/hooks/run.pre-push.js +234 -13
  23. package/dist/commands/recipes/impl/installed-recipes.d.ts +1 -0
  24. package/dist/commands/recipes/impl/installed-recipes.d.ts.map +1 -1
  25. package/dist/commands/recipes/impl/installed-recipes.js +24 -0
  26. package/dist/commands/task/derive.command.js +1 -1
  27. package/dist/commands/task/finish-execute.d.ts.map +1 -1
  28. package/dist/commands/task/finish-execute.js +8 -1
  29. package/dist/commands/task/shared/transitions.d.ts.map +1 -1
  30. package/dist/commands/task/shared/transitions.js +11 -2
  31. package/dist/commands/task/verify-command-shared.d.ts.map +1 -1
  32. package/dist/commands/task/verify-command-shared.js +8 -2
  33. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"work-start.hook-shim.d.ts","sourceRoot":"","sources":["../../../src/commands/branch/work-start.hook-shim.ts"],"names":[],"mappings":"AAkCA,wBAAsB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOxF"}
1
+ {"version":3,"file":"work-start.hook-shim.d.ts","sourceRoot":"","sources":["../../../src/commands/branch/work-start.hook-shim.ts"],"names":[],"mappings":"AAgDA,wBAAsB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOxF"}
@@ -1,8 +1,16 @@
1
1
  import { chmod, mkdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileExists } from "../../cli/fs-utils.js";
4
+ import { resolveAgentplaneBinPath } from "../../shared/package-paths.js";
4
5
  const HOOK_SHIM_MARKER = "agentplane-hook-shim";
5
- function repoLocalHookShimText() {
6
+ function shellSingleQuote(value) {
7
+ return `'${value.replaceAll("'", String.raw `'\''`)}'`;
8
+ }
9
+ function resolveInstalledHookRunnerPath() {
10
+ const activeBin = String(process.env.AGENTPLANE_RUNTIME_ACTIVE_BIN ?? "").trim();
11
+ return activeBin || resolveAgentplaneBinPath();
12
+ }
13
+ function repoLocalHookShimText(installedRunnerPath) {
6
14
  return [
7
15
  "#!/usr/bin/env sh",
8
16
  `# ${HOOK_SHIM_MARKER} (do not edit)`,
@@ -13,6 +21,10 @@ function repoLocalHookShimText() {
13
21
  'if command -v node >/dev/null 2>&1 && [ -f "$LOCAL_BIN" ]; then',
14
22
  ' exec node "$LOCAL_BIN" "$@"',
15
23
  "fi",
24
+ `INSTALL_BIN=${shellSingleQuote(installedRunnerPath)}`,
25
+ 'if command -v node >/dev/null 2>&1 && [ -f "$INSTALL_BIN" ]; then',
26
+ ' exec node "$INSTALL_BIN" "$@"',
27
+ "fi",
16
28
  'ENV_BIN="${AGENTPLANE_HOOK_RUNNER:-}"',
17
29
  'if [ -n "$ENV_BIN" ] && command -v node >/dev/null 2>&1 && [ -f "$ENV_BIN" ]; then',
18
30
  ' exec node "$ENV_BIN" "$@"',
@@ -20,10 +32,10 @@ function repoLocalHookShimText() {
20
32
  "if command -v agentplane >/dev/null 2>&1; then",
21
33
  ' exec agentplane "$@"',
22
34
  "fi",
23
- "if command -v npx >/dev/null 2>&1; then",
35
+ 'if [ "${AGENTPLANE_HOOK_ALLOW_NPX:-}" = "1" ] && command -v npx >/dev/null 2>&1; then',
24
36
  ' exec npx --yes agentplane "$@"',
25
37
  "fi",
26
- 'echo "agentplane shim: runner not found (need env runner, repo-local source, agentplane in PATH, or node+npx)." >&2',
38
+ 'echo "agentplane shim: runner not found (need installed runner, env runner, repo-local source, agentplane in PATH, or AGENTPLANE_HOOK_ALLOW_NPX=1)." >&2',
27
39
  " exit 127",
28
40
  "",
29
41
  ].join("\n");
@@ -33,6 +45,6 @@ export async function materializeHookShimForWorktree(worktreePath) {
33
45
  if (await fileExists(shimPath))
34
46
  return;
35
47
  await mkdir(path.dirname(shimPath), { recursive: true });
36
- await writeFile(shimPath, repoLocalHookShimText(), "utf8");
48
+ await writeFile(shimPath, repoLocalHookShimText(resolveInstalledHookRunnerPath()), "utf8");
37
49
  await chmod(shimPath, 0o755);
38
50
  }
@@ -0,0 +1,2 @@
1
+ export declare function checkManagedHookShimReadiness(repoRoot: string): Promise<string[]>;
2
+ //# sourceMappingURL=hook-readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-readiness.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/hook-readiness.ts"],"names":[],"mappings":"AA4EA,wBAAsB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAyIvF"}
@@ -0,0 +1,171 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { renderDiagnosticFinding } from "../shared/diagnostics.js";
4
+ import { fileIsManaged, HOOK_MARKER, HOOK_NAMES, resolveGitHooksDir, SHIM_MARKER, } from "../hooks/shared.js";
5
+ async function pathExists(absPath) {
6
+ try {
7
+ await fs.access(absPath);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ async function readTextIfExists(absPath) {
15
+ try {
16
+ return await fs.readFile(absPath, "utf8");
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ function unquoteShellSingleQuoted(value) {
23
+ return value.replaceAll(String.raw `'\''`, "'");
24
+ }
25
+ function extractInstallBinFromShim(shimText) {
26
+ const match = /\nINSTALL_BIN='((?:'\\''|[^'])*)'/.exec(shimText);
27
+ if (!match)
28
+ return null;
29
+ return unquoteShellSingleQuoted(match[1] ?? "").trim() || null;
30
+ }
31
+ async function resolveManagedHookNames(repoRoot) {
32
+ let hooksDir;
33
+ try {
34
+ hooksDir = await resolveGitHooksDir(repoRoot);
35
+ }
36
+ catch {
37
+ return { hooksDir: null, managedHooks: [], unmanagedHooks: [] };
38
+ }
39
+ const managedHooks = [];
40
+ const unmanagedHooks = [];
41
+ for (const hook of HOOK_NAMES) {
42
+ const hookPath = path.join(hooksDir, hook);
43
+ if (!(await pathExists(hookPath)))
44
+ continue;
45
+ if (await fileIsManaged(hookPath, HOOK_MARKER))
46
+ managedHooks.push(hook);
47
+ else
48
+ unmanagedHooks.push(hook);
49
+ }
50
+ return { hooksDir, managedHooks, unmanagedHooks };
51
+ }
52
+ function renderUnmanagedHooksFinding(unmanagedHooks) {
53
+ return renderDiagnosticFinding({
54
+ severity: "INFO",
55
+ state: "unmanaged git hooks are present and will not be overwritten by AgentPlane",
56
+ likelyCause: "the repository has custom hook files outside the AgentPlane managed hook set",
57
+ nextAction: {
58
+ command: "agentplane hooks install",
59
+ reason: "refresh managed hooks only after manually reconciling custom hook files that should remain custom",
60
+ },
61
+ details: [`Unmanaged hooks: ${unmanagedHooks.join(", ")}`],
62
+ });
63
+ }
64
+ export async function checkManagedHookShimReadiness(repoRoot) {
65
+ const findings = [];
66
+ const { managedHooks, unmanagedHooks } = await resolveManagedHookNames(repoRoot);
67
+ const shimRelPath = ".agentplane/bin/agentplane";
68
+ const shimPath = path.join(repoRoot, shimRelPath);
69
+ const shimText = await readTextIfExists(shimPath);
70
+ const managedShim = shimText?.includes(SHIM_MARKER) === true;
71
+ if (managedHooks.length === 0 && unmanagedHooks.length === 0 && !shimText)
72
+ return findings;
73
+ if (managedHooks.length > 0 && !shimText) {
74
+ findings.push(renderDiagnosticFinding({
75
+ severity: "WARN",
76
+ state: "managed git hooks are installed but the AgentPlane hook shim is missing",
77
+ likelyCause: "the repository hook files still call .agentplane/bin/agentplane, but that shim was deleted or never committed",
78
+ nextAction: {
79
+ command: "agentplane hooks install",
80
+ reason: "recreate the managed hook shim and refresh hook files",
81
+ },
82
+ details: [`Managed hooks: ${managedHooks.join(", ")}`],
83
+ }));
84
+ return findings;
85
+ }
86
+ if (shimText && !managedShim) {
87
+ findings.push(renderDiagnosticFinding({
88
+ severity: "WARN",
89
+ state: "AgentPlane hook shim path exists but is not managed by AgentPlane",
90
+ likelyCause: "a custom .agentplane/bin/agentplane file is shadowing the managed hook runner shim",
91
+ nextAction: {
92
+ command: "inspect .agentplane/bin/agentplane, remove the custom file if unintended, then run agentplane hooks install",
93
+ reason: "restore the managed shim without overwriting custom executable content silently",
94
+ },
95
+ details: [`Shim path: ${shimRelPath}`],
96
+ }));
97
+ return findings;
98
+ }
99
+ if (!shimText) {
100
+ if (unmanagedHooks.length > 0) {
101
+ findings.push(renderUnmanagedHooksFinding(unmanagedHooks));
102
+ }
103
+ return findings;
104
+ }
105
+ const installedBin = extractInstallBinFromShim(shimText);
106
+ if (!installedBin) {
107
+ findings.push(renderDiagnosticFinding({
108
+ severity: "WARN",
109
+ state: "managed AgentPlane hook shim uses a stale format without an installed runner fallback",
110
+ likelyCause: "the shim was generated by an older AgentPlane release before installed-binary fallback was embedded",
111
+ nextAction: {
112
+ command: "agentplane hooks install",
113
+ reason: "rewrite the managed shim with the current installed-runtime fallback contract",
114
+ },
115
+ details: [`Shim path: ${shimRelPath}`],
116
+ }));
117
+ }
118
+ else if (!(await pathExists(installedBin))) {
119
+ findings.push(renderDiagnosticFinding({
120
+ severity: "WARN",
121
+ state: "managed AgentPlane hook shim points to a missing installed runner",
122
+ likelyCause: "AgentPlane was reinstalled, removed, or moved after hooks were installed in this repository",
123
+ nextAction: {
124
+ command: "agentplane hooks install",
125
+ reason: "refresh the embedded installed runner path after reinstalling or updating AgentPlane",
126
+ },
127
+ details: [`Embedded runner: ${installedBin}`, `Shim path: ${shimRelPath}`],
128
+ }));
129
+ }
130
+ const hasEnvFallback = shimText.includes("AGENTPLANE_HOOK_RUNNER");
131
+ const hasNpxFallback = shimText.includes("AGENTPLANE_HOOK_ALLOW_NPX");
132
+ if (!hasEnvFallback || !hasNpxFallback) {
133
+ findings.push(renderDiagnosticFinding({
134
+ severity: "WARN",
135
+ state: "managed AgentPlane hook shim is missing current fallback branches",
136
+ likelyCause: "the repository still has an older managed shim that cannot use env-runner or explicit npx fallback recovery",
137
+ nextAction: {
138
+ command: "agentplane hooks install",
139
+ reason: "rewrite the shim with the current fallback chain",
140
+ },
141
+ details: [
142
+ `Shim path: ${shimRelPath}`,
143
+ `AGENTPLANE_HOOK_RUNNER fallback: ${hasEnvFallback ? "present" : "missing"}`,
144
+ `AGENTPLANE_HOOK_ALLOW_NPX fallback: ${hasNpxFallback ? "present" : "missing"}`,
145
+ ],
146
+ }));
147
+ }
148
+ if (managedHooks.includes("pre-push")) {
149
+ const repoPrePushScript = path.join(repoRoot, "scripts", "run-pre-push-hook.mjs");
150
+ const packageJsonText = await readTextIfExists(path.join(repoRoot, "package.json"));
151
+ if (!(await pathExists(repoPrePushScript)) && packageJsonText) {
152
+ findings.push(renderDiagnosticFinding({
153
+ severity: "INFO",
154
+ state: "managed pre-push hook will use installed clean-project fallback checks",
155
+ likelyCause: "this project does not ship a repository-local scripts/run-pre-push-hook.mjs",
156
+ nextAction: {
157
+ command: "add project-local package scripts such as format:check or test, or keep the installed fallback",
158
+ reason: "make pre-push behavior explicit when the project needs stricter repository-specific checks",
159
+ },
160
+ details: [
161
+ "Missing repository script: scripts/run-pre-push-hook.mjs",
162
+ "Installed fallback skips package scripts that are not declared by the clean project.",
163
+ ],
164
+ }));
165
+ }
166
+ }
167
+ if (unmanagedHooks.length > 0) {
168
+ findings.push(renderUnmanagedHooksFinding(unmanagedHooks));
169
+ }
170
+ return findings;
171
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/workspace.ts"],"names":[],"mappings":"AAYA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGpF,KAAK,eAAe,GAAG;IACrB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAqFF,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,CAyDnF;AAED,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,cAAc,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB;AAoMD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,cAAc,CAAA;CAAE,GAC9B,OAAO,CAAC,MAAM,EAAE,CAAC,CA2DnB"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/workspace.ts"],"names":[],"mappings":"AAYA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIpF,KAAK,eAAe,GAAG;IACrB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAqFF,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,CAyDnF;AAED,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,cAAc,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB;AAoMD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,cAAc,CAAA;CAAE,GAC9B,OAAO,CAAC,MAAM,EAAE,CAAC,CA4DnB"}
@@ -6,6 +6,7 @@ import { resolvePolicyGatewayForRepo } from "../../shared/policy-gateway.js";
6
6
  import { GitContext } from "@agentplaneorg/core/git";
7
7
  import { listTaskProjection } from "../shared/task-backend.js";
8
8
  import { resolveAgentplaneAssetPath } from "../../shared/package-paths.js";
9
+ import { checkManagedHookShimReadiness } from "./hook-readiness.js";
9
10
  async function pathExists(absPath) {
10
11
  try {
11
12
  await fs.access(absPath);
@@ -365,6 +366,6 @@ export async function checkWorkspace(repoRoot, opts) {
365
366
  if (!hasJson) {
366
367
  problems.push("No agent profiles found in .agentplane/agents (*.json expected).");
367
368
  }
368
- problems.push(...checkBackendReadiness(opts?.ctx), ...(await checkTaskReadmeMigrationState(repoRoot, opts?.ctx)), ...(await checkDoneTaskReadmeArchiveDrift(repoRoot, opts?.ctx)), ...(await checkTaskProjectionDrift(repoRoot, opts?.ctx)));
369
+ problems.push(...checkBackendReadiness(opts?.ctx), ...(await checkManagedHookShimReadiness(repoRoot)), ...(await checkTaskReadmeMigrationState(repoRoot, opts?.ctx)), ...(await checkDoneTaskReadmeArchiveDrift(repoRoot, opts?.ctx)), ...(await checkTaskProjectionDrift(repoRoot, opts?.ctx)));
369
370
  return problems;
370
371
  }
@@ -1 +1 @@
1
- {"version":3,"file":"commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commit.ts"],"names":[],"mappings":"AAaA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,8BAA8B,CAAC;AAiKtC,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,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;IACxB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC,GAAG,OAAO,CAAC,MAAM,CAAC,CA2HlB"}
1
+ {"version":3,"file":"commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commit.ts"],"names":[],"mappings":"AAaA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,8BAA8B,CAAC;AAwLtC,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,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;IACxB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC,GAAG,OAAO,CAAC,MAAM,CAAC,CAwIlB"}
@@ -137,6 +137,22 @@ function formatCommitRef(commit) {
137
137
  return "";
138
138
  return `${commit.hash?.slice(0, 12) ?? ""} ${commit.subject ?? ""}`.trim();
139
139
  }
140
+ async function stageActiveTaskArtifactsFromAllowTasks(opts) {
141
+ if (!opts.allowTasks)
142
+ return [];
143
+ const changedPaths = await opts.ctx.git.statusChangedPaths();
144
+ const taskArtifactPaths = changedPaths.filter((relPath) => isTaskLocalAdvancePath({
145
+ workflowDir: opts.ctx.config.paths.workflow_dir,
146
+ taskId: opts.taskId,
147
+ tasksPath: opts.ctx.config.paths.tasks_path,
148
+ relPath,
149
+ }));
150
+ if (taskArtifactPaths.length === 0)
151
+ return [];
152
+ const unique = [...new Set(taskArtifactPaths)].toSorted((a, b) => a.localeCompare(b));
153
+ await opts.ctx.git.stage(unique);
154
+ return unique;
155
+ }
140
156
  export async function cmdCommit(opts) {
141
157
  try {
142
158
  const ctx = opts.ctx ??
@@ -181,6 +197,16 @@ export async function cmdCommit(opts) {
181
197
  process.stdout.write(`${infoMessage(`commit auto-staged ${autoStaged.length} path(s) from allowlist`)}\n`);
182
198
  }
183
199
  }
200
+ else {
201
+ autoStaged = await stageActiveTaskArtifactsFromAllowTasks({
202
+ ctx,
203
+ taskId: opts.taskId,
204
+ allowTasks: opts.allowTasks,
205
+ });
206
+ if (autoStaged.length > 0 && !opts.quiet) {
207
+ process.stdout.write(`${infoMessage(`commit auto-staged ${autoStaged.length} active task artifact path(s) from --allow-tasks`)}\n`);
208
+ }
209
+ }
184
210
  await guardCommitCheck({
185
211
  ctx,
186
212
  cwd: opts.cwd,
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/install.ts"],"names":[],"mappings":"AAkFA,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/install.ts"],"names":[],"mappings":"AAgGA,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB"}
@@ -5,6 +5,7 @@ import { mapCoreError } from "../../cli/error-map.js";
5
5
  import { fileExists } from "../../cli/fs-utils.js";
6
6
  import { infoMessage, successMessage } from "../../cli/output.js";
7
7
  import { CliError } from "../../shared/errors.js";
8
+ import { resolveAgentplaneBinPath } from "../../shared/package-paths.js";
8
9
  import { fileIsManaged, HOOK_MARKER, HOOK_NAMES, resolveGitHooksDir, SHIM_MARKER, } from "./shared.js";
9
10
  function hookScriptText(hook) {
10
11
  return [
@@ -24,7 +25,14 @@ function hookScriptText(hook) {
24
25
  "",
25
26
  ].join("\n");
26
27
  }
27
- function shimScriptText() {
28
+ function shellSingleQuote(value) {
29
+ return `'${value.replaceAll("'", String.raw `'\''`)}'`;
30
+ }
31
+ function resolveInstalledHookRunnerPath() {
32
+ const activeBin = String(process.env.AGENTPLANE_RUNTIME_ACTIVE_BIN ?? "").trim();
33
+ return activeBin || resolveAgentplaneBinPath();
34
+ }
35
+ function shimScriptText(installedRunnerPath) {
28
36
  return [
29
37
  "#!/usr/bin/env sh",
30
38
  `# ${SHIM_MARKER} (do not edit)`,
@@ -35,6 +43,10 @@ function shimScriptText() {
35
43
  'if command -v node >/dev/null 2>&1 && [ -f "$LOCAL_BIN" ]; then',
36
44
  ' exec node "$LOCAL_BIN" "$@"',
37
45
  "fi",
46
+ `INSTALL_BIN=${shellSingleQuote(installedRunnerPath)}`,
47
+ 'if command -v node >/dev/null 2>&1 && [ -f "$INSTALL_BIN" ]; then',
48
+ ' exec node "$INSTALL_BIN" "$@"',
49
+ "fi",
38
50
  'ENV_BIN="${AGENTPLANE_HOOK_RUNNER:-}"',
39
51
  'if [ -n "$ENV_BIN" ] && command -v node >/dev/null 2>&1 && [ -f "$ENV_BIN" ]; then',
40
52
  ' exec node "$ENV_BIN" "$@"',
@@ -42,10 +54,10 @@ function shimScriptText() {
42
54
  "if command -v agentplane >/dev/null 2>&1; then",
43
55
  ' exec agentplane "$@"',
44
56
  "fi",
45
- "if command -v npx >/dev/null 2>&1; then",
57
+ 'if [ "${AGENTPLANE_HOOK_ALLOW_NPX:-}" = "1" ] && command -v npx >/dev/null 2>&1; then',
46
58
  ' exec npx --yes agentplane "$@"',
47
59
  "fi",
48
- 'echo "agentplane shim: runner not found (need env runner, repo-local source, agentplane in PATH, or node+npx)." >&2',
60
+ 'echo "agentplane shim: runner not found (need installed runner, env runner, repo-local source, agentplane in PATH, or AGENTPLANE_HOOK_ALLOW_NPX=1)." >&2',
49
61
  " exit 127",
50
62
  "",
51
63
  ].join("\n");
@@ -64,7 +76,7 @@ async function ensureShim(agentplaneDir, gitRoot) {
64
76
  });
65
77
  }
66
78
  }
67
- await writeFile(shimPath, shimScriptText(), "utf8");
79
+ await writeFile(shimPath, shimScriptText(resolveInstalledHookRunnerPath()), "utf8");
68
80
  await chmod(shimPath, 0o755);
69
81
  }
70
82
  export async function cmdHooksInstall(opts) {
@@ -1 +1 @@
1
- {"version":3,"file":"run.pre-push.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/run.pre-push.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAMhD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AA4CD,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC3E"}
1
+ {"version":3,"file":"run.pre-push.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/run.pre-push.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAyBhD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AA+TD,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAuB3E"}
@@ -1,12 +1,20 @@
1
1
  import { resolveProject } from "@agentplaneorg/core/project";
2
2
  import { runProcessSync } from "@agentplaneorg/core/process";
3
+ import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import { fileExists } from "../../cli/fs-utils.js";
5
- import { CliError } from "../../shared/errors.js";
6
6
  import { resolveAgentplaneRepoScriptPath } from "../../shared/package-paths.js";
7
7
  function resolveBundledPrePushHookScriptPath() {
8
8
  return resolveAgentplaneRepoScriptPath("run-pre-push-hook.mjs");
9
9
  }
10
+ class HookFailure extends Error {
11
+ details;
12
+ constructor(message, details = []) {
13
+ super(message);
14
+ this.details = details;
15
+ this.name = "HookFailure";
16
+ }
17
+ }
10
18
  export async function resolvePrePushHookScriptPath(gitRoot, opts = {}) {
11
19
  const repoScriptPath = path.join(gitRoot, "scripts", "run-pre-push-hook.mjs");
12
20
  if (await fileExists(repoScriptPath))
@@ -16,6 +24,228 @@ export async function resolvePrePushHookScriptPath(gitRoot, opts = {}) {
16
24
  return bundledScriptPath;
17
25
  return null;
18
26
  }
27
+ function parsePrePushStdin(rawStdin) {
28
+ return rawStdin
29
+ .split("\n")
30
+ .map((line) => line.trim())
31
+ .filter(Boolean)
32
+ .map((line) => {
33
+ const [localRef = "", localSha = "", remoteRef = "", remoteSha = ""] = line.split(/\s+/);
34
+ return { localRef, localSha, remoteRef, remoteSha };
35
+ });
36
+ }
37
+ function isAllZeroSha(value) {
38
+ return /^[0]+$/.test(value);
39
+ }
40
+ function isBranchRef(ref) {
41
+ return ref.startsWith("refs/heads/");
42
+ }
43
+ function runHookCommand(gitRoot, command, args) {
44
+ const result = runProcessSync({
45
+ command,
46
+ args,
47
+ cwd: gitRoot,
48
+ env: process.env,
49
+ stdout: "inherit",
50
+ stderr: "inherit",
51
+ reject: false,
52
+ });
53
+ return result.exitCode ?? (result.signal ? 1 : 0);
54
+ }
55
+ function runHookCommandWithEnv(gitRoot, command, args, env) {
56
+ const result = runProcessSync({
57
+ command,
58
+ args,
59
+ cwd: gitRoot,
60
+ env,
61
+ stdout: "inherit",
62
+ stderr: "inherit",
63
+ reject: false,
64
+ });
65
+ return result.exitCode ?? (result.signal ? 1 : 0);
66
+ }
67
+ function readGitText(gitRoot, args) {
68
+ const result = runProcessSync({
69
+ command: "git",
70
+ args,
71
+ cwd: gitRoot,
72
+ encoding: "utf8",
73
+ reject: false,
74
+ });
75
+ if (result.exitCode !== 0)
76
+ return "";
77
+ return String(result.stdout ?? "").trim();
78
+ }
79
+ function readPackageScripts(gitRoot) {
80
+ try {
81
+ const parsed = JSON.parse(fs.readFileSync(path.join(gitRoot, "package.json"), "utf8"));
82
+ if (!parsed.scripts || typeof parsed.scripts !== "object" || Array.isArray(parsed.scripts)) {
83
+ return {};
84
+ }
85
+ const scripts = {};
86
+ for (const [name, value] of Object.entries(parsed.scripts)) {
87
+ if (typeof value === "string")
88
+ scripts[name] = value;
89
+ }
90
+ return scripts;
91
+ }
92
+ catch {
93
+ return {};
94
+ }
95
+ }
96
+ function hasProjectScript(scripts, name) {
97
+ return Object.hasOwn(scripts, name);
98
+ }
99
+ function runOptionalProjectScript(gitRoot, scripts, name, opts = {}) {
100
+ if (!hasProjectScript(scripts, name)) {
101
+ process.stdout.write(`Skipping ${name}: package.json script is not defined.\n`);
102
+ return { exitCode: 0, skipped: true };
103
+ }
104
+ if (opts.heading)
105
+ process.stdout.write(opts.heading);
106
+ const exitCode = opts.env
107
+ ? runHookCommandWithEnv(gitRoot, "bun", ["run", name], opts.env)
108
+ : runHookCommand(gitRoot, "bun", ["run", name]);
109
+ return { exitCode, skipped: false };
110
+ }
111
+ function trackedChangesShort(gitRoot) {
112
+ return readGitText(gitRoot, ["status", "--short", "--untracked-files=no"]);
113
+ }
114
+ function fail(message, details = []) {
115
+ throw new HookFailure(message, details);
116
+ }
117
+ function failIfTrackedChanges(gitRoot, message) {
118
+ const changes = trackedChangesShort(gitRoot);
119
+ if (!changes)
120
+ return;
121
+ fail(message, [changes]);
122
+ }
123
+ function gitRefExists(gitRoot, ref) {
124
+ return readGitText(gitRoot, ["rev-parse", "--verify", "--quiet", ref]).length > 0;
125
+ }
126
+ function hasReleaseTagPush(updates) {
127
+ return updates.some((update) => update.remoteRef.startsWith("refs/tags/"));
128
+ }
129
+ function isDeleteOnlyPush(updates) {
130
+ return (updates.length > 0 &&
131
+ updates.every((update) => isBranchRef(update.remoteRef) && isAllZeroSha(update.localSha) && Boolean(update.remoteSha)));
132
+ }
133
+ function resolveDefaultBaseRef(gitRoot) {
134
+ const remoteHead = readGitText(gitRoot, [
135
+ "symbolic-ref",
136
+ "--quiet",
137
+ "--short",
138
+ "refs/remotes/origin/HEAD",
139
+ ]);
140
+ if (remoteHead)
141
+ return remoteHead;
142
+ if (gitRefExists(gitRoot, "origin/main"))
143
+ return "origin/main";
144
+ if (gitRefExists(gitRoot, "main"))
145
+ return "main";
146
+ return null;
147
+ }
148
+ function selectBranchDiffRange(updates, opts = {}) {
149
+ const branchUpdates = updates.filter((update) => isBranchRef(update.localRef) && isBranchRef(update.remoteRef));
150
+ if (branchUpdates.length !== 1)
151
+ return null;
152
+ const [update] = branchUpdates;
153
+ if (!update?.localSha || !update.remoteSha)
154
+ return null;
155
+ if (isAllZeroSha(update.localSha))
156
+ return null;
157
+ if (isAllZeroSha(update.remoteSha)) {
158
+ const fallbackRef = typeof opts.newBranchFallbackRef === "string" ? opts.newBranchFallbackRef.trim() : "";
159
+ return fallbackRef ? { from: fallbackRef, to: update.localSha } : null;
160
+ }
161
+ return { from: update.remoteSha, to: update.localSha };
162
+ }
163
+ function readChangedFilesForRange(gitRoot, range) {
164
+ if (!range)
165
+ return [];
166
+ const output = readGitText(gitRoot, ["diff", "--name-only", `${range.from}..${range.to}`]);
167
+ return output
168
+ .split("\n")
169
+ .map((line) => line.trim())
170
+ .filter(Boolean);
171
+ }
172
+ function fileExistsSync(filePath) {
173
+ try {
174
+ return fs.statSync(filePath).isFile();
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ function isTruthyHookEnv(name) {
181
+ return (String(process.env[name] ?? "")
182
+ .trim()
183
+ .toLowerCase() === "1");
184
+ }
185
+ function runInternalPrePushHook(gitRoot, stdin) {
186
+ try {
187
+ const updates = parsePrePushStdin(stdin);
188
+ const envRelease = isTruthyHookEnv("AGENTPLANE_HOOKS_RELEASE");
189
+ const envFull = isTruthyHookEnv("AGENTPLANE_HOOKS_FULL");
190
+ const isReleasePush = envRelease || envFull || hasReleaseTagPush(updates);
191
+ if (!isReleasePush && isDeleteOnlyPush(updates)) {
192
+ process.stdout.write("Skipping pre-push checks for delete-only remote branch cleanup.\n");
193
+ return 0;
194
+ }
195
+ const mode = isReleasePush ? "release" : "standard";
196
+ process.stdout.write(`Running pre-push checks in ${mode} mode.\n`);
197
+ const ciScript = envFull ? "ci:local:full" : "ci:local:fast";
198
+ const scripts = readPackageScripts(gitRoot);
199
+ const changedFiles = readChangedFilesForRange(gitRoot, selectBranchDiffRange(updates, {
200
+ newBranchFallbackRef: resolveDefaultBaseRef(gitRoot),
201
+ }));
202
+ const ciEnv = changedFiles.length > 0
203
+ ? { ...process.env, AGENTPLANE_FAST_CHANGED_FILES: changedFiles.join("\n") }
204
+ : process.env;
205
+ const formatResult = runOptionalProjectScript(gitRoot, scripts, "format:check", {
206
+ heading: "\n== Format (check) ==\n",
207
+ });
208
+ if (formatResult.exitCode !== 0) {
209
+ failIfTrackedChanges(gitRoot, "pre-push blocked: format:check changed tracked files unexpectedly. Revert or commit those changes and push again.");
210
+ fail("pre-push blocked: formatting check failed. Run `bun run format`, review the diff, commit it, and push again.");
211
+ }
212
+ if (!formatResult.skipped) {
213
+ failIfTrackedChanges(gitRoot, "pre-push blocked: format:check changed tracked files unexpectedly. Revert or commit those changes and push again.");
214
+ }
215
+ const ciResult = runOptionalProjectScript(gitRoot, scripts, ciScript, { env: ciEnv });
216
+ if (!ciResult.skipped) {
217
+ failIfTrackedChanges(gitRoot, `pre-push blocked: ${ciScript} changed tracked files. Commit or revert those changes and push again.`);
218
+ }
219
+ if (ciResult.exitCode !== 0) {
220
+ fail(`pre-push blocked: ${ciScript} failed. Fix the reported checks and push again.`);
221
+ }
222
+ if (isReleasePush) {
223
+ const releaseNotesScript = path.join(gitRoot, "scripts", "check-release-notes.mjs");
224
+ if (fileExistsSync(releaseNotesScript)) {
225
+ const notesExitCode = runHookCommand(gitRoot, "node", ["scripts/check-release-notes.mjs"]);
226
+ if (notesExitCode !== 0)
227
+ return notesExitCode;
228
+ }
229
+ else {
230
+ process.stdout.write("Skipping release notes check: scripts/check-release-notes.mjs is not defined.\n");
231
+ }
232
+ return runOptionalProjectScript(gitRoot, scripts, "release:prepublish").exitCode;
233
+ }
234
+ return 0;
235
+ }
236
+ catch (error) {
237
+ if (error instanceof HookFailure) {
238
+ process.stderr.write(`\n${error.message}\n`);
239
+ for (const detail of error.details) {
240
+ if (!detail)
241
+ continue;
242
+ process.stderr.write(`${detail}\n`);
243
+ }
244
+ return 1;
245
+ }
246
+ throw error;
247
+ }
248
+ }
19
249
  async function readHookStdinUtf8(timeoutMs = 25) {
20
250
  if (process.stdin.isTTY)
21
251
  return "";
@@ -59,19 +289,10 @@ export async function runPrePushHook(opts) {
59
289
  cwd: opts.cwd,
60
290
  rootOverride: opts.rootOverride ?? null,
61
291
  });
292
+ const stdin = await readHookStdinUtf8();
62
293
  const scriptPath = await resolvePrePushHookScriptPath(resolved.gitRoot);
63
294
  if (!scriptPath) {
64
- throw new CliError({
65
- exitCode: 2,
66
- code: "E_USAGE",
67
- message: [
68
- "Missing pre-push hook script: scripts/run-pre-push-hook.mjs",
69
- "The pre-push hook needs a repository-local script or an installed CLI bundle that ships the fallback.",
70
- "Fix:",
71
- " 1) Restore scripts/run-pre-push-hook.mjs in this repository, or",
72
- " 2) Run `agentplane hooks uninstall` if this repository should not use the agentplane pre-push gate.",
73
- ].join("\n"),
74
- });
295
+ return runInternalPrePushHook(resolved.gitRoot, stdin);
75
296
  }
76
297
  const result = runProcessSync({
77
298
  command: "node",
@@ -79,7 +300,7 @@ export async function runPrePushHook(opts) {
79
300
  cwd: resolved.gitRoot,
80
301
  env: process.env,
81
302
  encoding: "utf8",
82
- input: await readHookStdinUtf8(),
303
+ input: stdin,
83
304
  stdin: "pipe",
84
305
  stdout: "inherit",
85
306
  stderr: "inherit",
@@ -1,4 +1,5 @@
1
1
  import { type InstalledRecipesFile } from "@agentplaneorg/recipes";
2
2
  export declare function readInstalledRecipesFile(filePath: string): Promise<InstalledRecipesFile>;
3
+ export declare function readAndMigrateInstalledRecipesFile(filePath: string): Promise<InstalledRecipesFile>;
3
4
  export declare function writeInstalledRecipesFile(filePath: string, file: InstalledRecipesFile): Promise<void>;
4
5
  //# sourceMappingURL=installed-recipes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"installed-recipes.d.ts","sourceRoot":"","sources":["../../../../src/commands/recipes/impl/installed-recipes.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAwChC,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAS9F;AAED,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAOf"}
1
+ {"version":3,"file":"installed-recipes.d.ts","sourceRoot":"","sources":["../../../../src/commands/recipes/impl/installed-recipes.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAgDhC,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAS9F;AAED,wBAAsB,kCAAkC,CACtD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,CAc/B;AAED,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAOf"}