agentplane 0.3.2 → 0.3.4

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 (205) hide show
  1. package/assets/AGENTS.md +4 -4
  2. package/assets/agents/CODER.json +14 -10
  3. package/assets/agents/CREATOR.json +8 -6
  4. package/assets/agents/DOCS.json +9 -6
  5. package/assets/agents/INTEGRATOR.json +9 -6
  6. package/assets/agents/ORCHESTRATOR.json +8 -6
  7. package/assets/agents/PLANNER.json +8 -5
  8. package/assets/agents/REDMINE.json +6 -3
  9. package/assets/agents/REVIEWER.json +8 -6
  10. package/assets/agents/TESTER.json +8 -4
  11. package/assets/agents/UPDATER.json +6 -4
  12. package/assets/agents/UPGRADER.json +7 -6
  13. package/assets/policy/dod.code.md +2 -2
  14. package/assets/policy/dod.core.md +16 -2
  15. package/assets/policy/dod.docs.md +2 -2
  16. package/assets/policy/incidents.md +44 -1
  17. package/assets/policy/workflow.direct.md +8 -4
  18. package/bin/agentplane.js +59 -9
  19. package/bin/dist-guard.js +78 -10
  20. package/bin/runtime-context.d.ts +3 -0
  21. package/bin/runtime-context.js +13 -0
  22. package/bin/runtime-watch.d.ts +26 -0
  23. package/bin/runtime-watch.js +116 -0
  24. package/bin/stale-dist-policy.d.ts +6 -0
  25. package/bin/stale-dist-policy.js +44 -0
  26. package/dist/.build-manifest.json +2485 -5
  27. package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
  28. package/dist/backends/task-backend/local-backend.js +9 -12
  29. package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
  30. package/dist/backends/task-backend/redmine-backend.js +23 -18
  31. package/dist/backends/task-backend/shared/constants.d.ts +1 -0
  32. package/dist/backends/task-backend/shared/constants.d.ts.map +1 -1
  33. package/dist/backends/task-backend/shared/constants.js +1 -0
  34. package/dist/backends/task-backend/shared/doc.d.ts +1 -0
  35. package/dist/backends/task-backend/shared/doc.d.ts.map +1 -1
  36. package/dist/backends/task-backend/shared/doc.js +4 -1
  37. package/dist/backends/task-backend/shared/export.js +3 -3
  38. package/dist/cli/bootstrap-guide.d.ts +2 -3
  39. package/dist/cli/bootstrap-guide.d.ts.map +1 -1
  40. package/dist/cli/bootstrap-guide.js +16 -35
  41. package/dist/cli/command-guide.d.ts +14 -1
  42. package/dist/cli/command-guide.d.ts.map +1 -1
  43. package/dist/cli/command-guide.js +71 -47
  44. package/dist/cli/run-cli/catalog.d.ts +7 -0
  45. package/dist/cli/run-cli/catalog.d.ts.map +1 -0
  46. package/dist/cli/run-cli/catalog.js +22 -0
  47. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  48. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  49. package/dist/cli/run-cli/command-catalog.js +11 -0
  50. package/dist/cli/run-cli/commands/core.d.ts.map +1 -1
  51. package/dist/cli/run-cli/commands/core.js +37 -29
  52. package/dist/cli/run-cli/commands/init/write-config.d.ts.map +1 -1
  53. package/dist/cli/run-cli/commands/init/write-config.js +2 -0
  54. package/dist/cli/run-cli/commands/init/write-workflow.d.ts.map +1 -1
  55. package/dist/cli/run-cli/commands/init/write-workflow.js +6 -55
  56. package/dist/cli/run-cli/commands/init.js +5 -14
  57. package/dist/cli/run-cli/error-guidance.d.ts +9 -0
  58. package/dist/cli/run-cli/error-guidance.d.ts.map +1 -0
  59. package/dist/cli/run-cli/error-guidance.js +180 -0
  60. package/dist/cli/run-cli/globals.d.ts +22 -0
  61. package/dist/cli/run-cli/globals.d.ts.map +1 -0
  62. package/dist/cli/run-cli/globals.js +197 -0
  63. package/dist/cli/run-cli/update-warning.d.ts +6 -0
  64. package/dist/cli/run-cli/update-warning.d.ts.map +1 -0
  65. package/dist/cli/run-cli/update-warning.js +64 -0
  66. package/dist/cli/run-cli.d.ts.map +1 -1
  67. package/dist/cli/run-cli.js +5 -476
  68. package/dist/cli/spec/docs-render.d.ts.map +1 -1
  69. package/dist/cli/spec/docs-render.js +14 -1
  70. package/dist/commands/doctor/archive.d.ts +4 -0
  71. package/dist/commands/doctor/archive.d.ts.map +1 -0
  72. package/dist/commands/doctor/archive.js +211 -0
  73. package/dist/commands/doctor/fixes.d.ts +9 -0
  74. package/dist/commands/doctor/fixes.d.ts.map +1 -0
  75. package/dist/commands/doctor/fixes.js +40 -0
  76. package/dist/commands/doctor/layering.d.ts +2 -0
  77. package/dist/commands/doctor/layering.d.ts.map +1 -0
  78. package/dist/commands/doctor/layering.js +87 -0
  79. package/dist/commands/doctor/runtime.d.ts +4 -0
  80. package/dist/commands/doctor/runtime.d.ts.map +1 -0
  81. package/dist/commands/doctor/runtime.js +56 -0
  82. package/dist/commands/doctor/workflow.d.ts +6 -0
  83. package/dist/commands/doctor/workflow.d.ts.map +1 -0
  84. package/dist/commands/doctor/workflow.js +62 -0
  85. package/dist/commands/doctor/workspace.d.ts +2 -0
  86. package/dist/commands/doctor/workspace.d.ts.map +1 -0
  87. package/dist/commands/doctor/workspace.js +165 -0
  88. package/dist/commands/doctor.run.d.ts.map +1 -1
  89. package/dist/commands/doctor.run.js +16 -342
  90. package/dist/commands/doctor.spec.d.ts +1 -0
  91. package/dist/commands/doctor.spec.d.ts.map +1 -1
  92. package/dist/commands/doctor.spec.js +15 -1
  93. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  94. package/dist/commands/guard/impl/commands.js +19 -0
  95. package/dist/commands/release/apply.command.d.ts +2 -8
  96. package/dist/commands/release/apply.command.d.ts.map +1 -1
  97. package/dist/commands/release/apply.command.js +158 -387
  98. package/dist/commands/release/apply.mutation.d.ts +7 -0
  99. package/dist/commands/release/apply.mutation.d.ts.map +1 -0
  100. package/dist/commands/release/apply.mutation.js +107 -0
  101. package/dist/commands/release/apply.preflight.d.ts +25 -0
  102. package/dist/commands/release/apply.preflight.d.ts.map +1 -0
  103. package/dist/commands/release/apply.preflight.js +338 -0
  104. package/dist/commands/release/apply.reporting.d.ts +4 -0
  105. package/dist/commands/release/apply.reporting.d.ts.map +1 -0
  106. package/dist/commands/release/apply.reporting.js +24 -0
  107. package/dist/commands/release/apply.types.d.ts +46 -0
  108. package/dist/commands/release/apply.types.d.ts.map +1 -0
  109. package/dist/commands/release/apply.types.js +1 -0
  110. package/dist/commands/runtime.command.d.ts +28 -0
  111. package/dist/commands/runtime.command.d.ts.map +1 -0
  112. package/dist/commands/runtime.command.js +169 -0
  113. package/dist/commands/shared/task-store.d.ts.map +1 -1
  114. package/dist/commands/shared/task-store.js +7 -3
  115. package/dist/commands/task/add.d.ts.map +1 -1
  116. package/dist/commands/task/add.js +3 -33
  117. package/dist/commands/task/block.d.ts.map +1 -1
  118. package/dist/commands/task/block.js +2 -2
  119. package/dist/commands/task/close-duplicate.d.ts.map +1 -1
  120. package/dist/commands/task/close-duplicate.js +2 -2
  121. package/dist/commands/task/close-noop.d.ts.map +1 -1
  122. package/dist/commands/task/close-noop.js +2 -2
  123. package/dist/commands/task/comment.js +2 -2
  124. package/dist/commands/task/derive.d.ts.map +1 -1
  125. package/dist/commands/task/derive.js +3 -3
  126. package/dist/commands/task/doc-template.d.ts +10 -0
  127. package/dist/commands/task/doc-template.d.ts.map +1 -0
  128. package/dist/commands/task/doc-template.js +104 -0
  129. package/dist/commands/task/doc.d.ts.map +1 -1
  130. package/dist/commands/task/doc.js +36 -1
  131. package/dist/commands/task/finish.d.ts.map +1 -1
  132. package/dist/commands/task/finish.js +7 -4
  133. package/dist/commands/task/migrate-doc.command.d.ts.map +1 -1
  134. package/dist/commands/task/migrate-doc.command.js +5 -1
  135. package/dist/commands/task/migrate-doc.d.ts.map +1 -1
  136. package/dist/commands/task/migrate-doc.js +136 -2
  137. package/dist/commands/task/new.d.ts.map +1 -1
  138. package/dist/commands/task/new.js +4 -110
  139. package/dist/commands/task/new.spec.js +3 -3
  140. package/dist/commands/task/plan.d.ts.map +1 -1
  141. package/dist/commands/task/plan.js +5 -4
  142. package/dist/commands/task/scaffold.d.ts.map +1 -1
  143. package/dist/commands/task/scaffold.js +7 -52
  144. package/dist/commands/task/set-status.d.ts.map +1 -1
  145. package/dist/commands/task/set-status.js +2 -2
  146. package/dist/commands/task/shared/dependencies.d.ts +15 -0
  147. package/dist/commands/task/shared/dependencies.d.ts.map +1 -0
  148. package/dist/commands/task/shared/dependencies.js +143 -0
  149. package/dist/commands/task/shared/docs.d.ts +21 -0
  150. package/dist/commands/task/shared/docs.d.ts.map +1 -0
  151. package/dist/commands/task/shared/docs.js +121 -0
  152. package/dist/commands/task/shared/listing.d.ts +20 -0
  153. package/dist/commands/task/shared/listing.d.ts.map +1 -0
  154. package/dist/commands/task/shared/listing.js +127 -0
  155. package/dist/commands/task/shared/tags.d.ts +24 -0
  156. package/dist/commands/task/shared/tags.d.ts.map +1 -0
  157. package/dist/commands/task/shared/tags.js +177 -0
  158. package/dist/commands/task/shared/transitions.d.ts +42 -0
  159. package/dist/commands/task/shared/transitions.d.ts.map +1 -0
  160. package/dist/commands/task/shared/transitions.js +175 -0
  161. package/dist/commands/task/shared.d.ts +5 -106
  162. package/dist/commands/task/shared.d.ts.map +1 -1
  163. package/dist/commands/task/shared.js +5 -681
  164. package/dist/commands/task/start.d.ts.map +1 -1
  165. package/dist/commands/task/start.js +7 -5
  166. package/dist/commands/task/verify-record.d.ts.map +1 -1
  167. package/dist/commands/task/verify-record.js +9 -25
  168. package/dist/commands/task/verify-show.command.d.ts.map +1 -1
  169. package/dist/commands/task/verify-show.command.js +5 -1
  170. package/dist/commands/upgrade/apply.d.ts +44 -0
  171. package/dist/commands/upgrade/apply.d.ts.map +1 -0
  172. package/dist/commands/upgrade/apply.js +180 -0
  173. package/dist/commands/upgrade/report.d.ts +21 -0
  174. package/dist/commands/upgrade/report.d.ts.map +1 -0
  175. package/dist/commands/upgrade/report.js +81 -0
  176. package/dist/commands/upgrade/source.d.ts +35 -0
  177. package/dist/commands/upgrade/source.d.ts.map +1 -0
  178. package/dist/commands/upgrade/source.js +109 -0
  179. package/dist/commands/upgrade/types.d.ts +31 -0
  180. package/dist/commands/upgrade/types.d.ts.map +1 -0
  181. package/dist/commands/upgrade/types.js +1 -0
  182. package/dist/commands/upgrade.d.ts +1 -35
  183. package/dist/commands/upgrade.d.ts.map +1 -1
  184. package/dist/commands/upgrade.js +68 -332
  185. package/dist/commands/workflow-build.command.d.ts.map +1 -1
  186. package/dist/commands/workflow-build.command.js +9 -15
  187. package/dist/shared/diagnostics.d.ts +23 -0
  188. package/dist/shared/diagnostics.d.ts.map +1 -0
  189. package/dist/shared/diagnostics.js +57 -0
  190. package/dist/shared/errors.d.ts +2 -0
  191. package/dist/shared/errors.d.ts.map +1 -1
  192. package/dist/shared/errors.js +2 -0
  193. package/dist/shared/repo-cli-version.d.ts +13 -0
  194. package/dist/shared/repo-cli-version.d.ts.map +1 -0
  195. package/dist/shared/repo-cli-version.js +63 -0
  196. package/dist/shared/runtime-source.d.ts +33 -0
  197. package/dist/shared/runtime-source.d.ts.map +1 -0
  198. package/dist/shared/runtime-source.js +156 -0
  199. package/dist/shared/version-compare.d.ts +7 -0
  200. package/dist/shared/version-compare.d.ts.map +1 -0
  201. package/dist/shared/version-compare.js +30 -0
  202. package/dist/shared/workflow-artifacts.d.ts +37 -0
  203. package/dist/shared/workflow-artifacts.d.ts.map +1 -0
  204. package/dist/shared/workflow-artifacts.js +97 -0
  205. package/package.json +2 -2
