agy-squad 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp/dispatch.mjs CHANGED
@@ -12,16 +12,26 @@ const defaultManifests = [
12
12
  "suite-manifest.json"
13
13
  ];
14
14
 
15
- function assertWithinWorkspace(root, file) {
15
+ function isWithinWorkspace(root, file) {
16
16
  const relative = path.relative(root, file);
17
- if (relative === ".." || relative.startsWith(".." + path.sep) || path.isAbsolute(relative)) {
17
+ return !(relative === ".." || relative.startsWith(".." + path.sep) || path.isAbsolute(relative));
18
+ }
19
+
20
+ function assertWithinWorkspace(root, file) {
21
+ if (!isWithinWorkspace(root, file)) {
18
22
  throw new Error("Manifest path must stay inside the workspace.");
19
23
  }
20
24
  }
21
25
 
22
- function readManifest(root, filename) {
26
+ function readManifest(root, filename, logicalRoot = root) {
23
27
  const candidate = path.resolve(root, filename);
24
- assertWithinWorkspace(root, candidate);
28
+ if (path.isAbsolute(filename)) {
29
+ if (!isWithinWorkspace(root, candidate) && !isWithinWorkspace(logicalRoot, candidate)) {
30
+ throw new Error("Manifest path must stay inside the workspace.");
31
+ }
32
+ } else {
33
+ assertWithinWorkspace(root, candidate);
34
+ }
25
35
  const file = fs.realpathSync(candidate);
26
36
  assertWithinWorkspace(root, file);
27
37
  const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
@@ -129,20 +139,22 @@ function resolveIntegrityAuditor(squads, targetSquad) {
129
139
  ];
130
140
  }
131
141
 
132
- function loadSquads(root, manifestPath) {
142
+ function loadSquads(root, manifestPath, logicalRoot = root) {
133
143
  if (manifestPath !== undefined) {
134
- return readManifest(root, manifestPath);
144
+ return readManifest(root, manifestPath.trim(), logicalRoot);
135
145
  }
136
146
 
137
147
  let firstError;
138
148
  for (const candidate of defaultManifests) {
139
149
  try {
140
- return readManifest(root, candidate);
150
+ return readManifest(root, candidate, logicalRoot);
141
151
  } catch (error) {
142
152
  if (error.code !== "ENOENT") firstError ??= error;
143
153
  }
144
154
  }
145
- throw firstError ?? new Error("No squad manifest found. Pass a workspace-relative manifestPath.");
155
+ const notFound = new Error("No squad manifest found. Pass a workspace-relative manifestPath.");
156
+ notFound.code = "ENOENT";
157
+ throw firstError ?? notFound;
146
158
  }
147
159
 
148
160
  const inlineLeadKeys = [
@@ -203,21 +215,65 @@ function specialistAssignments(squads, squadId) {
203
215
  return specialists.map((spec, index) => ({ spec, id: squadId, number: index + 1 }));
204
216
  }
205
217
 
206
- function handleDispatchSquad(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE || process.cwd()) {
218
+ const ALLOWED_DISPATCH_KEYS = new Set(["squadId", "manifestPath", "workspace"]);
219
+
220
+ function resolveCandidateWorkspace(argsWorkspace, fallbackWorkspace) {
221
+ if (argsWorkspace !== undefined && (typeof argsWorkspace !== "string" || !argsWorkspace.trim())) {
222
+ throw new Error("workspace must be a non-empty string.");
223
+ }
224
+ const candidate = argsWorkspace !== undefined
225
+ ? argsWorkspace.trim()
226
+ : typeof fallbackWorkspace === "string" && fallbackWorkspace.trim()
227
+ ? fallbackWorkspace.trim()
228
+ : "";
229
+ if (!candidate) {
230
+ throw new Error("workspace must be a non-empty string.");
231
+ }
232
+ return candidate;
233
+ }
234
+
235
+ function logicalWorkspaceRoot(argsWorkspace, fallbackWorkspace) {
236
+ return path.resolve(resolveCandidateWorkspace(argsWorkspace, fallbackWorkspace));
237
+ }
238
+
239
+ function resolveWorkspaceRoot(argsWorkspace, fallbackWorkspace) {
240
+ const candidate = resolveCandidateWorkspace(argsWorkspace, fallbackWorkspace);
241
+ const root = fs.realpathSync(candidate);
242
+ if (!fs.statSync(root).isDirectory()) {
243
+ throw new Error(`Workspace path must be a directory: '${candidate}'.`);
244
+ }
245
+ return root;
246
+ }
247
+
248
+ function formatNotFoundError(args, fallbackWorkspace) {
249
+ const fallback = typeof fallbackWorkspace === "string" && fallbackWorkspace.trim()
250
+ ? fallbackWorkspace.trim()
251
+ : process.cwd();
252
+ const attemptedWorkspace =
253
+ typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : fallback;
254
+ const manifestDetail =
255
+ typeof args?.manifestPath === "string" && args.manifestPath.trim()
256
+ ? `, manifestPath: '${args.manifestPath.trim()}'`
257
+ : "";
258
+ return `Manifest or workspace not found (workspace: '${attemptedWorkspace}'${manifestDetail}). Pass 'workspace' set to your active repository root, set AGY_SQUAD_WORKSPACE, or run 'node bin/agy-squad.mjs' from the repository root.`;
259
+ }
260
+
261
+ function handleDispatchSquad(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE?.trim() || process.cwd()) {
207
262
  try {
208
263
  if (!args || typeof args !== "object" || Array.isArray(args) ||
209
- Object.keys(args).some(key => key !== "squadId" && key !== "manifestPath")) {
210
- throw new Error("Only squadId and manifestPath are supported.");
264
+ Object.keys(args).some(key => !ALLOWED_DISPATCH_KEYS.has(key))) {
265
+ throw new Error("Only squadId, manifestPath, and workspace are supported.");
211
266
  }
212
- const { squadId, manifestPath } = args;
267
+ const { squadId, manifestPath, workspace: argWorkspace } = args;
213
268
  if (typeof squadId !== "string" || !squadId.trim()) {
214
269
  throw new Error("Provide a squadId or use all_l1 for the leads.");
215
270
  }
216
271
  if (manifestPath !== undefined && (typeof manifestPath !== "string" || !manifestPath.trim())) {
217
272
  throw new Error("manifestPath must be a nonempty string.");
218
273
  }
219
- const root = fs.realpathSync(workspace);
220
- const { squads: loadedSquads, selectedManifest } = loadSquads(root, manifestPath);
274
+ const root = resolveWorkspaceRoot(argWorkspace, workspace);
275
+ const logicalRoot = logicalWorkspaceRoot(argWorkspace, workspace);
276
+ const { squads: loadedSquads, selectedManifest } = loadSquads(root, manifestPath, logicalRoot);
221
277
  const squads = { ...loadedSquads };
222
278
  const isIntegrityAuditor = squadId === "integrity_auditor" || squadId.startsWith("integrity_auditor:");
223
279
  const hasTargetColon = isIntegrityAuditor && squadId.includes(":");
@@ -234,8 +290,12 @@ function handleDispatchSquad(args = {}, workspace = process.env.AGY_SQUAD_WORKSP
234
290
  throw new Error(`Unknown targetSquad '${flatTarget}'.`);
235
291
  }
236
292
 
293
+ const delegateWorkspace = argWorkspace !== undefined ? root : undefined;
237
294
  const build = ({ spec, id, number }) =>
238
- buildSubagentSpec(spec, id, selectedManifest, number, undefined, { isIntegrityAuditor });
295
+ buildSubagentSpec(spec, id, selectedManifest, number, undefined, {
296
+ isIntegrityAuditor,
297
+ workspace: delegateWorkspace
298
+ });
239
299
  let assignments;
240
300
  if (squadId === "all_l1") {
241
301
  const workingSquads = Object.entries(squads).filter(
@@ -263,18 +323,21 @@ function handleDispatchSquad(args = {}, workspace = process.env.AGY_SQUAD_WORKSP
263
323
  content: [{ type: "text", text: JSON.stringify({ Subagents }, null, 2) }]
264
324
  };
265
325
  } catch (error) {
266
- const message = error.code === "ENOENT" ? "Manifest or workspace not found." : error.message;
326
+ const message = error.code === "ENOENT" ? formatNotFoundError(args, workspace) : error.message;
267
327
  return { isError: true, content: [{ type: "text", text: `dispatch_squad: ${message}` }] };
268
328
  }
269
329
  }
270
330
 
271
331
  export {
272
332
  assertWithinWorkspace,
333
+ formatNotFoundError,
273
334
  handleDispatchSquad,
274
335
  loadSquads,
336
+ logicalWorkspaceRoot,
275
337
  manifestSquads,
276
338
  readManifest,
277
339
  resolveIntegrityAuditor,
278
340
  resolveSquadLead,
341
+ resolveWorkspaceRoot,
279
342
  specialistAssignments
280
343
  };
package/mcp/index.js CHANGED
@@ -12,6 +12,38 @@ const isEntryPoint = Boolean(process.argv[1] && realOrSelf(process.argv[1]) ===
12
12
  const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
13
13
 
14
14
  const SERVER_NAME = "agy-squad";
15
+ let sessionWorkspace;
16
+
17
+ function workspaceFromInitializeParams(params) {
18
+ if (!params || typeof params !== "object") return undefined;
19
+ const candidates = [
20
+ ...(Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((folder) => folder?.uri) : []),
21
+ params.rootUri
22
+ ];
23
+ for (const candidate of candidates) {
24
+ if (typeof candidate !== "string") continue;
25
+ const trimmed = candidate.trim();
26
+ if (!/^file:\/\//i.test(trimmed)) continue;
27
+ try {
28
+ const parsedUrl = new URL(trimmed);
29
+ if (!parsedUrl.pathname || parsedUrl.pathname === "/") continue;
30
+ return fileURLToPath(parsedUrl);
31
+ } catch {
32
+ continue;
33
+ }
34
+ }
35
+ if (typeof params.rootPath === "string" && params.rootPath.trim()) {
36
+ return params.rootPath.trim();
37
+ }
38
+ return undefined;
39
+ }
40
+
41
+ const WORKSPACE_SCHEMA = {
42
+ type: "string",
43
+ minLength: 1,
44
+ description: "Active repository directory path (overrides initialize roots, AGY_SQUAD_WORKSPACE, and cwd)."
45
+ };
46
+
15
47
  const TOOLS = [
16
48
  {
17
49
  name: "dispatch_squad",
@@ -20,7 +52,8 @@ const TOOLS = [
20
52
  type: "object",
21
53
  properties: {
22
54
  squadId: { type: "string", minLength: 1, description: "'all_l1', 'integrity_auditor[:<target>]', or squad ID." },
23
- manifestPath: { type: "string", minLength: 1, description: "Workspace-relative manifest path." }
55
+ manifestPath: { type: "string", minLength: 1, description: "Workspace-relative manifest path." },
56
+ workspace: WORKSPACE_SCHEMA
24
57
  },
25
58
  required: ["squadId"],
26
59
  additionalProperties: false
@@ -40,6 +73,7 @@ const TOOLS = [
40
73
  required: ["squads"]
41
74
  },
42
75
  task: { type: "string", minLength: 1, description: "Freeform task (exclusive with manifestPath/manifest)." },
76
+ workspace: WORKSPACE_SCHEMA,
43
77
  squadId: { type: "string", minLength: 1, description: "Optional working squad filter (never 'all_l1')." },
44
78
  topology: { type: "string", enum: ["flat", "layered"], description: "Force flat (no leads) or layered (require leads) topology." },
45
79
  executionMode: { type: "string", enum: ["swarm", "subagent"], description: "Execution plane (default: swarm)." },
@@ -88,6 +122,7 @@ async function handleRequest(request) {
88
122
  }
89
123
 
90
124
  if (method === "initialize") {
125
+ sessionWorkspace = workspaceFromInitializeParams(params);
91
126
  return {
92
127
  jsonrpc: "2.0",
93
128
  id,
@@ -112,11 +147,13 @@ async function handleRequest(request) {
112
147
  return { jsonrpc: "2.0", id, error: { code: -32602, message: "Invalid params" } };
113
148
  }
114
149
  const { name, arguments: args = {} } = params;
150
+ const envWorkspace = process.env.AGY_SQUAD_WORKSPACE?.trim();
151
+ const defaultWorkspace = sessionWorkspace || envWorkspace || process.cwd();
115
152
  if (name === "dispatch_squad") {
116
- return { jsonrpc: "2.0", id, result: handleDispatchSquad(args) };
153
+ return { jsonrpc: "2.0", id, result: handleDispatchSquad(args, defaultWorkspace) };
117
154
  }
118
155
  if (name === "squad_planner") {
119
- return { jsonrpc: "2.0", id, result: handleSquadPlanner(args) };
156
+ return { jsonrpc: "2.0", id, result: handleSquadPlanner(args, defaultWorkspace) };
120
157
  }
121
158
  return { jsonrpc: "2.0", id, error: { code: -32602, message: `Unknown tool: ${name}` } };
122
159
  }
package/mcp/planner.mjs CHANGED
@@ -2,10 +2,13 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { SUBAGENT_MODEL_ALIASES, buildSubagentSpec, validateScopeFiles } from "./briefs.mjs";
4
4
  import {
5
+ formatNotFoundError,
5
6
  loadSquads,
7
+ logicalWorkspaceRoot,
6
8
  manifestSquads,
7
9
  resolveIntegrityAuditor,
8
10
  resolveSquadLead,
11
+ resolveWorkspaceRoot,
9
12
  specialistAssignments
10
13
  } from "./dispatch.mjs";
11
14
  import { normalizeReportRoot, reportPath } from "./handoffs.mjs";
@@ -14,6 +17,7 @@ const ALLOWED_PLANNER_KEYS = new Set([
14
17
  "manifestPath",
15
18
  "manifest",
16
19
  "task",
20
+ "workspace",
17
21
  "squadId",
18
22
  "executionMode",
19
23
  "model",
@@ -235,7 +239,7 @@ function buildSwarmWorkerBrief(compiledBrief, targetModel, workspaceIsolation) {
235
239
  * or invoke_subagent dispatch, explicit model provenance, scope-overlap checks, and L6-safe
236
240
  * integrity audit scaffolding.
237
241
  */
238
- function planSquadCohort(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE || process.cwd()) {
242
+ function planSquadCohort(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE?.trim() || process.cwd()) {
239
243
  if (!args || typeof args !== "object" || Array.isArray(args)) {
240
244
  throw new Error("Provide a planner options object.");
241
245
  }
@@ -249,6 +253,7 @@ function planSquadCohort(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE
249
253
  manifestPath,
250
254
  manifest,
251
255
  task,
256
+ workspace: argWorkspace,
252
257
  squadId,
253
258
  executionMode = "swarm",
254
259
  model,
@@ -303,7 +308,8 @@ function planSquadCohort(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE
303
308
  }
304
309
  const normalizedReportRoot = reportRoot !== undefined ? normalizeReportRoot(reportRoot) : undefined;
305
310
 
306
- const root = fs.realpathSync(workspace);
311
+ const root = resolveWorkspaceRoot(argWorkspace, workspace);
312
+ const logicalRoot = logicalWorkspaceRoot(argWorkspace, workspace);
307
313
  let loadedSquads;
308
314
  let selectedManifest;
309
315
  if (task !== undefined) {
@@ -328,7 +334,7 @@ function planSquadCohort(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE
328
334
  if (scopeFiles !== undefined || roles !== undefined) {
329
335
  throw new Error("scopeFiles and roles can only be passed with task.");
330
336
  }
331
- const loaded = loadSquads(root, manifestPath);
337
+ const loaded = loadSquads(root, manifestPath, logicalRoot);
332
338
  loadedSquads = loaded.squads;
333
339
  selectedManifest = loaded.selectedManifest;
334
340
  }
@@ -573,8 +579,8 @@ function planSquadCohort(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE
573
579
  overriddenSubagentModels
574
580
  );
575
581
  const targetScopeNote = executionMode === "swarm"
576
- ? `Audit provenanceScaffold entries for squad '${id}'. Verify reports, verifiedFiles hashes, and Evidence Digest ('agy-squad digest <record.json>'). read_board/swarm status confirm handoffs/roster/model only; verify worker execution traces via 'agy-context transcript query --id <id> --summary' (when granted access) or via a coordinator-extracted sanitized transcript index in scratch/ before CLEAN (re-execution alone cannot satisfy CLEAN; report 'Integrity Verdict: INTEGRITY VIOLATION' if traces are unavailable). NEVER copy raw .system_generated/ transcripts into scratch/.`
577
- : `Audit provenanceScaffold entries for squad '${id}'. Verify reports, verifiedFiles hashes, Evidence Digest ('agy-squad digest <record.json>'), and spot-check traces ONLY via single-pass 'agy-context transcript query --id <id> --summary' (or query_transcript(summaryOnly=true)) on coordinator-recorded conversationIds. NEVER copy .system_generated/ transcripts into scratch/.`;
582
+ ? `Audit provenanceScaffold entries for squad '${id}', verifiedFiles hashes, and Evidence Digest ('agy-squad digest <record.json>'). Use a coordinator-extracted sanitized transcript index in scratch/ when transcript tools are restricted (re-execution alone cannot satisfy CLEAN; report 'Integrity Verdict: INTEGRITY VIOLATION' if traces are unavailable).`
583
+ : `Audit provenanceScaffold entries for squad '${id}', verifiedFiles hashes, and Evidence Digest ('agy-squad digest <record.json>') against coordinator-recorded conversationIds.`;
578
584
  const compiledAudit = buildSubagentSpec(
579
585
  {
580
586
  ...rawAudit,
@@ -740,14 +746,14 @@ function planSquadCohort(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE
740
746
  };
741
747
  }
742
748
 
743
- function handleSquadPlanner(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE || process.cwd()) {
749
+ function handleSquadPlanner(args = {}, workspace = process.env.AGY_SQUAD_WORKSPACE?.trim() || process.cwd()) {
744
750
  try {
745
751
  const plan = planSquadCohort(args, workspace);
746
752
  return {
747
753
  content: [{ type: "text", text: JSON.stringify(plan, null, 2) }]
748
754
  };
749
755
  } catch (error) {
750
- const message = error.code === "ENOENT" ? "Manifest or workspace not found." : error.message;
756
+ const message = error.code === "ENOENT" ? formatNotFoundError(args, workspace) : error.message;
751
757
  return { isError: true, content: [{ type: "text", text: `squad_planner: ${message}` }] };
752
758
  }
753
759
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agy-squad",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Swarm planning, Pointer-Over-Wire governance, integrity auditing, and SHA-256 verification for Google Antigravity.",
5
5
  "main": "mcp/index.js",
6
6
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "type": "git",
36
36
  "url": "https://github.com/paragon-ux/Agy-Squad.git"
37
37
  },
38
- "homepage": "https://paragon-ux.github.io/agy-squad/",
38
+ "homepage": "https://paragon-ux.github.io/Agy-Squad/",
39
39
  "bugs": {
40
40
  "url": "https://github.com/paragon-ux/Agy-Squad/issues"
41
41
  },
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agy-squad",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Swarm planning, Pointer-Over-Wire governance, integrity auditing, and SHA-256 verification for Google Antigravity.",
5
5
  "author": {
6
6
  "name": "paragon-ux"
@@ -22,9 +22,9 @@ const repoRoot = path.resolve(__dirname, "..");
22
22
 
23
23
  const PACKAGE_VERSION = (() => {
24
24
  try {
25
- return JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8")).version || "0.1.0";
25
+ return JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8")).version || "0.1.1";
26
26
  } catch {
27
- return "0.1.0";
27
+ return "0.1.1";
28
28
  }
29
29
  })();
30
30
 
@@ -82,6 +82,7 @@ if (pkg) {
82
82
  const requiredPackageFiles = [
83
83
  "bin/agy-squad.mjs",
84
84
  "mcp/index.js",
85
+ "mcp/dispatch.mjs",
85
86
  "mcp/planner.mjs",
86
87
  "mcp/briefs.mjs",
87
88
  "mcp/handoffs.mjs",
@@ -91,6 +92,8 @@ if (pkg) {
91
92
  ".agents/skills/pr-review/SKILL.md",
92
93
  ".agents/skills/full-review/SKILL.md",
93
94
  ".agents/skills/self-diagnosis/SKILL.md",
95
+ ".agents/skills/self-diagnosis/scripts/diagnose.mjs",
96
+ ".agents/skills/self-diagnosis/resources/self-diagnosis-manifest.json",
94
97
  ".agents/skills/swarm-governance/SKILL.md",
95
98
  "plugin.json",
96
99
  "install.sh",
@@ -100,6 +103,27 @@ if (pkg) {
100
103
  "LICENSE"
101
104
  ];
102
105
 
106
+ const fileEntries = (pkg.files ?? [])
107
+ .filter((entry) => typeof entry === "string" && entry.trim())
108
+ .map((entry) => {
109
+ const negated = entry.startsWith("!");
110
+ const normalized = (negated ? entry.slice(1) : entry)
111
+ .replace(/\\/g, "/")
112
+ .replace(/^\.\//, "")
113
+ .replace(/\/+$/, "");
114
+ return { negated, normalized };
115
+ });
116
+ const positiveEntries = fileEntries.filter((entry) => !entry.negated);
117
+ const negativeEntries = fileEntries.filter((entry) => entry.negated);
118
+ const matchesPackageEntry = (relativePath, normalized) => {
119
+ if (!normalized) return false;
120
+ if (normalized.includes("*")) {
121
+ const escaped = normalized.replace(/[.+^${}()|[\]\\]/g, "\\$&");
122
+ return new RegExp(`^${escaped.replace(/\*\*|\*/g, (m) => (m === "**" ? ".*" : "[^/]*"))}$`).test(relativePath);
123
+ }
124
+ return relativePath === normalized || relativePath.startsWith(`${normalized}/`);
125
+ };
126
+
103
127
  for (const relativePath of requiredPackageFiles) {
104
128
  if (!exists(relativePath)) {
105
129
  errors.push(`Required runtime/release file is missing: ${relativePath}`);
@@ -107,10 +131,8 @@ if (pkg) {
107
131
  const alwaysIncluded = ["package.json", "README.md", "CHANGELOG.md", "LICENSE"].includes(relativePath);
108
132
  const coveredByFiles =
109
133
  alwaysIncluded ||
110
- (pkg.files ?? []).some((entry) => {
111
- const normalized = entry.replace(/\\/g, "/").replace(/\/$/, "");
112
- return relativePath === normalized || relativePath.startsWith(`${normalized}/`);
113
- });
134
+ (positiveEntries.some((entry) => matchesPackageEntry(relativePath, entry.normalized)) &&
135
+ !negativeEntries.some((entry) => matchesPackageEntry(relativePath, entry.normalized)));
114
136
  if (!coveredByFiles) {
115
137
  errors.push(`package.json "files" does not clearly include ${relativePath}.`);
116
138
  }
@@ -122,8 +144,15 @@ if (pkg) {
122
144
  }
123
145
  }
124
146
 
125
- if (!pkg.scripts?.setup || !pkg.scripts?.status || !pkg.scripts?.uninstall || !pkg.scripts?.preflight || !pkg.scripts?.test) {
126
- errors.push("Expected npm scripts setup, status, uninstall, preflight, and test to be present.");
147
+ if (
148
+ !pkg.scripts?.setup ||
149
+ !pkg.scripts?.status ||
150
+ !pkg.scripts?.uninstall ||
151
+ !pkg.scripts?.preflight ||
152
+ !pkg.scripts?.test ||
153
+ !pkg.scripts?.["verify:package"]
154
+ ) {
155
+ errors.push("Expected npm scripts setup, status, uninstall, preflight, test, and verify:package to be present.");
127
156
  }
128
157
 
129
158
  if (Object.keys(pkg.dependencies ?? {}).length > 0) {