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/.agents/skills/swarm-governance/references/intake-and-planning.md +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +83 -118
- package/bin/agy-squad.mjs +2 -2
- package/docs/benchmarks.md +59 -7
- package/docs/core-workflows.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/index.md +7 -7
- package/docs/reviews/v0.1.1-review.md +397 -0
- package/docs/tools.md +3 -2
- package/mcp/briefs.mjs +13 -9
- package/mcp/dispatch.mjs +79 -16
- package/mcp/index.js +40 -3
- package/mcp/planner.mjs +13 -7
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/scripts/install-register.mjs +2 -2
- package/scripts/release-preflight.mjs +35 -6
package/mcp/dispatch.mjs
CHANGED
|
@@ -12,16 +12,26 @@ const defaultManifests = [
|
|
|
12
12
|
"suite-manifest.json"
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
function
|
|
15
|
+
function isWithinWorkspace(root, file) {
|
|
16
16
|
const relative = path.relative(root, file);
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
210
|
-
throw new Error("Only squadId and
|
|
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 =
|
|
220
|
-
const
|
|
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, {
|
|
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" ?
|
|
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 =
|
|
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}'
|
|
577
|
-
: `Audit provenanceScaffold entries for squad '${id}'
|
|
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" ?
|
|
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.
|
|
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/
|
|
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
|
@@ -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.
|
|
25
|
+
return JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8")).version || "0.1.1";
|
|
26
26
|
} catch {
|
|
27
|
-
return "0.1.
|
|
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
|
-
(
|
|
111
|
-
|
|
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 (
|
|
126
|
-
|
|
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) {
|