package/bin/agentplane.js CHANGED
@@ -4,7 +4,9 @@ import path from "node:path";
4
4
  import { stat } from "node:fs/promises";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { distExists, isPackageBuildFresh } from "./dist-guard.js";
7
- import { resolveFrameworkBinaryContext } from "./runtime-context.js";
7
+ import { getWatchedRuntimePathsForPackage } from "./runtime-watch.js";
8
+ import { classifyStaleDistPolicy } from "./stale-dist-policy.js";
9
+ import { isPathInside, resolveFrameworkBinaryContext } from "./runtime-context.js";
8
10
 
9
11
  async function exists(p) {
10
12
  try {
@@ -43,6 +45,27 @@ function isRepoLocalHandoffInvocation() {
43
45
  return (process.env.AGENTPLANE_REPO_LOCAL_HANDOFF ?? "").trim() === "1";
44
46
  }
45
47
 
48
+ function inferRuntimeMode(context) {
49
+ if (context.inFrameworkCheckout && context.isRepoLocalRuntime) {
50
+ return isRepoLocalHandoffInvocation() ? "repo-local-handoff" : "repo-local";
51
+ }
52
+ if (context.inFrameworkCheckout && shouldUseGlobalBinaryInFramework()) {
53
+ return "global-forced-in-framework";
54
+ }
55
+ if (context.inFrameworkCheckout) {
56
+ return "global-in-framework";
57
+ }
58
+ return "global-installed";
59
+ }
60
+
61
+ function primeRuntimeEnv(context) {
62
+ process.env.AGENTPLANE_RUNTIME_ACTIVE_BIN = context.thisBin;
63
+ process.env.AGENTPLANE_RUNTIME_MODE = inferRuntimeMode(context);
64
+ if (!isRepoLocalHandoffInvocation()) {
65
+ delete process.env.AGENTPLANE_RUNTIME_HANDOFF_FROM;
66
+ }
67
+ }
68
+
46
69
  function handoffToRepoLocalBinary(context) {
47
70
  const repoBin = context.checkout?.repoBin;
48
71
  if (!repoBin) return false;
@@ -57,6 +80,9 @@ function handoffToRepoLocalBinary(context) {
57
80
  env: {
58
81
  ...process.env,
59
82
  AGENTPLANE_REPO_LOCAL_HANDOFF: "1",
83
+ AGENTPLANE_RUNTIME_MODE: "repo-local-handoff",
84
+ AGENTPLANE_RUNTIME_ACTIVE_BIN: path.resolve(repoBin),
85
+ AGENTPLANE_RUNTIME_HANDOFF_FROM: context.thisBin,
60
86
  },
61
87
  });
62
88
 
@@ -91,11 +117,6 @@ function isHooksRunCommitMsgInvocation(argv) {
91
117
  return false;
92
118
  }
93
119
 
94
- function isPathInside(baseDir, targetPath) {
95
- const rel = path.relative(path.resolve(baseDir), path.resolve(targetPath));
96
- return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
97
- }
98
-
99
120
  async function assertDistUpToDate() {
100
121
  const here = path.dirname(fileURLToPath(import.meta.url));
101
122
  const agentplaneRoot = path.resolve(here, "..");
@@ -120,11 +141,16 @@ async function assertDistUpToDate() {
120
141
  {
121
142
  name: "agentplane",
122
143
  root: agentplaneRoot,
123
- watchedPaths: ["src", "bin/agentplane.js", "bin/dist-guard.js"],
144
+ watchedPaths: getWatchedRuntimePathsForPackage("agentplane"),
124
145
  },
125
146
  ];
126
- if (await exists(path.join(coreRoot, "src")))
127
- checks.push({ name: "core", root: coreRoot, watchedPaths: ["src"] });
147
+ if (await exists(path.join(coreRoot, "src"))) {
148
+ checks.push({
149
+ name: "core",
150
+ root: coreRoot,
151
+ watchedPaths: getWatchedRuntimePathsForPackage("@agentplaneorg/core"),
152
+ });
153
+ }
128
154
 
129
155
  const staleReasons = [];
130
156
  for (const check of checks) {
@@ -150,6 +176,24 @@ async function assertDistUpToDate() {
150
176
  return true;
151
177
  }
152
178
 
179
+ const commandPolicy = classifyStaleDistPolicy(process.argv);
180
+ if (commandPolicy.mode === "warn_and_run") {
181
+ const commandText = process.argv
182
+ .slice(2)
183
+ .map((value) => String(value ?? "").trim())
184
+ .filter(Boolean)
185
+ .join(" ");
186
+ process.stderr.write(
187
+ "warning: allowing read-only diagnostic command to run with a stale repo build inside the framework checkout.\n" +
188
+ `command: ${commandText || "<unknown>"}\n` +
189
+ `detected: ${staleReasons.join(", ")}\n` +
190
+ "rebuild recommended:\n" +
191
+ " bun run --filter=@agentplaneorg/core build\n" +
192
+ " bun run --filter=agentplane build\n",
193
+ );
194
+ return true;
195
+ }
196
+
153
197
  process.stderr.write(
154
198
  "error: refusing to run a stale repo build (manifest/git quick-check failed).\n" +
155
199
  "Fix:\n" +
@@ -165,6 +209,12 @@ async function assertDistUpToDate() {
165
209
  return true;
166
210
  }
167
211
 
212
+ const runtimeContext = resolveFrameworkBinaryContext({
213
+ cwd: process.cwd(),
214
+ thisBin: fileURLToPath(import.meta.url),
215
+ });
216
+ primeRuntimeEnv(runtimeContext);
217
+
168
218
  if (!maybeHandoffToRepoLocalBinary()) {
169
219
  await maybeWarnGlobalBinaryInRepoCheckout();
170
220
  const ok = isHooksRunCommitMsgInvocation(process.argv) ? true : await assertDistUpToDate();
package/bin/dist-guard.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import path from "node:path";
3
3
  import { readFile, stat } from "node:fs/promises";
4
+ import { collectWatchedRuntimeSnapshot, compareWatchedRuntimeSnapshots } from "./runtime-watch.js";
4
5
 
5
6
  async function exists(p) {
6
7
  try {
@@ -33,12 +34,13 @@ function resolveGitHead(cwd) {
33
34
  }
34
35
  }
35
36
 
36
- function listGitPaths(cwd, args) {
37
+ function listGitPaths(cwd, args, options = {}) {
38
+ const trimLines = options.trimLines ?? true;
37
39
  try {
38
40
  const out = execFileSync("git", args, { cwd, encoding: "utf8" });
39
41
  return out
40
42
  .split(/\r?\n/u)
41
- .map((line) => line.trim())
43
+ .map((line) => (trimLines ? line.trim() : line))
42
44
  .filter(Boolean);
43
45
  } catch {
44
46
  return [];
@@ -50,14 +52,19 @@ function uniqueSorted(values) {
50
52
  }
51
53
 
52
54
  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));
55
+ const lines = listGitPaths(
56
+ cwd,
57
+ ["status", "--porcelain", "--untracked-files=all", "--", ...watchedPaths],
58
+ { trimLines: false },
59
+ );
60
+ return uniqueSorted(
61
+ lines
62
+ .map((line) => {
63
+ const normalized = String(line ?? "");
64
+ return normalized.length > 3 ? normalized.slice(3).trim() : "";
65
+ })
66
+ .filter(Boolean),
67
+ );
61
68
  }
62
69
 
63
70
  function committedChangedPathsSince(cwd, fromGitHead, watchedPaths) {
@@ -77,6 +84,45 @@ async function fileMtimeMs(p) {
77
84
  }
78
85
  }
79
86
 
87
+ function parseManifestSnapshot(manifest) {
88
+ if (
89
+ !Array.isArray(manifest?.watched_runtime_paths) ||
90
+ !Array.isArray(manifest?.watched_runtime_files) ||
91
+ typeof manifest?.watched_runtime_snapshot_hash !== "string"
92
+ ) {
93
+ return null;
94
+ }
95
+
96
+ const watchedPaths = manifest.watched_runtime_paths.filter((value) => typeof value === "string");
97
+ const files = manifest.watched_runtime_files
98
+ .filter(
99
+ (value) =>
100
+ value &&
101
+ typeof value === "object" &&
102
+ typeof value.path === "string" &&
103
+ typeof value.sha256 === "string" &&
104
+ typeof value.size_bytes === "number",
105
+ )
106
+ .map((value) => ({
107
+ path: value.path,
108
+ sha256: value.sha256,
109
+ size_bytes: value.size_bytes,
110
+ }));
111
+
112
+ if (
113
+ watchedPaths.length !== manifest.watched_runtime_paths.length ||
114
+ files.length !== manifest.watched_runtime_files.length
115
+ ) {
116
+ return null;
117
+ }
118
+
119
+ return {
120
+ watchedPaths,
121
+ files,
122
+ snapshotHash: manifest.watched_runtime_snapshot_hash,
123
+ };
124
+ }
125
+
80
126
  export async function isPackageBuildFresh(packageRoot, options = {}) {
81
127
  const watchedPaths = options.watchedPaths ?? ["src"];
82
128
  const manifestPath = path.join(packageRoot, "dist", ".build-manifest.json");
@@ -86,6 +132,28 @@ export async function isPackageBuildFresh(packageRoot, options = {}) {
86
132
  }
87
133
 
88
134
  const currentHead = resolveGitHead(packageRoot);
135
+ const manifestSnapshot = parseManifestSnapshot(manifest);
136
+ if (manifestSnapshot) {
137
+ const currentSnapshot = await collectWatchedRuntimeSnapshot(
138
+ packageRoot,
139
+ manifestSnapshot.watchedPaths,
140
+ );
141
+ const comparison = compareWatchedRuntimeSnapshots(manifestSnapshot, currentSnapshot);
142
+ if (!comparison.ok) {
143
+ return {
144
+ ok: false,
145
+ reason: "watched_runtime_snapshot_changed",
146
+ changedPaths: comparison.changedPaths,
147
+ };
148
+ }
149
+
150
+ if (manifest.git_head && currentHead && manifest.git_head !== currentHead) {
151
+ return { ok: true, reason: "fresh_after_snapshot_match", changedPaths: [] };
152
+ }
153
+
154
+ return { ok: true, reason: "fresh", changedPaths: [] };
155
+ }
156
+
89
157
  const changedPaths = uniqueSorted([
90
158
  ...committedChangedPathsSince(packageRoot, manifest.git_head, watchedPaths),
91
159
  ...workingTreeChangedPaths(packageRoot, watchedPaths),
@@ -8,12 +8,15 @@ export type FrameworkCheckout = {
8
8
  export type FrameworkBinaryContext = {
9
9
  inFrameworkCheckout: boolean;
10
10
  isRepoLocalBinary: boolean;
11
+ isRepoLocalRuntime: boolean;
11
12
  checkout: FrameworkCheckout | null;
12
13
  thisBin: string;
13
14
  };
14
15
 
15
16
  export function findFrameworkCheckout(startDir: string): FrameworkCheckout | null;
16
17
 
18
+ export function isPathInside(baseDir: string, targetPath: string): boolean;
19
+
17
20
  export function resolveFrameworkBinaryContext(options: {
18
21
  cwd: string;
19
22
  thisBin: string;
@@ -21,6 +21,7 @@ import path from "node:path";
21
21
  * @typedef {{
22
22
  * inFrameworkCheckout: boolean;
23
23
  * isRepoLocalBinary: boolean;
24
+ * isRepoLocalRuntime: boolean;
24
25
  * checkout: FrameworkCheckout | null;
25
26
  * thisBin: string;
26
27
  * }} FrameworkBinaryContext
@@ -55,6 +56,16 @@ export function findFrameworkCheckout(startDir) {
55
56
  }
56
57
  }
57
58
 
59
+ /**
60
+ * @param {string} baseDir
61
+ * @param {string} targetPath
62
+ * @returns {boolean}
63
+ */
64
+ export function isPathInside(baseDir, targetPath) {
65
+ const rel = path.relative(path.resolve(baseDir), path.resolve(targetPath));
66
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
67
+ }
68
+
58
69
  /**
59
70
  * @param {FrameworkBinaryContextOptions} options
60
71
  * @returns {FrameworkBinaryContext}
@@ -67,6 +78,7 @@ export function resolveFrameworkBinaryContext(options) {
67
78
  return {
68
79
  inFrameworkCheckout: false,
69
80
  isRepoLocalBinary: false,
81
+ isRepoLocalRuntime: false,
70
82
  checkout: null,
71
83
  thisBin,
72
84
  };
@@ -75,6 +87,7 @@ export function resolveFrameworkBinaryContext(options) {
75
87
  return {
76
88
  inFrameworkCheckout: true,
77
89
  isRepoLocalBinary: path.resolve(checkout.repoBin) === thisBin,
90
+ isRepoLocalRuntime: isPathInside(checkout.packageRoot, thisBin),
78
91
  checkout,
79
92
  thisBin,
80
93
  };
@@ -0,0 +1,26 @@
1
+ export type WatchedRuntimeSnapshotFile = {
2
+ path: string;
3
+ sha256: string;
4
+ size_bytes: number;
5
+ };
6
+
7
+ export type WatchedRuntimeSnapshot = {
8
+ watchedPaths: string[];
9
+ files: WatchedRuntimeSnapshotFile[];
10
+ snapshotHash: string;
11
+ };
12
+
13
+ export type WatchedRuntimeSnapshotComparison = {
14
+ ok: boolean;
15
+ changedPaths: string[];
16
+ };
17
+
18
+ export function getWatchedRuntimePathsForPackage(packageName: string): string[];
19
+ export function collectWatchedRuntimeSnapshot(
20
+ packageDir: string,
21
+ watchedPaths: string[],
22
+ ): Promise<WatchedRuntimeSnapshot>;
23
+ export function compareWatchedRuntimeSnapshots(
24
+ recordedSnapshot: WatchedRuntimeSnapshot,
25
+ currentSnapshot: WatchedRuntimeSnapshot,
26
+ ): WatchedRuntimeSnapshotComparison;
@@ -0,0 +1,116 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readdir, readFile, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const WATCHED_RUNTIME_PATHS = {
6
+ agentplane: [
7
+ "src",
8
+ "bin/agentplane.js",
9
+ "bin/dist-guard.js",
10
+ "bin/runtime-context.js",
11
+ "bin/stale-dist-policy.js",
12
+ ],
13
+ "@agentplaneorg/core": ["src"],
14
+ };
15
+
16
+ function normalizePath(value) {
17
+ return value.split(path.sep).join("/");
18
+ }
19
+
20
+ async function exists(targetPath) {
21
+ try {
22
+ await stat(targetPath);
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ async function walkFiles(targetPath) {
30
+ const targetStat = await stat(targetPath);
31
+ if (targetStat.isFile()) return [targetPath];
32
+ if (!targetStat.isDirectory()) return [];
33
+
34
+ const entries = await readdir(targetPath, { withFileTypes: true });
35
+ const files = [];
36
+ for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
37
+ const childPath = path.join(targetPath, entry.name);
38
+ if (entry.isDirectory()) {
39
+ files.push(...(await walkFiles(childPath)));
40
+ continue;
41
+ }
42
+ if (entry.isFile()) files.push(childPath);
43
+ }
44
+ return files;
45
+ }
46
+
47
+ function fileHash(content) {
48
+ return createHash("sha256").update(content).digest("hex");
49
+ }
50
+
51
+ function snapshotHash(files) {
52
+ const digest = createHash("sha256");
53
+ for (const file of files) {
54
+ digest.update(file.path);
55
+ digest.update(":");
56
+ digest.update(file.sha256);
57
+ digest.update("\n");
58
+ }
59
+ return digest.digest("hex");
60
+ }
61
+
62
+ export function getWatchedRuntimePathsForPackage(packageName) {
63
+ return [...(WATCHED_RUNTIME_PATHS[packageName] ?? ["src"])];
64
+ }
65
+
66
+ export async function collectWatchedRuntimeSnapshot(packageDir, watchedPaths) {
67
+ const normalizedWatchedPaths = [...new Set(watchedPaths.map((entry) => normalizePath(entry)))];
68
+
69
+ const absoluteFiles = [];
70
+ for (const watchedPath of normalizedWatchedPaths) {
71
+ const absolutePath = path.join(packageDir, watchedPath);
72
+ if (!(await exists(absolutePath))) continue;
73
+ absoluteFiles.push(...(await walkFiles(absolutePath)));
74
+ }
75
+
76
+ const uniqueAbsoluteFiles = [...new Set(absoluteFiles)].toSorted((a, b) => a.localeCompare(b));
77
+ const files = [];
78
+ for (const absolutePath of uniqueAbsoluteFiles) {
79
+ const content = await readFile(absolutePath);
80
+ files.push({
81
+ path: normalizePath(path.relative(packageDir, absolutePath)),
82
+ sha256: fileHash(content),
83
+ size_bytes: content.byteLength,
84
+ });
85
+ }
86
+
87
+ return {
88
+ watchedPaths: normalizedWatchedPaths,
89
+ files,
90
+ snapshotHash: snapshotHash(files),
91
+ };
92
+ }
93
+
94
+ function snapshotFileMap(snapshot) {
95
+ return new Map(snapshot.files.map((file) => [file.path, file.sha256]));
96
+ }
97
+
98
+ export function compareWatchedRuntimeSnapshots(recordedSnapshot, currentSnapshot) {
99
+ if (
100
+ recordedSnapshot.snapshotHash === currentSnapshot.snapshotHash &&
101
+ recordedSnapshot.files.length === currentSnapshot.files.length
102
+ ) {
103
+ return { ok: true, changedPaths: [] };
104
+ }
105
+
106
+ const recordedMap = snapshotFileMap(recordedSnapshot);
107
+ const currentMap = snapshotFileMap(currentSnapshot);
108
+ const changedPaths = [...new Set([...recordedMap.keys(), ...currentMap.keys()])]
109
+ .filter((filePath) => recordedMap.get(filePath) !== currentMap.get(filePath))
110
+ .toSorted((a, b) => a.localeCompare(b));
111
+
112
+ return {
113
+ ok: changedPaths.length === 0,
114
+ changedPaths,
115
+ };
116
+ }
@@ -0,0 +1,6 @@
1
+ export type StaleDistPolicy = {
2
+ mode: "warn_and_run" | "strict";
3
+ reason: "read_only_diagnostic" | "default";
4
+ };
5
+
6
+ export function classifyStaleDistPolicy(argv?: string[]): StaleDistPolicy;
@@ -0,0 +1,44 @@
1
+ function normalizeArgs(argv) {
2
+ return argv
3
+ .slice(2)
4
+ .map((value) => String(value ?? "").trim())
5
+ .filter(Boolean);
6
+ }
7
+
8
+ function isHelpOrVersionCommand(args) {
9
+ return (
10
+ args.length === 0 ||
11
+ args[0] === "--help" ||
12
+ args[0] === "-h" ||
13
+ args[0] === "--version" ||
14
+ args[0] === "-v" ||
15
+ args[0] === "help"
16
+ );
17
+ }
18
+
19
+ function isConfigInspectionCommand(args) {
20
+ return args[0] === "config" && args[1] === "show";
21
+ }
22
+
23
+ function isTaskInspectionCommand(args) {
24
+ return args[0] === "task" && ["list", "show", "verify-show"].includes(args[1] ?? "");
25
+ }
26
+
27
+ function isRoleOrQuickstartCommand(args) {
28
+ return args[0] === "quickstart" || args[0] === "role";
29
+ }
30
+
31
+ export function classifyStaleDistPolicy(argv = process.argv) {
32
+ const args = normalizeArgs(argv);
33
+ if (
34
+ args[0] === "doctor" ||
35
+ (args[0] === "runtime" && args[1] === "explain") ||
36
+ isHelpOrVersionCommand(args) ||
37
+ isRoleOrQuickstartCommand(args) ||
38
+ isConfigInspectionCommand(args) ||
39
+ isTaskInspectionCommand(args)
40
+ ) {
41
+ return { mode: "warn_and_run", reason: "read_only_diagnostic" };
42
+ }
43
+ return { mode: "strict", reason: "default" };
44
+ }