@gempack/squad-mcp 0.6.5 → 0.8.0
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +11 -6
- package/CHANGELOG.md +62 -0
- package/INSTALL.md +37 -22
- package/README.md +54 -18
- package/agents/code-explorer.md +77 -0
- package/agents/product-owner.md +10 -1
- package/agents/senior-architect.md +12 -0
- package/agents/senior-dba.md +15 -1
- package/agents/senior-dev-reviewer.md +101 -30
- package/agents/senior-dev-security.md +13 -0
- package/agents/senior-developer.md +15 -0
- package/agents/senior-qa.md +14 -1
- package/agents/tech-lead-consolidator.md +10 -0
- package/agents/tech-lead-planner.md +17 -0
- package/commands/brainstorm.md +12 -2
- package/commands/implement.md +32 -0
- package/commands/next.md +24 -0
- package/commands/question.md +20 -0
- package/commands/review.md +30 -0
- package/commands/task.md +29 -0
- package/commands/tasks.md +21 -0
- package/dist/config/ownership-matrix.d.ts +1 -1
- package/dist/config/ownership-matrix.js +21 -20
- package/dist/config/ownership-matrix.js.map +1 -1
- package/dist/config/squad-yaml.d.ts +1 -1
- package/dist/config/squad-yaml.js +4 -8
- package/dist/config/squad-yaml.js.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/exec/git.d.ts +1 -1
- package/dist/exec/git.js +0 -0
- package/dist/exec/git.js.map +1 -1
- package/dist/format/pr-review.js +1 -3
- package/dist/format/pr-review.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/learning/format.js +1 -5
- package/dist/learning/format.js.map +1 -1
- package/dist/learning/store.d.ts +1 -1
- package/dist/learning/store.js +90 -17
- package/dist/learning/store.js.map +1 -1
- package/dist/observability/logger.d.ts +2 -2
- package/dist/observability/logger.js +20 -20
- package/dist/observability/logger.js.map +1 -1
- package/dist/prompts/registry.js.map +1 -1
- package/dist/resources/agent-loader.js +1 -0
- package/dist/resources/agent-loader.js.map +1 -1
- package/dist/resources/registry.js +28 -28
- package/dist/tasks/select.js.map +1 -1
- package/dist/tasks/store.d.ts +2 -2
- package/dist/tasks/store.js +50 -12
- package/dist/tasks/store.js.map +1 -1
- package/dist/tools/_shared/schemas.d.ts +21 -0
- package/dist/tools/_shared/schemas.js +25 -0
- package/dist/tools/_shared/schemas.js.map +1 -0
- package/dist/tools/agents.d.ts +3 -3
- package/dist/tools/agents.js +9 -9
- package/dist/tools/agents.js.map +1 -1
- package/dist/tools/classify-work-type.d.ts +5 -5
- package/dist/tools/classify-work-type.js +0 -0
- package/dist/tools/classify-work-type.js.map +1 -1
- package/dist/tools/compose-advisory-bundle.d.ts +8 -0
- package/dist/tools/compose-advisory-bundle.js +12 -14
- package/dist/tools/compose-advisory-bundle.js.map +1 -1
- package/dist/tools/compose-prd-parse.js.map +1 -1
- package/dist/tools/compose-squad-workflow.d.ts +30 -1
- package/dist/tools/compose-squad-workflow.js +0 -0
- package/dist/tools/compose-squad-workflow.js.map +1 -1
- package/dist/tools/consolidate.js +1 -3
- package/dist/tools/consolidate.js.map +1 -1
- package/dist/tools/detect-changed-files.d.ts +5 -6
- package/dist/tools/detect-changed-files.js +0 -0
- package/dist/tools/detect-changed-files.js.map +1 -1
- package/dist/tools/list-tasks.js +1 -8
- package/dist/tools/list-tasks.js.map +1 -1
- package/dist/tools/mode/exec-mode.d.ts +124 -0
- package/dist/tools/mode/exec-mode.js +153 -0
- package/dist/tools/mode/exec-mode.js.map +1 -0
- package/dist/tools/next-task.js +1 -8
- package/dist/tools/next-task.js.map +1 -1
- package/dist/tools/read-learnings.js +3 -5
- package/dist/tools/read-learnings.js.map +1 -1
- package/dist/tools/read-squad-config.js +1 -1
- package/dist/tools/read-squad-config.js.map +1 -1
- package/dist/tools/record-learning.d.ts +1 -1
- package/dist/tools/record-learning.js +1 -1
- package/dist/tools/record-tasks.js.map +1 -1
- package/dist/tools/registry.js +2 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/score-risk.d.ts +3 -3
- package/dist/tools/score-risk.js +15 -15
- package/dist/tools/score-rubric.js.map +1 -1
- package/dist/tools/select-squad.d.ts +5 -5
- package/dist/tools/select-squad.js +0 -0
- package/dist/tools/select-squad.js.map +1 -1
- package/dist/tools/slice-files-for-task.js.map +1 -1
- package/dist/tools/slice-files.d.ts +2 -2
- package/dist/tools/slice-files.js +0 -0
- package/dist/tools/slice-files.js.map +1 -1
- package/dist/tools/update-task-status.js +1 -8
- package/dist/tools/update-task-status.js.map +1 -1
- package/dist/tools/validate-plan-text.d.ts +3 -3
- package/dist/tools/validate-plan-text.js +0 -0
- package/dist/tools/validate-plan-text.js.map +1 -1
- package/dist/util/file-lock.d.ts +10 -0
- package/dist/util/file-lock.js +102 -0
- package/dist/util/file-lock.js.map +1 -0
- package/dist/util/override-allowlist.d.ts +4 -4
- package/dist/util/override-allowlist.js +36 -27
- package/dist/util/override-allowlist.js.map +1 -1
- package/dist/util/path-internal.js +10 -8
- package/dist/util/path-internal.js.map +1 -1
- package/dist/util/path-safety.d.ts +15 -0
- package/dist/util/path-safety.js +47 -13
- package/dist/util/path-safety.js.map +1 -1
- package/package.json +13 -2
- package/shared/Skill-Squad-Dev.md +46 -35
- package/shared/Skill-Squad-Review.md +64 -41
- package/shared/_Severity-and-Ownership.md +6 -6
- package/skills/brainstorm/SKILL.md +50 -37
- package/skills/commit-suggest/SKILL.md +32 -14
- package/skills/question/SKILL.md +110 -0
- package/skills/squad/SKILL.md +70 -26
- package/tools/_tasks-io.mjs +25 -16
- package/tools/list-tasks.mjs +1 -4
- package/tools/next-task.mjs +4 -13
- package/tools/post-review.mjs +20 -30
- package/tools/record-learning.mjs +8 -11
- package/tools/record-tasks.mjs +2 -9
- package/tools/update-task-status.mjs +2 -9
- package/commands/squad-review.md +0 -20
- package/commands/squad.md +0 -22
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface FileLockOptions {
|
|
2
|
+
/** Total wall-clock budget to acquire the lock (ms). */
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Acquire the lock for `targetPath` (operates on `${targetPath}.lock`),
|
|
7
|
+
* run `fn`, then release. The lock file is removed in a finally so a
|
|
8
|
+
* thrown body still cleans up.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withFileLock<T>(targetPath: string, fn: () => Promise<T>, opts?: FileLockOptions): Promise<T>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Cross-process advisory lock implemented via O_EXCL on a sibling `.lock`
|
|
5
|
+
* file. Used to serialise read-modify-write on `.squad/tasks.json` and
|
|
6
|
+
* appends to `.squad/learnings.jsonl` when multiple MCP server processes
|
|
7
|
+
* share a workspace (a common case — two Claude clients open in the same
|
|
8
|
+
* repo).
|
|
9
|
+
*
|
|
10
|
+
* Limitations:
|
|
11
|
+
* - Not a kernel-enforced lock. Cooperative — every writer must use this.
|
|
12
|
+
* - A crash mid-section can leave a stale lock. After STALE_AFTER_MS the
|
|
13
|
+
* lock is force-acquired (one unlink, then retry once). Mtime is the
|
|
14
|
+
* staleness signal; we do NOT trust pid inside the file because PIDs
|
|
15
|
+
* can be reused across containers.
|
|
16
|
+
* - Single-host. NFS rename + flock semantics are weaker; not in scope.
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
19
|
+
const DEFAULT_RETRY_BASE_MS = 15;
|
|
20
|
+
const DEFAULT_RETRY_MAX_MS = 75;
|
|
21
|
+
const STALE_AFTER_MS = 30_000;
|
|
22
|
+
async function sleep(ms) {
|
|
23
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
24
|
+
}
|
|
25
|
+
async function tryAcquire(lockPath) {
|
|
26
|
+
try {
|
|
27
|
+
const fh = await fs.open(lockPath, "wx");
|
|
28
|
+
// The body is for human inspection only; logic depends only on existence + mtime.
|
|
29
|
+
await fh.writeFile(`${process.pid}\t${Date.now()}\n`, { encoding: "utf8" });
|
|
30
|
+
await fh.close();
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const code = err.code;
|
|
35
|
+
if (code === "EEXIST")
|
|
36
|
+
return false;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function isStale(lockPath) {
|
|
41
|
+
try {
|
|
42
|
+
const stat = await fs.stat(lockPath);
|
|
43
|
+
return Date.now() - stat.mtimeMs > STALE_AFTER_MS;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Lock vanished — treat as stale; the next tryAcquire will create it.
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Acquire the lock for `targetPath` (operates on `${targetPath}.lock`),
|
|
52
|
+
* run `fn`, then release. The lock file is removed in a finally so a
|
|
53
|
+
* thrown body still cleans up.
|
|
54
|
+
*/
|
|
55
|
+
export async function withFileLock(targetPath, fn, opts = {}) {
|
|
56
|
+
const lockPath = `${targetPath}.lock`;
|
|
57
|
+
// The lock lives alongside the target. The body may be creating the target
|
|
58
|
+
// for the first time so the parent directory may not exist yet — mkdir is
|
|
59
|
+
// cheap and idempotent.
|
|
60
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
61
|
+
const deadline = Date.now() + (opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
62
|
+
let attempt = 0;
|
|
63
|
+
let acquired = false;
|
|
64
|
+
while (!acquired) {
|
|
65
|
+
if (await tryAcquire(lockPath)) {
|
|
66
|
+
acquired = true;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
if (Date.now() > deadline) {
|
|
70
|
+
// Last chance: if the existing lock looks stale, force-acquire.
|
|
71
|
+
if (await isStale(lockPath)) {
|
|
72
|
+
try {
|
|
73
|
+
await fs.unlink(lockPath);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Another holder may have just removed it themselves — retry once.
|
|
77
|
+
}
|
|
78
|
+
if (await tryAcquire(lockPath)) {
|
|
79
|
+
acquired = true;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`file-lock: timed out acquiring ${lockPath} after ${opts.timeoutMs ?? DEFAULT_TIMEOUT_MS}ms`);
|
|
84
|
+
}
|
|
85
|
+
attempt += 1;
|
|
86
|
+
// Jittered backoff to avoid lockstep contention between processes.
|
|
87
|
+
const base = Math.min(DEFAULT_RETRY_BASE_MS * attempt, DEFAULT_RETRY_MAX_MS);
|
|
88
|
+
await sleep(base + Math.floor(Math.random() * 10));
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return await fn();
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
try {
|
|
95
|
+
await fs.unlink(lockPath);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Best-effort. If a stale-recovery removed it concurrently, that's fine.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=file-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/util/file-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SquadError } from
|
|
1
|
+
import { SquadError } from "../errors.js";
|
|
2
2
|
/**
|
|
3
3
|
* Validates that a directory chosen as the agent-override location lives under
|
|
4
4
|
* a known user-controlled prefix, and that per-file resolutions inside it stay
|
|
@@ -14,16 +14,16 @@ import { SquadError } from '../errors.js';
|
|
|
14
14
|
* `realpath()` and `fs.readFile()`. Acceptable for a single-user dev tool — an
|
|
15
15
|
* attacker with write access to a user-allowlisted root has already won.
|
|
16
16
|
*/
|
|
17
|
-
export type OverrideRejectionReason =
|
|
17
|
+
export type OverrideRejectionReason = "malformed" | "not_absolute" | "unc_or_device_namespace" | "outside_allowlist" | "symlink_escape";
|
|
18
18
|
export interface AllowlistRoot {
|
|
19
|
-
source:
|
|
19
|
+
source: "home" | "appdata" | "localappdata" | "xdg_config_home" | "cwd";
|
|
20
20
|
lexical: string;
|
|
21
21
|
real: string;
|
|
22
22
|
}
|
|
23
23
|
export interface ValidationOk {
|
|
24
24
|
ok: true;
|
|
25
25
|
resolvedPath: string;
|
|
26
|
-
allowlistMatch: AllowlistRoot[
|
|
26
|
+
allowlistMatch: AllowlistRoot["source"] | "unsafe_override";
|
|
27
27
|
unsafeOverride: boolean;
|
|
28
28
|
}
|
|
29
29
|
export interface ValidationFail {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { promises as fs } from
|
|
2
|
-
import path from
|
|
3
|
-
import os from
|
|
4
|
-
import { SquadError } from
|
|
5
|
-
import { rejectIfMalformed, realpathOrSelf } from
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { SquadError } from "../errors.js";
|
|
5
|
+
import { rejectIfMalformed, realpathOrSelf } from "./path-internal.js";
|
|
6
6
|
let allowlistCache = null;
|
|
7
7
|
/**
|
|
8
8
|
* Test-only: clears the memoized allowlist so env-var changes take effect.
|
|
@@ -12,16 +12,16 @@ export function __resetOverrideAllowlistCache() {
|
|
|
12
12
|
allowlistCache = null;
|
|
13
13
|
}
|
|
14
14
|
function isUnsafeOverrideEnabled() {
|
|
15
|
-
return process.env.SQUAD_AGENTS_ALLOW_UNSAFE ===
|
|
15
|
+
return process.env.SQUAD_AGENTS_ALLOW_UNSAFE === "1";
|
|
16
16
|
}
|
|
17
17
|
function isUncOrDeviceNamespace(absolute) {
|
|
18
|
-
if (process.platform !==
|
|
18
|
+
if (process.platform !== "win32")
|
|
19
19
|
return false;
|
|
20
|
-
if (absolute.startsWith(
|
|
20
|
+
if (absolute.startsWith("\\\\?\\"))
|
|
21
21
|
return true;
|
|
22
|
-
if (absolute.startsWith(
|
|
22
|
+
if (absolute.startsWith("\\\\.\\"))
|
|
23
23
|
return true;
|
|
24
|
-
if (absolute.startsWith(
|
|
24
|
+
if (absolute.startsWith("\\\\"))
|
|
25
25
|
return true;
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
@@ -31,15 +31,15 @@ async function buildAllowlist() {
|
|
|
31
31
|
const roots = [];
|
|
32
32
|
const seen = new Set();
|
|
33
33
|
const candidates = [
|
|
34
|
-
{ source:
|
|
35
|
-
{ source:
|
|
34
|
+
{ source: "home", raw: os.homedir() },
|
|
35
|
+
{ source: "cwd", raw: process.cwd() },
|
|
36
36
|
];
|
|
37
|
-
if (process.platform ===
|
|
38
|
-
candidates.push({ source:
|
|
39
|
-
candidates.push({ source:
|
|
37
|
+
if (process.platform === "win32") {
|
|
38
|
+
candidates.push({ source: "appdata", raw: process.env.APPDATA });
|
|
39
|
+
candidates.push({ source: "localappdata", raw: process.env.LOCALAPPDATA });
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
|
-
candidates.push({ source:
|
|
42
|
+
candidates.push({ source: "xdg_config_home", raw: process.env.XDG_CONFIG_HOME });
|
|
43
43
|
}
|
|
44
44
|
for (const c of candidates) {
|
|
45
45
|
if (!c.raw)
|
|
@@ -69,13 +69,13 @@ function isInsideRoot(candidate, root) {
|
|
|
69
69
|
if (candidate === root)
|
|
70
70
|
return true;
|
|
71
71
|
const rel = path.relative(root, candidate);
|
|
72
|
-
if (rel ===
|
|
72
|
+
if (rel === "" || rel === ".")
|
|
73
73
|
return true;
|
|
74
74
|
if (path.isAbsolute(rel))
|
|
75
75
|
return false;
|
|
76
|
-
if (rel ===
|
|
76
|
+
if (rel === "..")
|
|
77
77
|
return false;
|
|
78
|
-
if (rel.startsWith(
|
|
78
|
+
if (rel.startsWith(".." + path.sep))
|
|
79
79
|
return false;
|
|
80
80
|
return true;
|
|
81
81
|
}
|
|
@@ -103,14 +103,14 @@ export async function validateOverrideDir(rawDir) {
|
|
|
103
103
|
try {
|
|
104
104
|
rejectIfMalformed(rawDir);
|
|
105
105
|
}
|
|
106
|
-
catch
|
|
107
|
-
return { ok: false, reason:
|
|
106
|
+
catch {
|
|
107
|
+
return { ok: false, reason: "malformed", rejectedPath: rawDir };
|
|
108
108
|
}
|
|
109
109
|
if (!path.isAbsolute(rawDir)) {
|
|
110
|
-
return { ok: false, reason:
|
|
110
|
+
return { ok: false, reason: "not_absolute", rejectedPath: rawDir };
|
|
111
111
|
}
|
|
112
112
|
if (isUncOrDeviceNamespace(rawDir)) {
|
|
113
|
-
return { ok: false, reason:
|
|
113
|
+
return { ok: false, reason: "unc_or_device_namespace", rejectedPath: rawDir };
|
|
114
114
|
}
|
|
115
115
|
const lexical = path.normalize(rawDir);
|
|
116
116
|
const real = await realpathOrSelf(lexical);
|
|
@@ -129,9 +129,16 @@ export async function validateOverrideDir(rawDir) {
|
|
|
129
129
|
break;
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
-
const reason = lexicalMatchExists
|
|
132
|
+
const reason = lexicalMatchExists
|
|
133
|
+
? "symlink_escape"
|
|
134
|
+
: "outside_allowlist";
|
|
133
135
|
if (unsafe) {
|
|
134
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
ok: true,
|
|
138
|
+
resolvedPath: real,
|
|
139
|
+
allowlistMatch: "unsafe_override",
|
|
140
|
+
unsafeOverride: true,
|
|
141
|
+
};
|
|
135
142
|
}
|
|
136
143
|
return { ok: false, reason, rejectedPath: rawDir };
|
|
137
144
|
}
|
|
@@ -140,7 +147,7 @@ export async function validateOverrideDir(rawDir) {
|
|
|
140
147
|
* Caller decides whether to throw or downgrade to a warn-and-fallback.
|
|
141
148
|
*/
|
|
142
149
|
export function rejectionToError(fail, allowlistSize) {
|
|
143
|
-
return new SquadError(
|
|
150
|
+
return new SquadError("OVERRIDE_REJECTED", `override directory rejected: ${fail.reason}`, {
|
|
144
151
|
reason: fail.reason,
|
|
145
152
|
path: fail.rejectedPath,
|
|
146
153
|
allowlist_size: allowlistSize,
|
|
@@ -169,7 +176,9 @@ export async function validateOverrideFile(validatedDirReal, fileName) {
|
|
|
169
176
|
const normalized = path.normalize(fileName);
|
|
170
177
|
if (path.isAbsolute(normalized))
|
|
171
178
|
return null;
|
|
172
|
-
if (normalized ===
|
|
179
|
+
if (normalized === ".." ||
|
|
180
|
+
normalized.startsWith(".." + path.sep) ||
|
|
181
|
+
normalized.includes(path.sep + ".." + path.sep)) {
|
|
173
182
|
return null;
|
|
174
183
|
}
|
|
175
184
|
const candidate = path.resolve(validatedDirReal, normalized);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"override-allowlist.js","sourceRoot":"","sources":["../../src/util/override-allowlist.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"override-allowlist.js","sourceRoot":"","sources":["../../src/util/override-allowlist.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { promises as fs } from
|
|
2
|
-
import { SquadError } from
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { SquadError } from "../errors.js";
|
|
3
3
|
/**
|
|
4
4
|
* Internal helpers shared between path-safety.ts and override-allowlist.ts.
|
|
5
5
|
* Exported only for intra-`util/` reuse; do not import from outside src/util/.
|
|
6
6
|
*/
|
|
7
7
|
export function rejectIfMalformed(file) {
|
|
8
|
-
if (file.includes(
|
|
9
|
-
throw new SquadError(
|
|
8
|
+
if (file.includes("\0")) {
|
|
9
|
+
throw new SquadError("PATH_INVALID", "file path contains NUL byte", { file });
|
|
10
10
|
}
|
|
11
|
-
if (file.startsWith(
|
|
12
|
-
throw new SquadError(
|
|
11
|
+
if (file.startsWith("~")) {
|
|
12
|
+
throw new SquadError("PATH_INVALID", "file path starts with ~ (tilde expansion not supported)", { file });
|
|
13
13
|
}
|
|
14
|
-
const adsIndex = file.indexOf(
|
|
14
|
+
const adsIndex = file.indexOf(":", 2);
|
|
15
15
|
if (adsIndex !== -1) {
|
|
16
|
-
throw new SquadError(
|
|
16
|
+
throw new SquadError("PATH_INVALID", "file path contains ADS marker (:) after drive letter", {
|
|
17
|
+
file,
|
|
18
|
+
});
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
export async function realpathOrSelf(p) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-internal.js","sourceRoot":"","sources":["../../src/util/path-internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;GAGG;AAEH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,6BAA6B,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,UAAU,
|
|
1
|
+
{"version":3,"file":"path-internal.js","sourceRoot":"","sources":["../../src/util/path-internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;GAGG;AAEH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,6BAA6B,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,UAAU,CAClB,cAAc,EACd,yDAAyD,EACzD,EAAE,IAAI,EAAE,CACT,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACtC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,sDAAsD,EAAE;YAC3F,IAAI;SACL,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,CAAS;IAC5C,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
|
|
@@ -3,6 +3,21 @@ export interface SafePathContext {
|
|
|
3
3
|
rootRealCache: Map<string, string>;
|
|
4
4
|
}
|
|
5
5
|
export declare function createSafePathContext(): SafePathContext;
|
|
6
|
+
/**
|
|
7
|
+
* Lexical-only containment check for a config-supplied relative path
|
|
8
|
+
* (e.g. `.squad.yaml` `learnings.path` / `tasks.path`).
|
|
9
|
+
*
|
|
10
|
+
* Sync; no filesystem access. Throws PATH_TRAVERSAL_DENIED if `configuredPath`
|
|
11
|
+
* is absolute, or escapes `workspaceRoot` after lexical normalization. Used at
|
|
12
|
+
* the boundary where the LLM-controllable config first becomes a real fs path
|
|
13
|
+
* — without this, `.squad.yaml` with `learnings.path: ../../etc/whatever` gives
|
|
14
|
+
* an arbitrary-write primitive (CWE-22).
|
|
15
|
+
*
|
|
16
|
+
* Does NOT resolve symlinks. The config itself lives inside the workspace, so
|
|
17
|
+
* TOCTOU symlink swap is not in this gateway's threat model — the writer side
|
|
18
|
+
* (resolveSafePath) handles that for the data write path.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureRelativeInsideRoot(workspaceRoot: string, configuredPath: string, settingName: string): void;
|
|
6
21
|
/**
|
|
7
22
|
* Resolve a user-supplied file path safely against a workspace root.
|
|
8
23
|
*
|
package/dist/util/path-safety.js
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
import { promises as fs } from
|
|
2
|
-
import path from
|
|
3
|
-
import { SquadError } from
|
|
4
|
-
import { rejectIfMalformed, realpathOrSelf } from
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { SquadError } from "../errors.js";
|
|
4
|
+
import { rejectIfMalformed, realpathOrSelf } from "./path-internal.js";
|
|
5
5
|
export const MAX_BYTES = 16_384;
|
|
6
6
|
export function createSafePathContext() {
|
|
7
7
|
return { rootRealCache: new Map() };
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Lexical-only containment check for a config-supplied relative path
|
|
11
|
+
* (e.g. `.squad.yaml` `learnings.path` / `tasks.path`).
|
|
12
|
+
*
|
|
13
|
+
* Sync; no filesystem access. Throws PATH_TRAVERSAL_DENIED if `configuredPath`
|
|
14
|
+
* is absolute, or escapes `workspaceRoot` after lexical normalization. Used at
|
|
15
|
+
* the boundary where the LLM-controllable config first becomes a real fs path
|
|
16
|
+
* — without this, `.squad.yaml` with `learnings.path: ../../etc/whatever` gives
|
|
17
|
+
* an arbitrary-write primitive (CWE-22).
|
|
18
|
+
*
|
|
19
|
+
* Does NOT resolve symlinks. The config itself lives inside the workspace, so
|
|
20
|
+
* TOCTOU symlink swap is not in this gateway's threat model — the writer side
|
|
21
|
+
* (resolveSafePath) handles that for the data write path.
|
|
22
|
+
*/
|
|
23
|
+
export function ensureRelativeInsideRoot(workspaceRoot, configuredPath, settingName) {
|
|
24
|
+
if (path.isAbsolute(configuredPath)) {
|
|
25
|
+
throw new SquadError("PATH_TRAVERSAL_DENIED", `${settingName} must be a workspace-relative path, not absolute`, { setting: settingName, configuredPath });
|
|
26
|
+
}
|
|
27
|
+
const rootAbs = path.resolve(workspaceRoot);
|
|
28
|
+
const candidateAbs = path.resolve(rootAbs, configuredPath);
|
|
29
|
+
const rel = path.relative(rootAbs, candidateAbs);
|
|
30
|
+
if (path.isAbsolute(rel) || rel === ".." || rel.startsWith(".." + path.sep)) {
|
|
31
|
+
throw new SquadError("PATH_TRAVERSAL_DENIED", `${settingName} escapes workspace_root`, {
|
|
32
|
+
setting: settingName,
|
|
33
|
+
configuredPath,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
9
37
|
/**
|
|
10
38
|
* Resolve a user-supplied file path safely against a workspace root.
|
|
11
39
|
*
|
|
@@ -20,13 +48,15 @@ export function createSafePathContext() {
|
|
|
20
48
|
export async function resolveSafePath(workspaceRoot, file, ctx) {
|
|
21
49
|
rejectIfMalformed(file);
|
|
22
50
|
if (workspaceRoot === undefined) {
|
|
23
|
-
if (path.isAbsolute(file) || file.includes(
|
|
24
|
-
throw new SquadError(
|
|
51
|
+
if (path.isAbsolute(file) || file.includes("..")) {
|
|
52
|
+
throw new SquadError("PATH_REQUIRES_WORKSPACE", "absolute or traversal-bearing path requires workspace_root", { file });
|
|
25
53
|
}
|
|
26
54
|
return file;
|
|
27
55
|
}
|
|
28
56
|
if (!path.isAbsolute(workspaceRoot)) {
|
|
29
|
-
throw new SquadError(
|
|
57
|
+
throw new SquadError("PATH_INVALID", "workspace_root must be absolute", {
|
|
58
|
+
workspaceRoot,
|
|
59
|
+
});
|
|
30
60
|
}
|
|
31
61
|
const rootNormalized = path.normalize(workspaceRoot);
|
|
32
62
|
let rootReal = ctx.rootRealCache.get(rootNormalized);
|
|
@@ -37,8 +67,12 @@ export async function resolveSafePath(workspaceRoot, file, ctx) {
|
|
|
37
67
|
const fileNormalized = path.normalize(file);
|
|
38
68
|
const candidateAbs = path.resolve(rootReal, fileNormalized);
|
|
39
69
|
const lexicalRel = path.relative(rootReal, candidateAbs);
|
|
40
|
-
if (path.isAbsolute(lexicalRel) ||
|
|
41
|
-
|
|
70
|
+
if (path.isAbsolute(lexicalRel) ||
|
|
71
|
+
lexicalRel === ".." ||
|
|
72
|
+
lexicalRel.startsWith(".." + path.sep)) {
|
|
73
|
+
throw new SquadError("PATH_TRAVERSAL_DENIED", "path escapes workspace_root (lexical)", {
|
|
74
|
+
file,
|
|
75
|
+
});
|
|
42
76
|
}
|
|
43
77
|
let candidateExists = false;
|
|
44
78
|
try {
|
|
@@ -51,8 +85,8 @@ export async function resolveSafePath(workspaceRoot, file, ctx) {
|
|
|
51
85
|
if (candidateExists) {
|
|
52
86
|
const candidateReal = await realpathOrSelf(candidateAbs);
|
|
53
87
|
const realRel = path.relative(rootReal, candidateReal);
|
|
54
|
-
if (path.isAbsolute(realRel) || realRel ===
|
|
55
|
-
throw new SquadError(
|
|
88
|
+
if (path.isAbsolute(realRel) || realRel === ".." || realRel.startsWith(".." + path.sep)) {
|
|
89
|
+
throw new SquadError("PATH_TRAVERSAL_DENIED", "path escapes workspace_root (after realpath)", { file });
|
|
56
90
|
}
|
|
57
91
|
return candidateReal;
|
|
58
92
|
}
|
|
@@ -67,7 +101,7 @@ export async function resolveSafePath(workspaceRoot, file, ctx) {
|
|
|
67
101
|
export async function readSnippet(absPath) {
|
|
68
102
|
let fh;
|
|
69
103
|
try {
|
|
70
|
-
fh = await fs.open(absPath,
|
|
104
|
+
fh = await fs.open(absPath, "r");
|
|
71
105
|
}
|
|
72
106
|
catch {
|
|
73
107
|
return null;
|
|
@@ -76,7 +110,7 @@ export async function readSnippet(absPath) {
|
|
|
76
110
|
const buf = Buffer.alloc(MAX_BYTES);
|
|
77
111
|
const { bytesRead } = await fh.read(buf, 0, MAX_BYTES, 0);
|
|
78
112
|
return {
|
|
79
|
-
content: buf.slice(0, bytesRead).toString(
|
|
113
|
+
content: buf.slice(0, bytesRead).toString("utf8"),
|
|
80
114
|
truncated: bytesRead === MAX_BYTES,
|
|
81
115
|
};
|
|
82
116
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-safety.js","sourceRoot":"","sources":["../../src/util/path-safety.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEvE,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC;AAMhC,MAAM,UAAU,qBAAqB;IACnC,OAAO,EAAE,aAAa,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;AACtC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,aAAiC,EACjC,IAAY,EACZ,GAAoB;IAEpB,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAExB,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,UAAU,CAClB,yBAAyB,EACzB,4DAA4D,EAC5D,EAAE,IAAI,EAAE,CACT,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,iCAAiC,EAAE,
|
|
1
|
+
{"version":3,"file":"path-safety.js","sourceRoot":"","sources":["../../src/util/path-safety.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gempack/squad-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "MCP server for the squad-dev workflow: classification, risk scoring, agent selection, advisory orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -37,7 +37,12 @@
|
|
|
37
37
|
"dev": "tsx src/index.ts",
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"test:watch": "vitest",
|
|
40
|
-
"
|
|
40
|
+
"test:coverage": "vitest run --coverage",
|
|
41
|
+
"smoke": "node tests/smoke.mjs",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"lint": "tsc --noEmit && eslint .",
|
|
44
|
+
"format": "prettier --write .",
|
|
45
|
+
"format:check": "prettier --check .",
|
|
41
46
|
"prepublishOnly": "npm run build"
|
|
42
47
|
},
|
|
43
48
|
"files": [
|
|
@@ -75,8 +80,14 @@
|
|
|
75
80
|
"devDependencies": {
|
|
76
81
|
"@types/js-yaml": "^4.0.9",
|
|
77
82
|
"@types/node": "^22.0.0",
|
|
83
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
84
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
85
|
+
"@vitest/coverage-v8": "^2.1.0",
|
|
86
|
+
"eslint": "^9.0.0",
|
|
87
|
+
"prettier": "^3.3.0",
|
|
78
88
|
"tsx": "^4.19.0",
|
|
79
89
|
"typescript": "^5.6.0",
|
|
90
|
+
"typescript-eslint": "^8.0.0",
|
|
80
91
|
"vitest": "^2.1.0"
|
|
81
92
|
}
|
|
82
93
|
}
|