@printwithsynergy/synergy-mcp 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/CLAUDE.md +119 -0
- package/LICENSE +661 -0
- package/README.md +160 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/blast-radius.d.ts +33 -0
- package/dist/tools/blast-radius.d.ts.map +1 -0
- package/dist/tools/blast-radius.js +205 -0
- package/dist/tools/blast-radius.js.map +1 -0
- package/dist/tools/floor-pin-grid.d.ts +23 -0
- package/dist/tools/floor-pin-grid.d.ts.map +1 -0
- package/dist/tools/floor-pin-grid.js +133 -0
- package/dist/tools/floor-pin-grid.js.map +1 -0
- package/dist/tools/pattern-audit.d.ts +30 -0
- package/dist/tools/pattern-audit.d.ts.map +1 -0
- package/dist/tools/pattern-audit.js +184 -0
- package/dist/tools/pattern-audit.js.map +1 -0
- package/dist/tools/stack-health-grid.d.ts +35 -0
- package/dist/tools/stack-health-grid.d.ts.map +1 -0
- package/dist/tools/stack-health-grid.js +110 -0
- package/dist/tools/stack-health-grid.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* blast_radius — given a target (symbol, HTTP route, or env var),
|
|
3
|
+
* scan every configured repo's working tree and return the call
|
|
4
|
+
* sites.
|
|
5
|
+
*
|
|
6
|
+
* V1 uses ripgrep-style regex matching tuned to the language of
|
|
7
|
+
* each repo (Python vs TypeScript). It's not full AST analysis —
|
|
8
|
+
* ctxo (https://github.com/anthropics/code-review-graph) does that
|
|
9
|
+
* for TS/Go/C# but doesn't ship a Python plugin yet. Grep is the
|
|
10
|
+
* working substitute that lint-pdf's CLAUDE.md already calls out.
|
|
11
|
+
*
|
|
12
|
+
* Three target kinds:
|
|
13
|
+
*
|
|
14
|
+
* 1. `symbol` — e.g. `CodexDocument`, `withTenant`. Matches Python
|
|
15
|
+
* `from X import Y` lines and TS `import { Y } from 'X'` lines,
|
|
16
|
+
* plus bare usage sites.
|
|
17
|
+
* 2. `route` — e.g. `POST /v1/extract` or `/api/v1/jobs`. Matches
|
|
18
|
+
* quoted occurrences of the path in any source file.
|
|
19
|
+
* 3. `env_var` — e.g. `LENS_SERVER_URL`, `STRIPE_SECRET_KEY`.
|
|
20
|
+
* Matches `process.env.X`, `process.env["X"]`, `os.getenv("X")`,
|
|
21
|
+
* `os.environ.get("X")`, `os.environ["X"]`, and YAML `X:` lines.
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
24
|
+
import { extname, join } from "node:path";
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
import { KNOWN_REPOS, REPO_LANG, resolveAllRepoPaths, } from "../config.js";
|
|
27
|
+
export const BlastRadiusInput = z.object({
|
|
28
|
+
target: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.describe("What to search for. Examples: 'CodexDocument' (a symbol), '/v1/extract' (an HTTP route), 'LENS_SERVER_URL' (an env var). Use `kind` to disambiguate."),
|
|
32
|
+
kind: z
|
|
33
|
+
.enum(["symbol", "route", "env_var"])
|
|
34
|
+
.default("symbol")
|
|
35
|
+
.describe("How to interpret `target`. 'symbol' matches import lines + bare usage. 'route' matches the path string as a quoted literal. 'env_var' matches process.env / os.environ / os.getenv access."),
|
|
36
|
+
repos: z
|
|
37
|
+
.array(z.string())
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Optional subset of repos to search. When omitted, searches all configured repos."),
|
|
40
|
+
max_results_per_repo: z
|
|
41
|
+
.number()
|
|
42
|
+
.int()
|
|
43
|
+
.positive()
|
|
44
|
+
.default(50)
|
|
45
|
+
.describe("Cap on hits per repo to keep the response bounded."),
|
|
46
|
+
});
|
|
47
|
+
function escapeRegex(s) {
|
|
48
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
49
|
+
}
|
|
50
|
+
function buildPatterns(target, kind, lang) {
|
|
51
|
+
const t = escapeRegex(target);
|
|
52
|
+
if (kind === "env_var") {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
kind: "env",
|
|
56
|
+
re: new RegExp(
|
|
57
|
+
// TS: process.env.X, process.env['X'], process.env["X"]
|
|
58
|
+
// Python: os.getenv("X"), os.environ["X"], os.environ.get("X")
|
|
59
|
+
// YAML: X: value
|
|
60
|
+
`(?:process\\.env(?:\\.${t}|\\[['"]${t}['"]\\]))|(?:os\\.getenv\\(['"]${t}['"]\\))|(?:os\\.environ(?:\\[['"]${t}['"]\\]|\\.get\\(['"]${t}['"]\\)))|(?:^\\s*${t}\\s*:)`, "m"),
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
if (kind === "route") {
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
kind: "route",
|
|
68
|
+
re: new RegExp(`['"\`]${t}['"\`]`),
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
// symbol
|
|
73
|
+
const patterns = [];
|
|
74
|
+
if (lang === "python") {
|
|
75
|
+
patterns.push({
|
|
76
|
+
kind: "import",
|
|
77
|
+
re: new RegExp(`^\\s*from\\s+\\S+\\s+import\\s+[^#]*\\b${t}\\b`, "m"),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
patterns.push({
|
|
82
|
+
kind: "import",
|
|
83
|
+
re: new RegExp(`^\\s*import\\s+(?:[^;]*\\{[^}]*\\b${t}\\b[^}]*\\}|\\*\\s+as\\s+${t}|${t})`, "m"),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
patterns.push({
|
|
87
|
+
kind: "usage",
|
|
88
|
+
re: new RegExp(`\\b${t}\\b`),
|
|
89
|
+
});
|
|
90
|
+
return patterns;
|
|
91
|
+
}
|
|
92
|
+
const SOURCE_EXT = new Set([
|
|
93
|
+
".ts",
|
|
94
|
+
".tsx",
|
|
95
|
+
".js",
|
|
96
|
+
".jsx",
|
|
97
|
+
".mjs",
|
|
98
|
+
".cjs",
|
|
99
|
+
".py",
|
|
100
|
+
".pyi",
|
|
101
|
+
".yml",
|
|
102
|
+
".yaml",
|
|
103
|
+
]);
|
|
104
|
+
function isSearchable(name) {
|
|
105
|
+
return SOURCE_EXT.has(extname(name));
|
|
106
|
+
}
|
|
107
|
+
function walk(dir, onFile, depth = 0, maxDepth = 8) {
|
|
108
|
+
if (depth > maxDepth)
|
|
109
|
+
return true;
|
|
110
|
+
let entries;
|
|
111
|
+
try {
|
|
112
|
+
entries = readdirSync(dir);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
for (const name of entries) {
|
|
118
|
+
if (name === "node_modules" ||
|
|
119
|
+
name === ".git" ||
|
|
120
|
+
name === "dist" ||
|
|
121
|
+
name === "build" ||
|
|
122
|
+
name === ".venv" ||
|
|
123
|
+
name === "__pycache__" ||
|
|
124
|
+
name === ".turbo" ||
|
|
125
|
+
name === ".next") {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const full = join(dir, name);
|
|
129
|
+
let s;
|
|
130
|
+
try {
|
|
131
|
+
s = statSync(full);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (s.isDirectory()) {
|
|
137
|
+
const keepGoing = walk(full, onFile, depth + 1, maxDepth);
|
|
138
|
+
if (!keepGoing)
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
else if (s.isFile() && isSearchable(name)) {
|
|
142
|
+
const keepGoing = onFile(full);
|
|
143
|
+
if (!keepGoing)
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
function searchRepo(repo, root, patterns, cap) {
|
|
150
|
+
const hits = [];
|
|
151
|
+
walk(root, (file) => {
|
|
152
|
+
let text;
|
|
153
|
+
try {
|
|
154
|
+
text = readFileSync(file, "utf8");
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
const lines = text.split("\n");
|
|
160
|
+
for (let i = 0; i < lines.length; i++) {
|
|
161
|
+
const line = lines[i] ?? "";
|
|
162
|
+
for (const p of patterns) {
|
|
163
|
+
if (p.re.test(line)) {
|
|
164
|
+
hits.push({
|
|
165
|
+
repo,
|
|
166
|
+
file,
|
|
167
|
+
line: i + 1,
|
|
168
|
+
text: line.trim().slice(0, 300),
|
|
169
|
+
kind: p.kind,
|
|
170
|
+
});
|
|
171
|
+
if (hits.length >= cap)
|
|
172
|
+
return false;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
});
|
|
179
|
+
return hits;
|
|
180
|
+
}
|
|
181
|
+
export async function blastRadius(input) {
|
|
182
|
+
const paths = resolveAllRepoPaths();
|
|
183
|
+
const notes = [];
|
|
184
|
+
const subset = input.repos
|
|
185
|
+
? input.repos.filter((r) => KNOWN_REPOS.includes(r))
|
|
186
|
+
: KNOWN_REPOS;
|
|
187
|
+
const allHits = [];
|
|
188
|
+
for (const repo of subset) {
|
|
189
|
+
const root = paths[repo];
|
|
190
|
+
if (!root) {
|
|
191
|
+
notes.push(`repo ${repo}: not configured`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const patterns = buildPatterns(input.target, input.kind, REPO_LANG[repo]);
|
|
195
|
+
const hits = searchRepo(repo, root, patterns, input.max_results_per_repo);
|
|
196
|
+
allHits.push(...hits);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
target: input.target,
|
|
200
|
+
kind: input.kind,
|
|
201
|
+
hits: allHits,
|
|
202
|
+
notes,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=blast-radius.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blast-radius.js","sourceRoot":"","sources":["../../src/tools/blast-radius.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type RepoName } from "../config.js";
|
|
3
|
+
export declare const FloorPinGridInput: z.ZodObject<{
|
|
4
|
+
package: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
package?: string | undefined;
|
|
7
|
+
}, {
|
|
8
|
+
package?: string | undefined;
|
|
9
|
+
}>;
|
|
10
|
+
/** A single row in the grid. */
|
|
11
|
+
export interface FloorPinRow {
|
|
12
|
+
repo: RepoName;
|
|
13
|
+
package: string;
|
|
14
|
+
spec: string;
|
|
15
|
+
/** Which manifest file + section the pin came from. */
|
|
16
|
+
source: string;
|
|
17
|
+
}
|
|
18
|
+
export interface FloorPinGridResult {
|
|
19
|
+
rows: FloorPinRow[];
|
|
20
|
+
notes: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function floorPinGrid(input: z.infer<typeof FloorPinGridInput>): Promise<FloorPinGridResult>;
|
|
23
|
+
//# sourceMappingURL=floor-pin-grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floor-pin-grid.d.ts","sourceRoot":"","sources":["../../src/tools/floor-pin-grid.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAe,KAAK,QAAQ,EAAuB,MAAM,cAAc,CAAC;AAE/E,eAAO,MAAM,iBAAiB;;;;;;EAO5B,CAAC;AAEH,gCAAgC;AAChC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AA+FD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,GACvC,OAAO,CAAC,kBAAkB,CAAC,CA8B7B"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* floor_pin_grid — return the version-pin matrix for org packages
|
|
3
|
+
* across every configured repo.
|
|
4
|
+
*
|
|
5
|
+
* Audits Python `pyproject.toml` `dependencies = [...]` lists and
|
|
6
|
+
* Node `package.json` `dependencies` + `devDependencies` +
|
|
7
|
+
* `peerDependencies` for the known printwithsynergy package names.
|
|
8
|
+
*
|
|
9
|
+
* Audit finding #2 in the cross-stack audit caught lint-pdf pinning
|
|
10
|
+
* `codex-pdf>=1.19.0` with no upper bound while compile-pdf pinned
|
|
11
|
+
* `>=1.15.0,<2.0`. This tool surfaces that mismatch as a one-call
|
|
12
|
+
* grid query.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { KNOWN_REPOS, resolveAllRepoPaths } from "../config.js";
|
|
18
|
+
export const FloorPinGridInput = z.object({
|
|
19
|
+
package: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Optional package-name filter (e.g. 'codex-pdf' or '@printwithsynergy/lens-pdf'). When omitted, returns rows for every detected org package across every repo."),
|
|
23
|
+
});
|
|
24
|
+
/** Known org package names worth surfacing in the grid. */
|
|
25
|
+
const ORG_PACKAGES = [
|
|
26
|
+
"codex-pdf",
|
|
27
|
+
"lint-pdf",
|
|
28
|
+
"compile-pdf",
|
|
29
|
+
"@printwithsynergy/codex-client",
|
|
30
|
+
"@printwithsynergy/lens-pdf",
|
|
31
|
+
"@printwithsynergy/client",
|
|
32
|
+
"@printwithsynergy/synergy-mcp",
|
|
33
|
+
];
|
|
34
|
+
function isOrgPackage(name) {
|
|
35
|
+
return ORG_PACKAGES.includes(name);
|
|
36
|
+
}
|
|
37
|
+
/** Parse a single pyproject.toml `dependencies = [...]` entry.
|
|
38
|
+
*
|
|
39
|
+
* Hand-rolled rather than `[\\s\\S]*?\\]` regex because package specs
|
|
40
|
+
* legitimately contain `]` (e.g. `uvicorn[standard]>=0.30.0`), which
|
|
41
|
+
* a lazy regex would close on. We walk lines instead: find
|
|
42
|
+
* `^dependencies = [`, then accumulate until `^]` at line start. */
|
|
43
|
+
function parsePyprojectDeps(repo, root) {
|
|
44
|
+
const path = join(root, "pyproject.toml");
|
|
45
|
+
if (!existsSync(path))
|
|
46
|
+
return [];
|
|
47
|
+
const text = readFileSync(path, "utf8");
|
|
48
|
+
const rows = [];
|
|
49
|
+
const lines = text.split("\n");
|
|
50
|
+
let inDeps = false;
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
if (!inDeps) {
|
|
53
|
+
if (/^dependencies\s*=\s*\[/.test(line))
|
|
54
|
+
inDeps = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (/^\]\s*$/.test(line))
|
|
58
|
+
break;
|
|
59
|
+
// Match the package name + version spec inside quotes; the spec
|
|
60
|
+
// may legitimately contain commas + < > = ~ ! characters.
|
|
61
|
+
const m = line.match(/['"]([A-Za-z0-9_.\-]+)(\[[^\]]*\])?([<>=!~,][^'"]*)?['"]/);
|
|
62
|
+
if (m?.[1]) {
|
|
63
|
+
const name = m[1].trim();
|
|
64
|
+
const spec = (m[3] ?? "").trim() || "(unpinned)";
|
|
65
|
+
if (isOrgPackage(name)) {
|
|
66
|
+
rows.push({
|
|
67
|
+
repo,
|
|
68
|
+
package: name,
|
|
69
|
+
spec,
|
|
70
|
+
source: "pyproject.toml#dependencies",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
/** Parse package.json dep sections for org packages. */
|
|
78
|
+
function parsePackageJson(repo, root) {
|
|
79
|
+
const path = join(root, "package.json");
|
|
80
|
+
if (!existsSync(path))
|
|
81
|
+
return [];
|
|
82
|
+
const rows = [];
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
85
|
+
for (const section of [
|
|
86
|
+
"dependencies",
|
|
87
|
+
"devDependencies",
|
|
88
|
+
"peerDependencies",
|
|
89
|
+
]) {
|
|
90
|
+
const block = parsed[section];
|
|
91
|
+
if (block && typeof block === "object") {
|
|
92
|
+
for (const [name, spec] of Object.entries(block)) {
|
|
93
|
+
if (isOrgPackage(name)) {
|
|
94
|
+
rows.push({
|
|
95
|
+
repo,
|
|
96
|
+
package: name,
|
|
97
|
+
spec,
|
|
98
|
+
source: `package.json#${section}`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Malformed JSON — skip rather than throw; surface as empty rows.
|
|
107
|
+
}
|
|
108
|
+
return rows;
|
|
109
|
+
}
|
|
110
|
+
export async function floorPinGrid(input) {
|
|
111
|
+
const paths = resolveAllRepoPaths();
|
|
112
|
+
const rows = [];
|
|
113
|
+
const notes = [];
|
|
114
|
+
for (const repo of KNOWN_REPOS) {
|
|
115
|
+
const root = paths[repo];
|
|
116
|
+
if (!root) {
|
|
117
|
+
notes.push(`repo ${repo}: not configured (set SYNERGY_MCP_PATH_${repo
|
|
118
|
+
.toUpperCase()
|
|
119
|
+
.replace(/-/g, "_")} or SYNERGY_MCP_STACK_ROOT)`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
rows.push(...parsePyprojectDeps(repo, root));
|
|
123
|
+
rows.push(...parsePackageJson(repo, root));
|
|
124
|
+
}
|
|
125
|
+
const filtered = input.package
|
|
126
|
+
? rows.filter((r) => r.package === input.package)
|
|
127
|
+
: rows;
|
|
128
|
+
return {
|
|
129
|
+
rows: filtered.sort((a, b) => a.package.localeCompare(b.package) || a.repo.localeCompare(b.repo)),
|
|
130
|
+
notes,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=floor-pin-grid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floor-pin-grid.js","sourceRoot":"","sources":["../../src/tools/floor-pin-grid.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type RepoName } from "../config.js";
|
|
3
|
+
export declare const PatternAuditInput: z.ZodObject<{
|
|
4
|
+
patterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
patterns?: string[] | undefined;
|
|
7
|
+
}, {
|
|
8
|
+
patterns?: string[] | undefined;
|
|
9
|
+
}>;
|
|
10
|
+
export interface PatternRow {
|
|
11
|
+
repo: RepoName;
|
|
12
|
+
pattern: string;
|
|
13
|
+
present: boolean;
|
|
14
|
+
evidence: string;
|
|
15
|
+
}
|
|
16
|
+
/** Return all pattern names this tool knows. */
|
|
17
|
+
export declare function knownPatterns(): {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
}[];
|
|
21
|
+
export interface PatternAuditResult {
|
|
22
|
+
rows: PatternRow[];
|
|
23
|
+
notes: string[];
|
|
24
|
+
available_patterns: {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
export declare function patternAudit(input: z.infer<typeof PatternAuditInput>): Promise<PatternAuditResult>;
|
|
30
|
+
//# sourceMappingURL=pattern-audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-audit.d.ts","sourceRoot":"","sources":["../../src/tools/pattern-audit.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAe,KAAK,QAAQ,EAAuB,MAAM,cAAc,CAAC;AAE/E,eAAO,MAAM,iBAAiB;;;;;;EAO5B,CAAC;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiGD,gDAAgD;AAChD,wBAAgB,aAAa,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EAAE,CAKvE;AA+DD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kBAAkB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7D;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,GACvC,OAAO,CAAC,kBAAkB,CAAC,CAwB7B"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pattern_audit — return the (repo × pattern) matrix for the patterns
|
|
3
|
+
* the cross-stack audit found inconsistently applied.
|
|
4
|
+
*
|
|
5
|
+
* Each pattern has a probe function that decides {present, evidence}
|
|
6
|
+
* for a given repo working tree. Patterns are intentionally cheap:
|
|
7
|
+
* file-exists checks, regex matches against well-known manifests,
|
|
8
|
+
* or directory presence. No AST.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { KNOWN_REPOS, resolveAllRepoPaths } from "../config.js";
|
|
14
|
+
export const PatternAuditInput = z.object({
|
|
15
|
+
patterns: z
|
|
16
|
+
.array(z.string())
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Optional subset of pattern names to evaluate. When omitted, evaluates all known patterns. See the description of `pattern` in each row for the full list."),
|
|
19
|
+
});
|
|
20
|
+
const PROBES = {
|
|
21
|
+
has_ci: {
|
|
22
|
+
description: "Repo has at least one .github/workflows/*.yml file. Audit finding #1 caught platform with zero workflows on main.",
|
|
23
|
+
probe: (root) => {
|
|
24
|
+
const dir = join(root, ".github", "workflows");
|
|
25
|
+
if (!existsSync(dir))
|
|
26
|
+
return { present: false, evidence: "no .github/workflows" };
|
|
27
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
28
|
+
return {
|
|
29
|
+
present: files.length > 0,
|
|
30
|
+
evidence: files.join(", ") || "(empty dir)",
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
has_openapi_contract_endpoint: {
|
|
35
|
+
description: "Repo exposes a /v1/contract HTTP endpoint (the codex/compile convention).",
|
|
36
|
+
probe: (root) => {
|
|
37
|
+
const hits = grepFirst(root, /["']\/v1\/contract["']/, ["src", "apps"]);
|
|
38
|
+
return hits
|
|
39
|
+
? { present: true, evidence: hits }
|
|
40
|
+
: { present: false, evidence: "no /v1/contract reference" };
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
has_consume_surface_audit: {
|
|
44
|
+
description: "Repo has scripts/consume_surface_audit.py (compile-pdf's tripwire — banning re-implementation of an upstream's primitives).",
|
|
45
|
+
probe: (root) => {
|
|
46
|
+
const p = join(root, "scripts", "consume_surface_audit.py");
|
|
47
|
+
return existsSync(p)
|
|
48
|
+
? { present: true, evidence: "scripts/consume_surface_audit.py" }
|
|
49
|
+
: { present: false, evidence: "not present" };
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
has_engine_purity_audit: {
|
|
53
|
+
description: "Repo has scripts/check_engine_purity.sh (lint-pdf's tripwire — banning analyzer-side imports of host services).",
|
|
54
|
+
probe: (root) => {
|
|
55
|
+
const p = join(root, "scripts", "check_engine_purity.sh");
|
|
56
|
+
return existsSync(p)
|
|
57
|
+
? { present: true, evidence: "scripts/check_engine_purity.sh" }
|
|
58
|
+
: { present: false, evidence: "not present" };
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
has_openapi_descriptions_audit: {
|
|
62
|
+
description: "Repo has scripts/check_openapi_descriptions.py (lint-pdf's tripwire — every Pydantic Field must include description=).",
|
|
63
|
+
probe: (root) => {
|
|
64
|
+
const p = join(root, "scripts", "check_openapi_descriptions.py");
|
|
65
|
+
return existsSync(p)
|
|
66
|
+
? { present: true, evidence: "scripts/check_openapi_descriptions.py" }
|
|
67
|
+
: { present: false, evidence: "not present" };
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
has_problem_details_errors: {
|
|
71
|
+
description: "Repo emits RFC 7807 Problem Details (audit finding #13: the org-aligned target error envelope).",
|
|
72
|
+
probe: (root) => {
|
|
73
|
+
const hits = grepFirst(root, /problem[-_]?details|application\/problem\+json|rfc7807/i, ["src", "apps", "packages"]);
|
|
74
|
+
return hits
|
|
75
|
+
? { present: true, evidence: hits }
|
|
76
|
+
: { present: false, evidence: "no Problem Details reference" };
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
has_claude_md_deep: {
|
|
80
|
+
description: "CLAUDE.md is longer than 50 lines (a rough proxy for 'has architectural rules', not just process bullets).",
|
|
81
|
+
probe: (root) => {
|
|
82
|
+
const p = join(root, "CLAUDE.md");
|
|
83
|
+
if (!existsSync(p))
|
|
84
|
+
return { present: false, evidence: "no CLAUDE.md" };
|
|
85
|
+
const lines = readFileSync(p, "utf8").split("\n").length;
|
|
86
|
+
return {
|
|
87
|
+
present: lines >= 50,
|
|
88
|
+
evidence: `${lines} lines`,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
/** Return all pattern names this tool knows. */
|
|
94
|
+
export function knownPatterns() {
|
|
95
|
+
return Object.entries(PROBES).map(([name, p]) => ({
|
|
96
|
+
name,
|
|
97
|
+
description: p.description,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
/** Walk a few top-level directories looking for the first match of `re`. */
|
|
101
|
+
function grepFirst(root, re, dirs) {
|
|
102
|
+
for (const dir of dirs) {
|
|
103
|
+
const p = join(root, dir);
|
|
104
|
+
if (!existsSync(p))
|
|
105
|
+
continue;
|
|
106
|
+
const hit = walkFirst(p, re, 0, 4);
|
|
107
|
+
if (hit)
|
|
108
|
+
return hit;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
function walkFirst(dir, re, depth, maxDepth) {
|
|
113
|
+
if (depth > maxDepth)
|
|
114
|
+
return null;
|
|
115
|
+
let entries;
|
|
116
|
+
try {
|
|
117
|
+
entries = readdirSync(dir);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
for (const name of entries) {
|
|
123
|
+
if (name === "node_modules" || name === ".git" || name === "dist")
|
|
124
|
+
continue;
|
|
125
|
+
const full = join(dir, name);
|
|
126
|
+
let s;
|
|
127
|
+
try {
|
|
128
|
+
s = statSync(full);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (s.isDirectory()) {
|
|
134
|
+
const sub = walkFirst(full, re, depth + 1, maxDepth);
|
|
135
|
+
if (sub)
|
|
136
|
+
return sub;
|
|
137
|
+
}
|
|
138
|
+
else if (s.isFile() &&
|
|
139
|
+
/\.(ts|tsx|js|py|json|toml|yml|yaml|md)$/.test(name)) {
|
|
140
|
+
try {
|
|
141
|
+
const text = readFileSync(full, "utf8");
|
|
142
|
+
if (re.test(text)) {
|
|
143
|
+
// Find the first line that hits
|
|
144
|
+
const lines = text.split("\n");
|
|
145
|
+
for (let i = 0; i < lines.length; i++) {
|
|
146
|
+
const line = lines[i];
|
|
147
|
+
if (line !== undefined && re.test(line)) {
|
|
148
|
+
return `${full}:${i + 1}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return full;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// ignore binary/unreadable
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
export async function patternAudit(input) {
|
|
162
|
+
const paths = resolveAllRepoPaths();
|
|
163
|
+
const notes = [];
|
|
164
|
+
const probesToRun = input.patterns ?? Object.keys(PROBES);
|
|
165
|
+
const rows = [];
|
|
166
|
+
for (const repo of KNOWN_REPOS) {
|
|
167
|
+
const root = paths[repo];
|
|
168
|
+
if (!root) {
|
|
169
|
+
notes.push(`repo ${repo}: not configured`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
for (const name of probesToRun) {
|
|
173
|
+
const probe = PROBES[name];
|
|
174
|
+
if (!probe) {
|
|
175
|
+
notes.push(`pattern '${name}' is not known; ignored`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const result = probe.probe(root);
|
|
179
|
+
rows.push({ repo, pattern: name, ...result });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { rows, notes, available_patterns: knownPatterns() };
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=pattern-audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-audit.js","sourceRoot":"","sources":["../../src/tools/pattern-audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stack_health_grid — fetch /healthz (and /v1/contract when available)
|
|
3
|
+
* across the configured prod URLs for the stack, in parallel, and
|
|
4
|
+
* return a grid with status code + latency + version skew.
|
|
5
|
+
*
|
|
6
|
+
* Audit finding #15 in the cross-stack audit caught five different
|
|
7
|
+
* health/contract endpoint patterns. This tool branches per-service
|
|
8
|
+
* so the caller doesn't have to.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { type RepoName } from "../config.js";
|
|
12
|
+
export declare const StackHealthGridInput: z.ZodObject<{
|
|
13
|
+
repos: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
14
|
+
timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
timeout_ms: number;
|
|
17
|
+
repos?: string[] | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
repos?: string[] | undefined;
|
|
20
|
+
timeout_ms?: number | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
export interface HealthRow {
|
|
23
|
+
repo: RepoName;
|
|
24
|
+
url: string;
|
|
25
|
+
endpoint: "healthz" | "v1/healthz" | "health" | "ready" | "v1/contract";
|
|
26
|
+
status: number | "timeout" | "error";
|
|
27
|
+
latency_ms: number | null;
|
|
28
|
+
body_summary: string;
|
|
29
|
+
}
|
|
30
|
+
export interface StackHealthGridResult {
|
|
31
|
+
rows: HealthRow[];
|
|
32
|
+
notes: string[];
|
|
33
|
+
}
|
|
34
|
+
export declare function stackHealthGrid(input: z.infer<typeof StackHealthGridInput>): Promise<StackHealthGridResult>;
|
|
35
|
+
//# sourceMappingURL=stack-health-grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack-health-grid.d.ts","sourceRoot":"","sources":["../../src/tools/stack-health-grid.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAA0B,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAErE,eAAO,MAAM,oBAAoB;;;;;;;;;EAe/B,CAAC;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,SAAS,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAC;IACxE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACrC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAsED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,GAC1C,OAAO,CAAC,qBAAqB,CAAC,CAmChC"}
|