@massu/core 1.5.3 → 1.5.5
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/dist/cli.js +2624 -405
- package/dist/hooks/session-start.js +44 -2
- package/package.json +1 -1
- package/src/commands/init.ts +43 -1
- package/src/detect/adapters/file-sampler.ts +225 -0
- package/src/detect/adapters/index.ts +2 -0
- package/src/detect/codebase-introspector.ts +30 -10
- package/src/security/registry-pubkey.generated.ts +1 -1
package/dist/cli.js
CHANGED
|
@@ -1673,16 +1673,16 @@ import { parse as parseYaml2 } from "yaml";
|
|
|
1673
1673
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
1674
1674
|
if (!existsSync3(filePath)) return "skipped";
|
|
1675
1675
|
const content = readFileSync2(filePath, "utf-8");
|
|
1676
|
-
const
|
|
1676
|
+
const basename11 = (filePath.split("/").pop() ?? "").replace(".md", "");
|
|
1677
1677
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1678
|
-
let name2 =
|
|
1678
|
+
let name2 = basename11;
|
|
1679
1679
|
let description = "";
|
|
1680
1680
|
let type = "discovery";
|
|
1681
1681
|
let confidence;
|
|
1682
1682
|
if (frontmatterMatch) {
|
|
1683
1683
|
try {
|
|
1684
1684
|
const fm = parseYaml2(frontmatterMatch[1]);
|
|
1685
|
-
name2 = fm.name ??
|
|
1685
|
+
name2 = fm.name ?? basename11;
|
|
1686
1686
|
description = fm.description ?? "";
|
|
1687
1687
|
type = fm.type ?? "discovery";
|
|
1688
1688
|
confidence = fm.confidence != null ? Number(fm.confidence) : void 0;
|
|
@@ -7374,8 +7374,8 @@ var require_pattern = __commonJS({
|
|
|
7374
7374
|
}
|
|
7375
7375
|
exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
|
|
7376
7376
|
function isAffectDepthOfReadingPattern(pattern) {
|
|
7377
|
-
const
|
|
7378
|
-
return endsWithSlashGlobStar(pattern) || isStaticPattern(
|
|
7377
|
+
const basename11 = path.basename(pattern);
|
|
7378
|
+
return endsWithSlashGlobStar(pattern) || isStaticPattern(basename11);
|
|
7379
7379
|
}
|
|
7380
7380
|
exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
|
|
7381
7381
|
function expandPatternsWithBraceExpansion(patterns) {
|
|
@@ -7420,15 +7420,15 @@ var require_pattern = __commonJS({
|
|
|
7420
7420
|
exports.removeDuplicateSlashes = removeDuplicateSlashes;
|
|
7421
7421
|
function partitionAbsoluteAndRelative(patterns) {
|
|
7422
7422
|
const absolute = [];
|
|
7423
|
-
const
|
|
7423
|
+
const relative13 = [];
|
|
7424
7424
|
for (const pattern of patterns) {
|
|
7425
7425
|
if (isAbsolute3(pattern)) {
|
|
7426
7426
|
absolute.push(pattern);
|
|
7427
7427
|
} else {
|
|
7428
|
-
|
|
7428
|
+
relative13.push(pattern);
|
|
7429
7429
|
}
|
|
7430
7430
|
}
|
|
7431
|
-
return [absolute,
|
|
7431
|
+
return [absolute, relative13];
|
|
7432
7432
|
}
|
|
7433
7433
|
exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
|
|
7434
7434
|
function isAbsolute3(pattern) {
|
|
@@ -7849,11 +7849,11 @@ var require_out = __commonJS({
|
|
|
7849
7849
|
async.read(path, getSettings(optionsOrSettingsOrCallback), callback);
|
|
7850
7850
|
}
|
|
7851
7851
|
exports.stat = stat;
|
|
7852
|
-
function
|
|
7852
|
+
function statSync15(path, optionsOrSettings) {
|
|
7853
7853
|
const settings = getSettings(optionsOrSettings);
|
|
7854
7854
|
return sync.read(path, settings);
|
|
7855
7855
|
}
|
|
7856
|
-
exports.statSync =
|
|
7856
|
+
exports.statSync = statSync15;
|
|
7857
7857
|
function getSettings(settingsOrOptions = {}) {
|
|
7858
7858
|
if (settingsOrOptions instanceof settings_1.default) {
|
|
7859
7859
|
return settingsOrOptions;
|
|
@@ -10623,15 +10623,204 @@ var init_regex_fallback = __esm({
|
|
|
10623
10623
|
});
|
|
10624
10624
|
|
|
10625
10625
|
// src/detect/adapters/parse-guard.ts
|
|
10626
|
-
|
|
10626
|
+
function isParsableSource(source, sizeBytes) {
|
|
10627
|
+
const bytes = sizeBytes ?? Buffer.byteLength(source, "utf-8");
|
|
10628
|
+
if (bytes > MAX_AST_FILE_BYTES) {
|
|
10629
|
+
return {
|
|
10630
|
+
reason: "size-cap",
|
|
10631
|
+
detail: `${bytes} bytes > ${MAX_AST_FILE_BYTES} cap`
|
|
10632
|
+
};
|
|
10633
|
+
}
|
|
10634
|
+
let depth = 0;
|
|
10635
|
+
let maxDepth = 0;
|
|
10636
|
+
for (let i2 = 0; i2 < source.length; i2++) {
|
|
10637
|
+
const c2 = source.charCodeAt(i2);
|
|
10638
|
+
if (c2 === 0) {
|
|
10639
|
+
return { reason: "control-bytes", detail: "NUL byte at offset " + i2 };
|
|
10640
|
+
}
|
|
10641
|
+
if (c2 === 40 || c2 === 91 || c2 === 123) {
|
|
10642
|
+
depth++;
|
|
10643
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
10644
|
+
if (depth > MAX_AST_PARSE_DEPTH) {
|
|
10645
|
+
return {
|
|
10646
|
+
reason: "depth-cap",
|
|
10647
|
+
detail: `nesting depth exceeded ${MAX_AST_PARSE_DEPTH}`
|
|
10648
|
+
};
|
|
10649
|
+
}
|
|
10650
|
+
} else if (c2 === 41 || c2 === 93 || c2 === 125) {
|
|
10651
|
+
depth = depth > 0 ? depth - 1 : 0;
|
|
10652
|
+
}
|
|
10653
|
+
}
|
|
10654
|
+
return null;
|
|
10655
|
+
}
|
|
10656
|
+
var MAX_AST_FILE_BYTES, MAX_AST_PARSE_DEPTH;
|
|
10627
10657
|
var init_parse_guard = __esm({
|
|
10628
10658
|
"src/detect/adapters/parse-guard.ts"() {
|
|
10629
10659
|
"use strict";
|
|
10630
10660
|
MAX_AST_FILE_BYTES = 1 * 1024 * 1024;
|
|
10661
|
+
MAX_AST_PARSE_DEPTH = 5e3;
|
|
10631
10662
|
}
|
|
10632
10663
|
});
|
|
10633
10664
|
|
|
10634
10665
|
// src/detect/adapters/runner.ts
|
|
10666
|
+
import { basename as basename3, relative as relative4 } from "path";
|
|
10667
|
+
import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
10668
|
+
import { join as join7 } from "path";
|
|
10669
|
+
async function runAdapters(adapters, rootDir, signals, options = {}) {
|
|
10670
|
+
const out2 = {
|
|
10671
|
+
byAdapter: {},
|
|
10672
|
+
skipped: [],
|
|
10673
|
+
errored: []
|
|
10674
|
+
};
|
|
10675
|
+
for (const adapter of adapters) {
|
|
10676
|
+
if (out2.byAdapter[adapter.id] || out2.skipped.includes(adapter.id)) {
|
|
10677
|
+
continue;
|
|
10678
|
+
}
|
|
10679
|
+
let matches;
|
|
10680
|
+
try {
|
|
10681
|
+
matches = adapter.matches(signals);
|
|
10682
|
+
} catch (e2) {
|
|
10683
|
+
out2.errored.push({
|
|
10684
|
+
adapterId: adapter.id,
|
|
10685
|
+
error: `matches() threw: ${e2 instanceof Error ? e2.message : String(e2)}`
|
|
10686
|
+
});
|
|
10687
|
+
continue;
|
|
10688
|
+
}
|
|
10689
|
+
if (!matches) {
|
|
10690
|
+
out2.skipped.push(adapter.id);
|
|
10691
|
+
continue;
|
|
10692
|
+
}
|
|
10693
|
+
let files;
|
|
10694
|
+
try {
|
|
10695
|
+
files = options.sampleFiles ? await options.sampleFiles(adapter, rootDir) : [];
|
|
10696
|
+
} catch (e2) {
|
|
10697
|
+
out2.errored.push({
|
|
10698
|
+
adapterId: adapter.id,
|
|
10699
|
+
error: `sampleFiles threw: ${e2 instanceof Error ? e2.message : String(e2)}`
|
|
10700
|
+
});
|
|
10701
|
+
continue;
|
|
10702
|
+
}
|
|
10703
|
+
const safeFiles = [];
|
|
10704
|
+
for (const f2 of files) {
|
|
10705
|
+
const skip = isParsableSource(f2.content, f2.size);
|
|
10706
|
+
if (skip) {
|
|
10707
|
+
process.stderr.write(
|
|
10708
|
+
`[massu/ast] WARN: skipping ${f2.path} for adapter ${adapter.id}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES} bytes. (Phase 3.5 mitigation)
|
|
10709
|
+
`
|
|
10710
|
+
);
|
|
10711
|
+
continue;
|
|
10712
|
+
}
|
|
10713
|
+
safeFiles.push(f2);
|
|
10714
|
+
}
|
|
10715
|
+
files = safeFiles;
|
|
10716
|
+
let result;
|
|
10717
|
+
try {
|
|
10718
|
+
result = await adapter.introspect(files, rootDir);
|
|
10719
|
+
} catch (e2) {
|
|
10720
|
+
out2.errored.push({
|
|
10721
|
+
adapterId: adapter.id,
|
|
10722
|
+
error: `introspect() threw: ${e2 instanceof Error ? e2.message : String(e2)}`
|
|
10723
|
+
});
|
|
10724
|
+
continue;
|
|
10725
|
+
}
|
|
10726
|
+
if (result.confidence === "none") {
|
|
10727
|
+
out2.byAdapter[adapter.id] = {
|
|
10728
|
+
conventions: {},
|
|
10729
|
+
_provenance: {},
|
|
10730
|
+
confidence: "none"
|
|
10731
|
+
};
|
|
10732
|
+
continue;
|
|
10733
|
+
}
|
|
10734
|
+
const conventions = {};
|
|
10735
|
+
const provenanceMap = {};
|
|
10736
|
+
for (const [field, value] of Object.entries(result.conventions)) {
|
|
10737
|
+
if (value === null || value === void 0) continue;
|
|
10738
|
+
if (field in conventions) continue;
|
|
10739
|
+
conventions[field] = value;
|
|
10740
|
+
}
|
|
10741
|
+
for (const p19 of result.provenance) {
|
|
10742
|
+
if (p19.field in provenanceMap) continue;
|
|
10743
|
+
provenanceMap[p19.field] = formatProvenance(p19, rootDir);
|
|
10744
|
+
}
|
|
10745
|
+
const resolved = {
|
|
10746
|
+
conventions,
|
|
10747
|
+
_provenance: provenanceMap,
|
|
10748
|
+
confidence: result.confidence
|
|
10749
|
+
};
|
|
10750
|
+
out2.byAdapter[adapter.id] = resolved;
|
|
10751
|
+
}
|
|
10752
|
+
return out2;
|
|
10753
|
+
}
|
|
10754
|
+
function formatProvenance(p19, rootDir) {
|
|
10755
|
+
const rel = p19.sourceFile.startsWith(rootDir + "/") ? relative4(rootDir, p19.sourceFile) : basename3(p19.sourceFile);
|
|
10756
|
+
return `${rel}:${p19.line} :: ${p19.query}`;
|
|
10757
|
+
}
|
|
10758
|
+
function buildDetectionSignals(rootDir) {
|
|
10759
|
+
const presentDirs = /* @__PURE__ */ new Set();
|
|
10760
|
+
const presentFiles = /* @__PURE__ */ new Set();
|
|
10761
|
+
try {
|
|
10762
|
+
for (const entry of readdirSync7(rootDir)) {
|
|
10763
|
+
if (entry.startsWith(".")) continue;
|
|
10764
|
+
try {
|
|
10765
|
+
const st = statSync5(join7(rootDir, entry));
|
|
10766
|
+
if (st.isDirectory()) presentDirs.add(entry);
|
|
10767
|
+
else if (st.isFile()) presentFiles.add(entry);
|
|
10768
|
+
} catch {
|
|
10769
|
+
}
|
|
10770
|
+
}
|
|
10771
|
+
} catch {
|
|
10772
|
+
}
|
|
10773
|
+
return {
|
|
10774
|
+
packageJson: tryReadJson(join7(rootDir, "package.json")),
|
|
10775
|
+
pyprojectToml: tryReadToml(join7(rootDir, "pyproject.toml")),
|
|
10776
|
+
gemfile: tryReadString(join7(rootDir, "Gemfile")),
|
|
10777
|
+
cargoToml: tryReadToml(join7(rootDir, "Cargo.toml")),
|
|
10778
|
+
goMod: tryReadString(join7(rootDir, "go.mod")),
|
|
10779
|
+
mixExs: tryReadString(join7(rootDir, "mix.exs")),
|
|
10780
|
+
csproj: tryReadFirstCsproj(rootDir, presentFiles),
|
|
10781
|
+
pomXml: tryReadString(join7(rootDir, "pom.xml")),
|
|
10782
|
+
gradleBuild: tryReadGradleBuild(rootDir, presentFiles),
|
|
10783
|
+
presentDirs,
|
|
10784
|
+
presentFiles
|
|
10785
|
+
};
|
|
10786
|
+
}
|
|
10787
|
+
function tryReadFirstCsproj(rootDir, presentFiles) {
|
|
10788
|
+
const csprojNames = [...presentFiles].filter((f2) => f2.endsWith(".csproj")).sort();
|
|
10789
|
+
if (csprojNames.length === 0) return void 0;
|
|
10790
|
+
return tryReadString(join7(rootDir, csprojNames[0]));
|
|
10791
|
+
}
|
|
10792
|
+
function tryReadGradleBuild(rootDir, presentFiles) {
|
|
10793
|
+
if (presentFiles.has("build.gradle.kts")) {
|
|
10794
|
+
return tryReadString(join7(rootDir, "build.gradle.kts"));
|
|
10795
|
+
}
|
|
10796
|
+
if (presentFiles.has("build.gradle")) {
|
|
10797
|
+
return tryReadString(join7(rootDir, "build.gradle"));
|
|
10798
|
+
}
|
|
10799
|
+
return void 0;
|
|
10800
|
+
}
|
|
10801
|
+
function tryReadString(path) {
|
|
10802
|
+
if (!existsSync9(path)) return void 0;
|
|
10803
|
+
try {
|
|
10804
|
+
return readFileSync7(path, "utf-8");
|
|
10805
|
+
} catch {
|
|
10806
|
+
return void 0;
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10809
|
+
function tryReadJson(path) {
|
|
10810
|
+
const txt = tryReadString(path);
|
|
10811
|
+
if (!txt) return void 0;
|
|
10812
|
+
try {
|
|
10813
|
+
const parsed = JSON.parse(txt);
|
|
10814
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
10815
|
+
} catch {
|
|
10816
|
+
return void 0;
|
|
10817
|
+
}
|
|
10818
|
+
}
|
|
10819
|
+
function tryReadToml(path) {
|
|
10820
|
+
const txt = tryReadString(path);
|
|
10821
|
+
if (!txt) return void 0;
|
|
10822
|
+
return { __raw: txt };
|
|
10823
|
+
}
|
|
10635
10824
|
var init_runner = __esm({
|
|
10636
10825
|
"src/detect/adapters/runner.ts"() {
|
|
10637
10826
|
"use strict";
|
|
@@ -14588,22 +14777,408 @@ ${JSON.stringify(symbolNames, null, 2)}`);
|
|
|
14588
14777
|
});
|
|
14589
14778
|
|
|
14590
14779
|
// src/detect/adapters/query-helpers.ts
|
|
14780
|
+
function compileQuery(language, source, queryName) {
|
|
14781
|
+
let perLang = queryCache.get(language);
|
|
14782
|
+
if (!perLang) {
|
|
14783
|
+
perLang = /* @__PURE__ */ new Map();
|
|
14784
|
+
queryCache.set(language, perLang);
|
|
14785
|
+
}
|
|
14786
|
+
const cached = perLang.get(source);
|
|
14787
|
+
if (cached) return cached;
|
|
14788
|
+
let q2;
|
|
14789
|
+
try {
|
|
14790
|
+
q2 = new Query(language, source);
|
|
14791
|
+
} catch (e2) {
|
|
14792
|
+
throw new InvalidQueryError(queryName, source, e2);
|
|
14793
|
+
}
|
|
14794
|
+
perLang.set(source, q2);
|
|
14795
|
+
return q2;
|
|
14796
|
+
}
|
|
14797
|
+
function runQuery(parser, source, queryText, queryName, filePath) {
|
|
14798
|
+
const language = parser.language;
|
|
14799
|
+
if (!language) {
|
|
14800
|
+
throw new InvalidQueryError(
|
|
14801
|
+
queryName,
|
|
14802
|
+
queryText,
|
|
14803
|
+
new Error("Parser has no language assigned")
|
|
14804
|
+
);
|
|
14805
|
+
}
|
|
14806
|
+
const query = compileQuery(language, queryText, queryName);
|
|
14807
|
+
const tree = parser.parse(source);
|
|
14808
|
+
if (!tree) return [];
|
|
14809
|
+
let matches;
|
|
14810
|
+
try {
|
|
14811
|
+
matches = query.matches(tree.rootNode);
|
|
14812
|
+
} catch (e2) {
|
|
14813
|
+
throw new InvalidQueryError(queryName, queryText, e2);
|
|
14814
|
+
}
|
|
14815
|
+
const out2 = [];
|
|
14816
|
+
for (const match of matches) {
|
|
14817
|
+
if (!match.captures || match.captures.length === 0) continue;
|
|
14818
|
+
const captures = {};
|
|
14819
|
+
let earliestLine = Number.POSITIVE_INFINITY;
|
|
14820
|
+
for (const cap of match.captures) {
|
|
14821
|
+
const node = cap.node;
|
|
14822
|
+
captures[cap.name] = node.text;
|
|
14823
|
+
if (node.startPosition.row + 1 < earliestLine) {
|
|
14824
|
+
earliestLine = node.startPosition.row + 1;
|
|
14825
|
+
}
|
|
14826
|
+
}
|
|
14827
|
+
out2.push({
|
|
14828
|
+
captures,
|
|
14829
|
+
file: filePath,
|
|
14830
|
+
line: Number.isFinite(earliestLine) ? earliestLine : 1,
|
|
14831
|
+
queryName
|
|
14832
|
+
});
|
|
14833
|
+
}
|
|
14834
|
+
try {
|
|
14835
|
+
tree.delete();
|
|
14836
|
+
} catch {
|
|
14837
|
+
}
|
|
14838
|
+
return out2;
|
|
14839
|
+
}
|
|
14840
|
+
var InvalidQueryError, queryCache;
|
|
14591
14841
|
var init_query_helpers = __esm({
|
|
14592
14842
|
"src/detect/adapters/query-helpers.ts"() {
|
|
14593
14843
|
"use strict";
|
|
14594
14844
|
init_tree_sitter();
|
|
14845
|
+
InvalidQueryError = class extends Error {
|
|
14846
|
+
queryName;
|
|
14847
|
+
querySource;
|
|
14848
|
+
cause;
|
|
14849
|
+
constructor(queryName, querySource, cause) {
|
|
14850
|
+
const causeMsg = cause instanceof Error ? cause.message : String(cause);
|
|
14851
|
+
super(
|
|
14852
|
+
`[query-helpers] Invalid Tree-sitter query "${queryName}": ${causeMsg}
|
|
14853
|
+
Query source:
|
|
14854
|
+
${querySource}`
|
|
14855
|
+
);
|
|
14856
|
+
this.name = "InvalidQueryError";
|
|
14857
|
+
this.queryName = queryName;
|
|
14858
|
+
this.querySource = querySource;
|
|
14859
|
+
this.cause = cause;
|
|
14860
|
+
}
|
|
14861
|
+
};
|
|
14862
|
+
queryCache = /* @__PURE__ */ new WeakMap();
|
|
14595
14863
|
}
|
|
14596
14864
|
});
|
|
14597
14865
|
|
|
14598
14866
|
// src/detect/adapters/tree-sitter-loader.ts
|
|
14867
|
+
import { createHash as createHash2 } from "crypto";
|
|
14868
|
+
import {
|
|
14869
|
+
mkdirSync as mkdirSync3,
|
|
14870
|
+
readdirSync as readdirSync8,
|
|
14871
|
+
readFileSync as readFileSync8,
|
|
14872
|
+
writeFileSync,
|
|
14873
|
+
renameSync as renameSync2,
|
|
14874
|
+
unlinkSync,
|
|
14875
|
+
lstatSync as lstatSync3,
|
|
14876
|
+
chmodSync,
|
|
14877
|
+
utimesSync
|
|
14878
|
+
} from "fs";
|
|
14879
|
+
import { homedir as homedir2 } from "os";
|
|
14880
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
14881
|
+
function getCacheDir() {
|
|
14882
|
+
return process.env.MASSU_WASM_CACHE_DIR ?? join8(homedir2(), ".massu", "wasm-cache");
|
|
14883
|
+
}
|
|
14884
|
+
function getCachedPath(language, sha) {
|
|
14885
|
+
return join8(getCacheDir(), `${language}-${sha}.wasm`);
|
|
14886
|
+
}
|
|
14887
|
+
function getCacheRetainCount() {
|
|
14888
|
+
const env = process.env.MASSU_WASM_CACHE_RETAIN;
|
|
14889
|
+
if (env) {
|
|
14890
|
+
const n = Number(env);
|
|
14891
|
+
if (Number.isFinite(n) && n >= 1 && n <= 1024) return Math.floor(n);
|
|
14892
|
+
}
|
|
14893
|
+
return DEFAULT_CACHE_RETAIN_COUNT;
|
|
14894
|
+
}
|
|
14895
|
+
function touchCacheFile(path) {
|
|
14896
|
+
try {
|
|
14897
|
+
const now = /* @__PURE__ */ new Date();
|
|
14898
|
+
utimesSync(path, now, now);
|
|
14899
|
+
} catch {
|
|
14900
|
+
}
|
|
14901
|
+
}
|
|
14902
|
+
function evictBeyondRetainCount(retain = getCacheRetainCount()) {
|
|
14903
|
+
const dir = getCacheDir();
|
|
14904
|
+
let entries;
|
|
14905
|
+
try {
|
|
14906
|
+
entries = readdirSync8(dir);
|
|
14907
|
+
} catch {
|
|
14908
|
+
return;
|
|
14909
|
+
}
|
|
14910
|
+
const candidates = [];
|
|
14911
|
+
for (const name2 of entries) {
|
|
14912
|
+
if (!name2.endsWith(".wasm")) continue;
|
|
14913
|
+
const path = join8(dir, name2);
|
|
14914
|
+
let stat;
|
|
14915
|
+
try {
|
|
14916
|
+
stat = lstatSync3(path);
|
|
14917
|
+
} catch {
|
|
14918
|
+
continue;
|
|
14919
|
+
}
|
|
14920
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
14921
|
+
console.error(
|
|
14922
|
+
`[tree-sitter-loader] cache eviction skipped non-regular file: ${path} (possible symlink attack \u2014 see Phase 3.5 finding F-008).`
|
|
14923
|
+
);
|
|
14924
|
+
continue;
|
|
14925
|
+
}
|
|
14926
|
+
candidates.push({ path, mtimeMs: stat.mtimeMs });
|
|
14927
|
+
}
|
|
14928
|
+
if (candidates.length <= retain) return;
|
|
14929
|
+
candidates.sort((a2, b2) => b2.mtimeMs - a2.mtimeMs);
|
|
14930
|
+
for (const victim of candidates.slice(retain)) {
|
|
14931
|
+
try {
|
|
14932
|
+
unlinkSync(victim.path);
|
|
14933
|
+
} catch {
|
|
14934
|
+
}
|
|
14935
|
+
}
|
|
14936
|
+
}
|
|
14937
|
+
function sha256(bytes) {
|
|
14938
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
14939
|
+
}
|
|
14940
|
+
async function ensureParserInitialized() {
|
|
14941
|
+
if (parserInitPromise) return parserInitPromise;
|
|
14942
|
+
parserInitPromise = Parser.init();
|
|
14943
|
+
return parserInitPromise;
|
|
14944
|
+
}
|
|
14945
|
+
async function loadGrammar(language, options = {}) {
|
|
14946
|
+
await ensureParserInitialized();
|
|
14947
|
+
const cached = loadedGrammars.get(language);
|
|
14948
|
+
if (cached) return cached;
|
|
14949
|
+
const manifest = options.manifestOverride?.[language] ?? GRAMMAR_MANIFEST[language];
|
|
14950
|
+
if (!manifest) {
|
|
14951
|
+
throw new GrammarUnavailableError(
|
|
14952
|
+
language,
|
|
14953
|
+
new Error(`No manifest entry for language "${language}". v1 supports: ${Object.keys(GRAMMAR_MANIFEST).join(", ")}.`)
|
|
14954
|
+
);
|
|
14955
|
+
}
|
|
14956
|
+
const cachePath = getCachedPath(language, manifest.sha256);
|
|
14957
|
+
let cacheLstat;
|
|
14958
|
+
try {
|
|
14959
|
+
cacheLstat = lstatSync3(cachePath);
|
|
14960
|
+
} catch {
|
|
14961
|
+
cacheLstat = null;
|
|
14962
|
+
}
|
|
14963
|
+
if (cacheLstat) {
|
|
14964
|
+
if (cacheLstat.isSymbolicLink() || !cacheLstat.isFile()) {
|
|
14965
|
+
throw new GrammarCacheSymlinkError(cachePath);
|
|
14966
|
+
}
|
|
14967
|
+
let bytes;
|
|
14968
|
+
try {
|
|
14969
|
+
bytes = readFileSync8(cachePath);
|
|
14970
|
+
} catch (e2) {
|
|
14971
|
+
bytes = new Uint8Array(0);
|
|
14972
|
+
}
|
|
14973
|
+
if (bytes.byteLength > 0) {
|
|
14974
|
+
const actualSha = sha256(bytes);
|
|
14975
|
+
if (actualSha !== manifest.sha256) {
|
|
14976
|
+
throw new GrammarSHAMismatchError(language, manifest.sha256, actualSha);
|
|
14977
|
+
}
|
|
14978
|
+
const lang2 = await Language.load(bytes);
|
|
14979
|
+
loadedGrammars.set(language, lang2);
|
|
14980
|
+
touchCacheFile(cachePath);
|
|
14981
|
+
return lang2;
|
|
14982
|
+
}
|
|
14983
|
+
}
|
|
14984
|
+
if (!/^https:\/\//i.test(manifest.url)) {
|
|
14985
|
+
throw new GrammarUrlNotHttpsError(manifest.url);
|
|
14986
|
+
}
|
|
14987
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
14988
|
+
if (!fetchImpl) {
|
|
14989
|
+
throw new GrammarUnavailableError(
|
|
14990
|
+
language,
|
|
14991
|
+
new Error("No fetch implementation available (Node < 18?)")
|
|
14992
|
+
);
|
|
14993
|
+
}
|
|
14994
|
+
let body2;
|
|
14995
|
+
try {
|
|
14996
|
+
const res = await fetchImpl(manifest.url);
|
|
14997
|
+
if (!res.ok) {
|
|
14998
|
+
throw new Error(`HTTP ${res.status ?? "unknown"} from ${manifest.url}`);
|
|
14999
|
+
}
|
|
15000
|
+
body2 = new Uint8Array(await res.arrayBuffer());
|
|
15001
|
+
} catch (e2) {
|
|
15002
|
+
throw new GrammarUnavailableError(language, e2);
|
|
15003
|
+
}
|
|
15004
|
+
const downloadedSha = sha256(body2);
|
|
15005
|
+
if (downloadedSha !== manifest.sha256) {
|
|
15006
|
+
throw new GrammarSHAMismatchError(language, manifest.sha256, downloadedSha);
|
|
15007
|
+
}
|
|
15008
|
+
try {
|
|
15009
|
+
mkdirSync3(dirname4(cachePath), { recursive: true, mode: 448 });
|
|
15010
|
+
try {
|
|
15011
|
+
chmodSync(dirname4(cachePath), 448);
|
|
15012
|
+
} catch {
|
|
15013
|
+
}
|
|
15014
|
+
const tmpPath = `${cachePath}.tmp.${process.pid}`;
|
|
15015
|
+
writeFileSync(tmpPath, body2, { mode: 384 });
|
|
15016
|
+
try {
|
|
15017
|
+
chmodSync(tmpPath, 384);
|
|
15018
|
+
} catch {
|
|
15019
|
+
}
|
|
15020
|
+
try {
|
|
15021
|
+
renameSync2(tmpPath, cachePath);
|
|
15022
|
+
try {
|
|
15023
|
+
chmodSync(cachePath, 384);
|
|
15024
|
+
} catch {
|
|
15025
|
+
}
|
|
15026
|
+
} catch (e2) {
|
|
15027
|
+
try {
|
|
15028
|
+
unlinkSync(tmpPath);
|
|
15029
|
+
} catch {
|
|
15030
|
+
}
|
|
15031
|
+
throw e2;
|
|
15032
|
+
}
|
|
15033
|
+
evictBeyondRetainCount();
|
|
15034
|
+
} catch (e2) {
|
|
15035
|
+
console.error(
|
|
15036
|
+
`[tree-sitter-loader] cache write failed for ${language}: ${e2 instanceof Error ? e2.message : String(e2)} \u2014 loading directly from memory.`
|
|
15037
|
+
);
|
|
15038
|
+
}
|
|
15039
|
+
const lang = await Language.load(body2);
|
|
15040
|
+
loadedGrammars.set(language, lang);
|
|
15041
|
+
return lang;
|
|
15042
|
+
}
|
|
15043
|
+
var GrammarSHAMismatchError, GrammarUnavailableError, GrammarCacheSymlinkError, GrammarUrlNotHttpsError, GRAMMAR_MANIFEST, DEFAULT_CACHE_RETAIN_COUNT, parserInitPromise, loadedGrammars;
|
|
14599
15044
|
var init_tree_sitter_loader = __esm({
|
|
14600
15045
|
"src/detect/adapters/tree-sitter-loader.ts"() {
|
|
14601
15046
|
"use strict";
|
|
14602
15047
|
init_tree_sitter();
|
|
15048
|
+
GrammarSHAMismatchError = class extends Error {
|
|
15049
|
+
language;
|
|
15050
|
+
expected;
|
|
15051
|
+
actual;
|
|
15052
|
+
constructor(language, expected, actual) {
|
|
15053
|
+
super(
|
|
15054
|
+
`[tree-sitter-loader] SHA-256 mismatch for grammar "${language}". Expected ${expected}, got ${actual}. REFUSING to load \u2014 see Phase 3.5 audit attack vector #3.`
|
|
15055
|
+
);
|
|
15056
|
+
this.name = "GrammarSHAMismatchError";
|
|
15057
|
+
this.language = language;
|
|
15058
|
+
this.expected = expected;
|
|
15059
|
+
this.actual = actual;
|
|
15060
|
+
}
|
|
15061
|
+
};
|
|
15062
|
+
GrammarUnavailableError = class extends Error {
|
|
15063
|
+
language;
|
|
15064
|
+
cause;
|
|
15065
|
+
constructor(language, cause) {
|
|
15066
|
+
const causeMsg = cause instanceof Error ? cause.message : cause ? String(cause) : "no cached grammar and download failed";
|
|
15067
|
+
super(
|
|
15068
|
+
`[tree-sitter-loader] Grammar for "${language}" is unavailable: ${causeMsg}. Falling back to regex introspection for files in ${language}.`
|
|
15069
|
+
);
|
|
15070
|
+
this.name = "GrammarUnavailableError";
|
|
15071
|
+
this.language = language;
|
|
15072
|
+
this.cause = cause;
|
|
15073
|
+
}
|
|
15074
|
+
};
|
|
15075
|
+
GrammarCacheSymlinkError = class extends Error {
|
|
15076
|
+
cachePath;
|
|
15077
|
+
constructor(cachePath) {
|
|
15078
|
+
super(
|
|
15079
|
+
`[tree-sitter-loader] Refusing to load grammar \u2014 cache path "${cachePath}" is a symlink or non-regular file. (Phase 3.5 finding #3 \u2014 symlink attack vector.)`
|
|
15080
|
+
);
|
|
15081
|
+
this.name = "GrammarCacheSymlinkError";
|
|
15082
|
+
this.cachePath = cachePath;
|
|
15083
|
+
}
|
|
15084
|
+
};
|
|
15085
|
+
GrammarUrlNotHttpsError = class extends Error {
|
|
15086
|
+
url;
|
|
15087
|
+
constructor(url) {
|
|
15088
|
+
super(
|
|
15089
|
+
`[tree-sitter-loader] Refusing to download grammar from non-HTTPS URL: ${url}. Only https:// URLs are accepted. (Phase 3.5 finding #3.)`
|
|
15090
|
+
);
|
|
15091
|
+
this.name = "GrammarUrlNotHttpsError";
|
|
15092
|
+
this.url = url;
|
|
15093
|
+
}
|
|
15094
|
+
};
|
|
15095
|
+
GRAMMAR_MANIFEST = {
|
|
15096
|
+
python: {
|
|
15097
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-python.wasm",
|
|
15098
|
+
sha256: "9056d0fb0c337810d019fae350e8167786119da98f0f282aceae7ab89ee8253b",
|
|
15099
|
+
version: "0.1.13"
|
|
15100
|
+
},
|
|
15101
|
+
typescript: {
|
|
15102
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-typescript.wasm",
|
|
15103
|
+
sha256: "8515404dceed38e1ed86aa34b09fcf3379fff1b4ff9dd3967bcd6d1eb5ac3d8f",
|
|
15104
|
+
version: "0.1.13"
|
|
15105
|
+
},
|
|
15106
|
+
javascript: {
|
|
15107
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-javascript.wasm",
|
|
15108
|
+
sha256: "63812b9e275d26851264734868d27a1656bd44a2ef6eb3e85e6b03728c595ab5",
|
|
15109
|
+
version: "0.1.13"
|
|
15110
|
+
},
|
|
15111
|
+
swift: {
|
|
15112
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-swift.wasm",
|
|
15113
|
+
sha256: "41c4fdb2249a3aa6d87eed0d383081ff09725c2248b4977043a43825980ffcc7",
|
|
15114
|
+
version: "0.1.13"
|
|
15115
|
+
},
|
|
15116
|
+
// ----------------------------------------------------------------
|
|
15117
|
+
// Plan 3c Phase 7 expansion (2026-05-07):
|
|
15118
|
+
//
|
|
15119
|
+
// Six additional grammars to support the registry-verified framework
|
|
15120
|
+
// adapters (go-chi, rails, aspnet, spring, ktor, phoenix) plus the
|
|
15121
|
+
// bundled adapters in the same language families (gin/echo/fiber,
|
|
15122
|
+
// sinatra, etc.). All entries use the SAME pinned tree-sitter-wasms
|
|
15123
|
+
// version (0.1.13) as the v1 four to keep the dependency surface
|
|
15124
|
+
// single-source.
|
|
15125
|
+
//
|
|
15126
|
+
// SHA-256s computed 2026-05-07 via:
|
|
15127
|
+
// curl -fsSL <url> | shasum -a 256
|
|
15128
|
+
//
|
|
15129
|
+
// The unpkg filename for C# uses an underscore (`c_sharp`) while the
|
|
15130
|
+
// TreeSitterLanguage identifier uses no separator (`csharp`); the map
|
|
15131
|
+
// key is the type identifier, the URL is the storage path — they do
|
|
15132
|
+
// NOT need to match, the same as how `python` maps to `tree-sitter-
|
|
15133
|
+
// python.wasm`. This is intentional and validated by the manifest
|
|
15134
|
+
// shape test in tree-sitter-loader-manifest.test.ts.
|
|
15135
|
+
// ----------------------------------------------------------------
|
|
15136
|
+
go: {
|
|
15137
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-go.wasm",
|
|
15138
|
+
sha256: "9963ca89b616eaf04b08a43bc1fb0f07b85395bec313330851f1f1ead2f755b6",
|
|
15139
|
+
version: "0.1.13"
|
|
15140
|
+
},
|
|
15141
|
+
ruby: {
|
|
15142
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-ruby.wasm",
|
|
15143
|
+
sha256: "93a5022855314cdb45458c7bb026a24a0ebc3a5ff6439e542e881f14dfa13a39",
|
|
15144
|
+
version: "0.1.13"
|
|
15145
|
+
},
|
|
15146
|
+
csharp: {
|
|
15147
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-c_sharp.wasm",
|
|
15148
|
+
sha256: "6266a7e32d68a3459104d994dc848df15d5672b0ea8e86d327274b694f8e6991",
|
|
15149
|
+
version: "0.1.13"
|
|
15150
|
+
},
|
|
15151
|
+
java: {
|
|
15152
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-java.wasm",
|
|
15153
|
+
sha256: "637aac4415fb39a211a4f4292d63c66b5ce9c32fa2cd35464af4f681d91b9a1f",
|
|
15154
|
+
version: "0.1.13"
|
|
15155
|
+
},
|
|
15156
|
+
kotlin: {
|
|
15157
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-kotlin.wasm",
|
|
15158
|
+
sha256: "b5cb00c8d06ed0f10f1dbe497205b437809d7e87db1f638721a8cfb30e044449",
|
|
15159
|
+
version: "0.1.13"
|
|
15160
|
+
},
|
|
15161
|
+
elixir: {
|
|
15162
|
+
url: "https://unpkg.com/tree-sitter-wasms@0.1.13/out/tree-sitter-elixir.wasm",
|
|
15163
|
+
sha256: "82e91b9759ddca30d8978ebbfa8e347b4451b64c931f9ae62112e6db9b8fac20",
|
|
15164
|
+
version: "0.1.13"
|
|
15165
|
+
}
|
|
15166
|
+
};
|
|
15167
|
+
DEFAULT_CACHE_RETAIN_COUNT = 16;
|
|
15168
|
+
parserInitPromise = null;
|
|
15169
|
+
loadedGrammars = /* @__PURE__ */ new Map();
|
|
14603
15170
|
}
|
|
14604
15171
|
});
|
|
14605
15172
|
|
|
14606
15173
|
// src/detect/adapters/python-fastapi.ts
|
|
15174
|
+
function extractPrefixBase2(prefix3) {
|
|
15175
|
+
if (!prefix3.startsWith("/")) return null;
|
|
15176
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
15177
|
+
const firstSeg = stripped.split("/")[0];
|
|
15178
|
+
if (!firstSeg) return null;
|
|
15179
|
+
return "/" + firstSeg;
|
|
15180
|
+
}
|
|
15181
|
+
var AUTH_DEP_QUERY, API_PREFIX_QUERY, PYTEST_ASYNCIO_QUERY, pythonFastApiAdapter;
|
|
14607
15182
|
var init_python_fastapi = __esm({
|
|
14608
15183
|
"src/detect/adapters/python-fastapi.ts"() {
|
|
14609
15184
|
"use strict";
|
|
@@ -14611,43 +15186,1621 @@ var init_python_fastapi = __esm({
|
|
|
14611
15186
|
init_query_helpers();
|
|
14612
15187
|
init_tree_sitter_loader();
|
|
14613
15188
|
init_parse_guard();
|
|
15189
|
+
AUTH_DEP_QUERY = `
|
|
15190
|
+
(call
|
|
15191
|
+
function: (identifier) @_callee (#eq? @_callee "Depends")
|
|
15192
|
+
arguments: (argument_list
|
|
15193
|
+
(identifier) @auth_dep))
|
|
15194
|
+
`;
|
|
15195
|
+
API_PREFIX_QUERY = `
|
|
15196
|
+
(call
|
|
15197
|
+
function: (identifier) @_callee (#eq? @_callee "APIRouter")
|
|
15198
|
+
arguments: (argument_list
|
|
15199
|
+
(keyword_argument
|
|
15200
|
+
name: (identifier) @_kw (#eq? @_kw "prefix")
|
|
15201
|
+
value: (string) @prefix_value)))
|
|
15202
|
+
`;
|
|
15203
|
+
PYTEST_ASYNCIO_QUERY = `
|
|
15204
|
+
(decorator
|
|
15205
|
+
(attribute
|
|
15206
|
+
object: (attribute
|
|
15207
|
+
object: (identifier) @_pkg (#eq? @_pkg "pytest")
|
|
15208
|
+
attribute: (identifier) @_mark (#eq? @_mark "mark"))
|
|
15209
|
+
attribute: (identifier) @_marker (#eq? @_marker "asyncio"))) @decorator
|
|
15210
|
+
`;
|
|
15211
|
+
pythonFastApiAdapter = {
|
|
15212
|
+
id: "python-fastapi",
|
|
15213
|
+
languages: ["python"],
|
|
15214
|
+
matches(signals) {
|
|
15215
|
+
const pyToml = signals.pyprojectToml;
|
|
15216
|
+
if (pyToml?.__raw && /\bfastapi\b/i.test(pyToml.__raw)) return true;
|
|
15217
|
+
if (signals.presentDirs.has("routers")) return true;
|
|
15218
|
+
if (signals.presentDirs.has("app") && signals.presentFiles.has("main.py")) return true;
|
|
15219
|
+
return false;
|
|
15220
|
+
},
|
|
15221
|
+
async introspect(files, _rootDir) {
|
|
15222
|
+
if (files.length === 0) {
|
|
15223
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15224
|
+
}
|
|
15225
|
+
let language;
|
|
15226
|
+
try {
|
|
15227
|
+
language = await loadGrammar("python");
|
|
15228
|
+
} catch (e2) {
|
|
15229
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15230
|
+
}
|
|
15231
|
+
const parser = new Parser();
|
|
15232
|
+
parser.setLanguage(language);
|
|
15233
|
+
const authDeps = /* @__PURE__ */ new Map();
|
|
15234
|
+
const prefixBases = /* @__PURE__ */ new Map();
|
|
15235
|
+
const testAsyncPatterns = /* @__PURE__ */ new Map();
|
|
15236
|
+
try {
|
|
15237
|
+
for (const file of files) {
|
|
15238
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15239
|
+
if (skip) {
|
|
15240
|
+
process.stderr.write(
|
|
15241
|
+
`[massu/ast] WARN: python-fastapi skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15242
|
+
`
|
|
15243
|
+
);
|
|
15244
|
+
continue;
|
|
15245
|
+
}
|
|
15246
|
+
try {
|
|
15247
|
+
for (const hit of runQuery(parser, file.content, AUTH_DEP_QUERY, "fastapi-auth-dep", file.path)) {
|
|
15248
|
+
const name2 = hit.captures.auth_dep;
|
|
15249
|
+
if (name2 && !authDeps.has(name2)) {
|
|
15250
|
+
authDeps.set(name2, { line: hit.line, file: file.path });
|
|
15251
|
+
}
|
|
15252
|
+
}
|
|
15253
|
+
for (const hit of runQuery(parser, file.content, API_PREFIX_QUERY, "fastapi-api-prefix", file.path)) {
|
|
15254
|
+
const raw = hit.captures.prefix_value;
|
|
15255
|
+
if (!raw) continue;
|
|
15256
|
+
const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
|
|
15257
|
+
const base = extractPrefixBase2(literal);
|
|
15258
|
+
if (base && !prefixBases.has(base)) {
|
|
15259
|
+
prefixBases.set(base, { line: hit.line, file: file.path });
|
|
15260
|
+
}
|
|
15261
|
+
}
|
|
15262
|
+
for (const hit of runQuery(parser, file.content, PYTEST_ASYNCIO_QUERY, "fastapi-pytest-asyncio", file.path)) {
|
|
15263
|
+
const pat = "@pytest.mark.asyncio";
|
|
15264
|
+
if (!testAsyncPatterns.has(pat)) {
|
|
15265
|
+
testAsyncPatterns.set(pat, { line: hit.line, file: file.path });
|
|
15266
|
+
}
|
|
15267
|
+
}
|
|
15268
|
+
} catch (e2) {
|
|
15269
|
+
if (e2 instanceof InvalidQueryError) {
|
|
15270
|
+
throw e2;
|
|
15271
|
+
}
|
|
15272
|
+
continue;
|
|
15273
|
+
}
|
|
15274
|
+
}
|
|
15275
|
+
} finally {
|
|
15276
|
+
try {
|
|
15277
|
+
parser.delete();
|
|
15278
|
+
} catch {
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
const conventions = {};
|
|
15282
|
+
const provenance = [];
|
|
15283
|
+
if (authDeps.size === 1) {
|
|
15284
|
+
const [name2, { line, file }] = authDeps.entries().next().value;
|
|
15285
|
+
conventions.auth_dep = name2;
|
|
15286
|
+
provenance.push({ field: "auth_dep", sourceFile: file, line, query: "fastapi-auth-dep" });
|
|
15287
|
+
} else if (authDeps.size >= 2) {
|
|
15288
|
+
const [name2, { line, file }] = authDeps.entries().next().value;
|
|
15289
|
+
conventions.auth_dep = name2;
|
|
15290
|
+
provenance.push({ field: "auth_dep", sourceFile: file, line, query: "fastapi-auth-dep" });
|
|
15291
|
+
}
|
|
15292
|
+
if (prefixBases.size >= 1) {
|
|
15293
|
+
const [base, { line, file }] = prefixBases.entries().next().value;
|
|
15294
|
+
conventions.api_prefix_base = base;
|
|
15295
|
+
provenance.push({ field: "api_prefix_base", sourceFile: file, line, query: "fastapi-api-prefix" });
|
|
15296
|
+
}
|
|
15297
|
+
if (testAsyncPatterns.size >= 1) {
|
|
15298
|
+
const [pat, { line, file }] = testAsyncPatterns.entries().next().value;
|
|
15299
|
+
conventions.test_async_pattern = pat;
|
|
15300
|
+
provenance.push({ field: "test_async_pattern", sourceFile: file, line, query: "fastapi-pytest-asyncio" });
|
|
15301
|
+
}
|
|
15302
|
+
let confidence;
|
|
15303
|
+
if (Object.keys(conventions).length === 0) {
|
|
15304
|
+
confidence = "none";
|
|
15305
|
+
} else if (authDeps.size === 1 || authDeps.size === 0 && prefixBases.size > 0) {
|
|
15306
|
+
confidence = "high";
|
|
15307
|
+
} else if (authDeps.size >= 2) {
|
|
15308
|
+
confidence = "low";
|
|
15309
|
+
} else {
|
|
15310
|
+
confidence = "medium";
|
|
15311
|
+
}
|
|
15312
|
+
return { conventions, provenance, confidence };
|
|
15313
|
+
}
|
|
15314
|
+
};
|
|
15315
|
+
}
|
|
15316
|
+
});
|
|
15317
|
+
|
|
15318
|
+
// src/detect/adapters/python-django.ts
|
|
15319
|
+
var DECORATOR_QUERY, CLASS_BASE_QUERY, URLPATTERNS_QUERY, DJANGO_MIXIN_NAMES, DJANGO_DECORATOR_PATTERNS, pythonDjangoAdapter;
|
|
15320
|
+
var init_python_django = __esm({
|
|
15321
|
+
"src/detect/adapters/python-django.ts"() {
|
|
15322
|
+
"use strict";
|
|
15323
|
+
init_tree_sitter();
|
|
15324
|
+
init_query_helpers();
|
|
15325
|
+
init_tree_sitter_loader();
|
|
15326
|
+
init_parse_guard();
|
|
15327
|
+
DECORATOR_QUERY = `
|
|
15328
|
+
(decorator
|
|
15329
|
+
(identifier) @decorator_name)
|
|
15330
|
+
`;
|
|
15331
|
+
CLASS_BASE_QUERY = `
|
|
15332
|
+
(class_definition
|
|
15333
|
+
name: (identifier) @class_name
|
|
15334
|
+
superclasses: (argument_list
|
|
15335
|
+
(identifier) @base_name))
|
|
15336
|
+
`;
|
|
15337
|
+
URLPATTERNS_QUERY = `
|
|
15338
|
+
(assignment
|
|
15339
|
+
left: (identifier) @_target (#eq? @_target "urlpatterns")
|
|
15340
|
+
right: (list) @urlpatterns_list)
|
|
15341
|
+
`;
|
|
15342
|
+
DJANGO_MIXIN_NAMES = /* @__PURE__ */ new Set([
|
|
15343
|
+
"LoginRequiredMixin",
|
|
15344
|
+
"PermissionRequiredMixin",
|
|
15345
|
+
"UserPassesTestMixin",
|
|
15346
|
+
"StaffuserRequiredMixin"
|
|
15347
|
+
]);
|
|
15348
|
+
DJANGO_DECORATOR_PATTERNS = [
|
|
15349
|
+
/^login_required$/,
|
|
15350
|
+
/^permission_required$/,
|
|
15351
|
+
/_required$/,
|
|
15352
|
+
/^require_/
|
|
15353
|
+
];
|
|
15354
|
+
pythonDjangoAdapter = {
|
|
15355
|
+
id: "python-django",
|
|
15356
|
+
languages: ["python"],
|
|
15357
|
+
matches(signals) {
|
|
15358
|
+
if (signals.presentFiles.has("manage.py")) return true;
|
|
15359
|
+
const pyToml = signals.pyprojectToml;
|
|
15360
|
+
if (pyToml?.__raw && /\bdjango\b/i.test(pyToml.__raw)) return true;
|
|
15361
|
+
return false;
|
|
15362
|
+
},
|
|
15363
|
+
async introspect(files, _rootDir) {
|
|
15364
|
+
if (files.length === 0) {
|
|
15365
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15366
|
+
}
|
|
15367
|
+
let language;
|
|
15368
|
+
try {
|
|
15369
|
+
language = await loadGrammar("python");
|
|
15370
|
+
} catch {
|
|
15371
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15372
|
+
}
|
|
15373
|
+
const parser = new Parser();
|
|
15374
|
+
parser.setLanguage(language);
|
|
15375
|
+
const decoratorsFound = /* @__PURE__ */ new Map();
|
|
15376
|
+
const mixinsFound = /* @__PURE__ */ new Map();
|
|
15377
|
+
const urlpatternsShape = {
|
|
15378
|
+
value: null,
|
|
15379
|
+
line: 0,
|
|
15380
|
+
file: ""
|
|
15381
|
+
};
|
|
15382
|
+
try {
|
|
15383
|
+
for (const file of files) {
|
|
15384
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15385
|
+
if (skip) {
|
|
15386
|
+
process.stderr.write(
|
|
15387
|
+
`[massu/ast] WARN: python-django skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15388
|
+
`
|
|
15389
|
+
);
|
|
15390
|
+
continue;
|
|
15391
|
+
}
|
|
15392
|
+
try {
|
|
15393
|
+
for (const hit of runQuery(parser, file.content, DECORATOR_QUERY, "django-decorator", file.path)) {
|
|
15394
|
+
const name2 = hit.captures.decorator_name;
|
|
15395
|
+
if (!name2) continue;
|
|
15396
|
+
if (DJANGO_DECORATOR_PATTERNS.some((re2) => re2.test(name2)) && !decoratorsFound.has(name2)) {
|
|
15397
|
+
decoratorsFound.set(name2, { line: hit.line, file: file.path });
|
|
15398
|
+
}
|
|
15399
|
+
}
|
|
15400
|
+
for (const hit of runQuery(parser, file.content, CLASS_BASE_QUERY, "django-mixin", file.path)) {
|
|
15401
|
+
const base = hit.captures.base_name;
|
|
15402
|
+
if (base && DJANGO_MIXIN_NAMES.has(base) && !mixinsFound.has(base)) {
|
|
15403
|
+
mixinsFound.set(base, { line: hit.line, file: file.path });
|
|
15404
|
+
}
|
|
15405
|
+
}
|
|
15406
|
+
for (const hit of runQuery(parser, file.content, URLPATTERNS_QUERY, "django-urlpatterns", file.path)) {
|
|
15407
|
+
const listText = hit.captures.urlpatterns_list ?? "";
|
|
15408
|
+
const hasFunctionForm = /\bpath\s*\(/.test(listText) || /\bre_path\s*\(/.test(listText);
|
|
15409
|
+
const hasClassForm = /\.as_view\s*\(/.test(listText);
|
|
15410
|
+
let shape = null;
|
|
15411
|
+
if (hasFunctionForm && hasClassForm) shape = "mixed";
|
|
15412
|
+
else if (hasFunctionForm) shape = "function-views";
|
|
15413
|
+
else if (hasClassForm) shape = "class-views";
|
|
15414
|
+
if (shape && !urlpatternsShape.value) {
|
|
15415
|
+
urlpatternsShape.value = shape;
|
|
15416
|
+
urlpatternsShape.line = hit.line;
|
|
15417
|
+
urlpatternsShape.file = file.path;
|
|
15418
|
+
}
|
|
15419
|
+
}
|
|
15420
|
+
} catch (e2) {
|
|
15421
|
+
if (e2 instanceof InvalidQueryError) throw e2;
|
|
15422
|
+
continue;
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
} finally {
|
|
15426
|
+
try {
|
|
15427
|
+
parser.delete();
|
|
15428
|
+
} catch {
|
|
15429
|
+
}
|
|
15430
|
+
}
|
|
15431
|
+
const conventions = {};
|
|
15432
|
+
const provenance = [];
|
|
15433
|
+
if (decoratorsFound.size > 0) {
|
|
15434
|
+
const list = Array.from(decoratorsFound.keys());
|
|
15435
|
+
conventions.decorator_usage = list;
|
|
15436
|
+
const [first, { line, file }] = decoratorsFound.entries().next().value;
|
|
15437
|
+
provenance.push({ field: "decorator_usage", sourceFile: file, line, query: "django-decorator" });
|
|
15438
|
+
conventions.auth_dep = first;
|
|
15439
|
+
provenance.push({ field: "auth_dep", sourceFile: file, line, query: "django-decorator" });
|
|
15440
|
+
}
|
|
15441
|
+
if (mixinsFound.size > 0) {
|
|
15442
|
+
const list = Array.from(mixinsFound.keys());
|
|
15443
|
+
conventions.mixin_classes = list;
|
|
15444
|
+
const [, { line, file }] = mixinsFound.entries().next().value;
|
|
15445
|
+
provenance.push({ field: "mixin_classes", sourceFile: file, line, query: "django-mixin" });
|
|
15446
|
+
}
|
|
15447
|
+
if (urlpatternsShape.value) {
|
|
15448
|
+
conventions.urlpatterns_shape = urlpatternsShape.value;
|
|
15449
|
+
provenance.push({
|
|
15450
|
+
field: "urlpatterns_shape",
|
|
15451
|
+
sourceFile: urlpatternsShape.file,
|
|
15452
|
+
line: urlpatternsShape.line,
|
|
15453
|
+
query: "django-urlpatterns"
|
|
15454
|
+
});
|
|
15455
|
+
}
|
|
15456
|
+
let confidence;
|
|
15457
|
+
if (Object.keys(conventions).length === 0) {
|
|
15458
|
+
confidence = "none";
|
|
15459
|
+
} else if (decoratorsFound.size > 0 || mixinsFound.size > 0) {
|
|
15460
|
+
confidence = "high";
|
|
15461
|
+
} else {
|
|
15462
|
+
confidence = "medium";
|
|
15463
|
+
}
|
|
15464
|
+
return { conventions, provenance, confidence };
|
|
15465
|
+
}
|
|
15466
|
+
};
|
|
15467
|
+
}
|
|
15468
|
+
});
|
|
15469
|
+
|
|
15470
|
+
// src/detect/adapters/nextjs-trpc.ts
|
|
15471
|
+
var ROUTER_BUILDER_QUERY, PROCEDURE_QUERY, KNOWN_BUILDERS, nextjsTrpcAdapter;
|
|
15472
|
+
var init_nextjs_trpc = __esm({
|
|
15473
|
+
"src/detect/adapters/nextjs-trpc.ts"() {
|
|
15474
|
+
"use strict";
|
|
15475
|
+
init_tree_sitter();
|
|
15476
|
+
init_query_helpers();
|
|
15477
|
+
init_tree_sitter_loader();
|
|
15478
|
+
init_parse_guard();
|
|
15479
|
+
ROUTER_BUILDER_QUERY = `
|
|
15480
|
+
(call_expression
|
|
15481
|
+
function: (identifier) @builder_id (#match? @builder_id "^(createTRPCRouter|router)$"))
|
|
15482
|
+
|
|
15483
|
+
(call_expression
|
|
15484
|
+
function: (member_expression
|
|
15485
|
+
object: (identifier) @_obj
|
|
15486
|
+
property: (property_identifier) @_prop (#eq? @_prop "router"))) @member_call
|
|
15487
|
+
`;
|
|
15488
|
+
PROCEDURE_QUERY = `
|
|
15489
|
+
(member_expression
|
|
15490
|
+
object: (identifier) @procedure_id (#match? @procedure_id "Procedure$"))
|
|
15491
|
+
|
|
15492
|
+
(call_expression
|
|
15493
|
+
function: (identifier) @procedure_call (#match? @procedure_call "Procedure$"))
|
|
15494
|
+
`;
|
|
15495
|
+
KNOWN_BUILDERS = /* @__PURE__ */ new Set(["createTRPCRouter", "router"]);
|
|
15496
|
+
nextjsTrpcAdapter = {
|
|
15497
|
+
id: "nextjs-trpc",
|
|
15498
|
+
languages: ["typescript"],
|
|
15499
|
+
matches(signals) {
|
|
15500
|
+
const pkgJson = signals.packageJson;
|
|
15501
|
+
if (pkgJson) {
|
|
15502
|
+
const deps = pkgJson.dependencies;
|
|
15503
|
+
const devDeps = pkgJson.devDependencies;
|
|
15504
|
+
const all = { ...deps ?? {}, ...devDeps ?? {} };
|
|
15505
|
+
if (Object.keys(all).some((k3) => k3.startsWith("@trpc/"))) return true;
|
|
15506
|
+
}
|
|
15507
|
+
if (signals.presentDirs.has("server")) return true;
|
|
15508
|
+
return false;
|
|
15509
|
+
},
|
|
15510
|
+
async introspect(files, _rootDir) {
|
|
15511
|
+
if (files.length === 0) {
|
|
15512
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15513
|
+
}
|
|
15514
|
+
let language;
|
|
15515
|
+
try {
|
|
15516
|
+
language = await loadGrammar("typescript");
|
|
15517
|
+
} catch {
|
|
15518
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15519
|
+
}
|
|
15520
|
+
const parser = new Parser();
|
|
15521
|
+
parser.setLanguage(language);
|
|
15522
|
+
const builders = /* @__PURE__ */ new Map();
|
|
15523
|
+
const procedures = /* @__PURE__ */ new Map();
|
|
15524
|
+
try {
|
|
15525
|
+
for (const file of files) {
|
|
15526
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15527
|
+
if (skip) {
|
|
15528
|
+
process.stderr.write(
|
|
15529
|
+
`[massu/ast] WARN: nextjs-trpc skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15530
|
+
`
|
|
15531
|
+
);
|
|
15532
|
+
continue;
|
|
15533
|
+
}
|
|
15534
|
+
try {
|
|
15535
|
+
for (const hit of runQuery(parser, file.content, ROUTER_BUILDER_QUERY, "trpc-router-builder", file.path)) {
|
|
15536
|
+
const directId = hit.captures.builder_id;
|
|
15537
|
+
const memberCall = hit.captures.member_call;
|
|
15538
|
+
let label = null;
|
|
15539
|
+
if (directId && KNOWN_BUILDERS.has(directId)) {
|
|
15540
|
+
label = directId;
|
|
15541
|
+
} else if (memberCall) {
|
|
15542
|
+
const m3 = memberCall.match(/([A-Za-z_$][A-Za-z0-9_$]*)\.router/);
|
|
15543
|
+
if (m3) label = `${m3[1]}.router`;
|
|
15544
|
+
}
|
|
15545
|
+
if (label && !builders.has(label)) {
|
|
15546
|
+
builders.set(label, { line: hit.line, file: file.path });
|
|
15547
|
+
}
|
|
15548
|
+
}
|
|
15549
|
+
for (const hit of runQuery(parser, file.content, PROCEDURE_QUERY, "trpc-procedure", file.path)) {
|
|
15550
|
+
const proc = hit.captures.procedure_id ?? hit.captures.procedure_call;
|
|
15551
|
+
if (proc && !procedures.has(proc)) {
|
|
15552
|
+
procedures.set(proc, { line: hit.line, file: file.path });
|
|
15553
|
+
}
|
|
15554
|
+
}
|
|
15555
|
+
} catch (e2) {
|
|
15556
|
+
if (e2 instanceof InvalidQueryError) throw e2;
|
|
15557
|
+
continue;
|
|
15558
|
+
}
|
|
15559
|
+
}
|
|
15560
|
+
} finally {
|
|
15561
|
+
try {
|
|
15562
|
+
parser.delete();
|
|
15563
|
+
} catch {
|
|
15564
|
+
}
|
|
15565
|
+
}
|
|
15566
|
+
const conventions = {};
|
|
15567
|
+
const provenance = [];
|
|
15568
|
+
if (builders.size > 0) {
|
|
15569
|
+
const [name2, { line, file }] = builders.entries().next().value;
|
|
15570
|
+
conventions.trpc_router_builder = name2;
|
|
15571
|
+
provenance.push({ field: "trpc_router_builder", sourceFile: file, line, query: "trpc-router-builder" });
|
|
15572
|
+
}
|
|
15573
|
+
if (procedures.size > 0) {
|
|
15574
|
+
const [name2, { line, file }] = procedures.entries().next().value;
|
|
15575
|
+
conventions.procedure_pattern = name2;
|
|
15576
|
+
provenance.push({ field: "procedure_pattern", sourceFile: file, line, query: "trpc-procedure" });
|
|
15577
|
+
}
|
|
15578
|
+
let confidence;
|
|
15579
|
+
if (Object.keys(conventions).length === 0) {
|
|
15580
|
+
confidence = "none";
|
|
15581
|
+
} else if (builders.size === 1 && procedures.size <= 2) {
|
|
15582
|
+
confidence = "high";
|
|
15583
|
+
} else if (builders.size > 1) {
|
|
15584
|
+
confidence = "low";
|
|
15585
|
+
} else {
|
|
15586
|
+
confidence = "medium";
|
|
15587
|
+
}
|
|
15588
|
+
return { conventions, provenance, confidence };
|
|
15589
|
+
}
|
|
15590
|
+
};
|
|
15591
|
+
}
|
|
15592
|
+
});
|
|
15593
|
+
|
|
15594
|
+
// src/detect/adapters/swift-swiftui.ts
|
|
15595
|
+
var API_CLASS_QUERY, POLICY_QUERY, NAV_QUERY, POLICY_NAMES, swiftSwiftUiAdapter;
|
|
15596
|
+
var init_swift_swiftui = __esm({
|
|
15597
|
+
"src/detect/adapters/swift-swiftui.ts"() {
|
|
15598
|
+
"use strict";
|
|
15599
|
+
init_tree_sitter();
|
|
15600
|
+
init_query_helpers();
|
|
15601
|
+
init_tree_sitter_loader();
|
|
15602
|
+
init_parse_guard();
|
|
15603
|
+
API_CLASS_QUERY = `
|
|
15604
|
+
(simple_identifier) @ident
|
|
15605
|
+
`;
|
|
15606
|
+
POLICY_QUERY = `
|
|
15607
|
+
(navigation_expression
|
|
15608
|
+
suffix: (navigation_suffix
|
|
15609
|
+
(simple_identifier) @policy_name))
|
|
15610
|
+
`;
|
|
15611
|
+
NAV_QUERY = `
|
|
15612
|
+
(simple_identifier) @nav_ident
|
|
15613
|
+
`;
|
|
15614
|
+
POLICY_NAMES = /* @__PURE__ */ new Set([
|
|
15615
|
+
"deviceOwnerAuthentication",
|
|
15616
|
+
"deviceOwnerAuthenticationWithBiometrics"
|
|
15617
|
+
]);
|
|
15618
|
+
swiftSwiftUiAdapter = {
|
|
15619
|
+
id: "swift-swiftui",
|
|
15620
|
+
languages: ["swift"],
|
|
15621
|
+
matches(signals) {
|
|
15622
|
+
if (signals.presentFiles.has("Package.swift")) return true;
|
|
15623
|
+
for (const dir of signals.presentDirs) {
|
|
15624
|
+
if (dir.endsWith(".xcodeproj") || dir.endsWith(".xcworkspace")) return true;
|
|
15625
|
+
if (dir === "Sources") return true;
|
|
15626
|
+
}
|
|
15627
|
+
for (const file of signals.presentFiles) {
|
|
15628
|
+
if (file.endsWith(".swift")) return true;
|
|
15629
|
+
}
|
|
15630
|
+
return false;
|
|
15631
|
+
},
|
|
15632
|
+
async introspect(files, _rootDir) {
|
|
15633
|
+
if (files.length === 0) {
|
|
15634
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15635
|
+
}
|
|
15636
|
+
let language;
|
|
15637
|
+
try {
|
|
15638
|
+
language = await loadGrammar("swift");
|
|
15639
|
+
} catch {
|
|
15640
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15641
|
+
}
|
|
15642
|
+
const parser = new Parser();
|
|
15643
|
+
parser.setLanguage(language);
|
|
15644
|
+
const apiClasses = /* @__PURE__ */ new Map();
|
|
15645
|
+
const policies = /* @__PURE__ */ new Map();
|
|
15646
|
+
const navs = /* @__PURE__ */ new Map();
|
|
15647
|
+
try {
|
|
15648
|
+
for (const file of files) {
|
|
15649
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15650
|
+
if (skip) {
|
|
15651
|
+
process.stderr.write(
|
|
15652
|
+
`[massu/ast] WARN: swift-swiftui skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15653
|
+
`
|
|
15654
|
+
);
|
|
15655
|
+
continue;
|
|
15656
|
+
}
|
|
15657
|
+
try {
|
|
15658
|
+
for (const hit of runQuery(parser, file.content, API_CLASS_QUERY, "swift-api-class", file.path)) {
|
|
15659
|
+
const ident = hit.captures.ident;
|
|
15660
|
+
if (ident && /^[A-Z][A-Za-z0-9_]*API$/.test(ident) && !apiClasses.has(ident)) {
|
|
15661
|
+
apiClasses.set(ident, { line: hit.line, file: file.path });
|
|
15662
|
+
}
|
|
15663
|
+
}
|
|
15664
|
+
for (const hit of runQuery(parser, file.content, POLICY_QUERY, "swift-biometric-policy", file.path)) {
|
|
15665
|
+
const name2 = hit.captures.policy_name;
|
|
15666
|
+
if (name2 && POLICY_NAMES.has(name2) && !policies.has(name2)) {
|
|
15667
|
+
policies.set(name2, { line: hit.line, file: file.path });
|
|
15668
|
+
}
|
|
15669
|
+
}
|
|
15670
|
+
for (const hit of runQuery(parser, file.content, NAV_QUERY, "swift-navigation", file.path)) {
|
|
15671
|
+
const ident = hit.captures.nav_ident;
|
|
15672
|
+
if ((ident === "NavigationStack" || ident === "NavigationView") && !navs.has(ident)) {
|
|
15673
|
+
navs.set(ident, { line: hit.line, file: file.path });
|
|
15674
|
+
}
|
|
15675
|
+
}
|
|
15676
|
+
} catch (e2) {
|
|
15677
|
+
if (e2 instanceof InvalidQueryError) throw e2;
|
|
15678
|
+
continue;
|
|
15679
|
+
}
|
|
15680
|
+
}
|
|
15681
|
+
} finally {
|
|
15682
|
+
try {
|
|
15683
|
+
parser.delete();
|
|
15684
|
+
} catch {
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
const conventions = {};
|
|
15688
|
+
const provenance = [];
|
|
15689
|
+
if (apiClasses.size > 0) {
|
|
15690
|
+
const [name2, { line, file }] = apiClasses.entries().next().value;
|
|
15691
|
+
conventions.api_client_class = name2;
|
|
15692
|
+
provenance.push({ field: "api_client_class", sourceFile: file, line, query: "swift-api-class" });
|
|
15693
|
+
}
|
|
15694
|
+
if (policies.size > 0) {
|
|
15695
|
+
const [name2, { line, file }] = policies.entries().next().value;
|
|
15696
|
+
conventions.biometric_policy = name2;
|
|
15697
|
+
provenance.push({ field: "biometric_policy", sourceFile: file, line, query: "swift-biometric-policy" });
|
|
15698
|
+
}
|
|
15699
|
+
if (navs.size > 0) {
|
|
15700
|
+
const [name2, { line, file }] = navs.entries().next().value;
|
|
15701
|
+
conventions.navigation_pattern = name2;
|
|
15702
|
+
provenance.push({ field: "navigation_pattern", sourceFile: file, line, query: "swift-navigation" });
|
|
15703
|
+
}
|
|
15704
|
+
let confidence;
|
|
15705
|
+
if (Object.keys(conventions).length === 0) {
|
|
15706
|
+
confidence = "none";
|
|
15707
|
+
} else if (apiClasses.size === 1 && policies.size <= 1) {
|
|
15708
|
+
confidence = "high";
|
|
15709
|
+
} else if (apiClasses.size > 1) {
|
|
15710
|
+
confidence = "low";
|
|
15711
|
+
} else {
|
|
15712
|
+
confidence = "medium";
|
|
15713
|
+
}
|
|
15714
|
+
return { conventions, provenance, confidence };
|
|
15715
|
+
}
|
|
15716
|
+
};
|
|
15717
|
+
}
|
|
15718
|
+
});
|
|
15719
|
+
|
|
15720
|
+
// src/detect/adapters/python-flask.ts
|
|
15721
|
+
function extractPrefixBase3(prefix3) {
|
|
15722
|
+
if (!prefix3.startsWith("/")) return null;
|
|
15723
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
15724
|
+
const firstSeg = stripped.split("/")[0];
|
|
15725
|
+
if (!firstSeg) return null;
|
|
15726
|
+
return "/" + firstSeg;
|
|
15727
|
+
}
|
|
15728
|
+
var AUTH_DECORATOR_QUERY, BLUEPRINT_URL_PREFIX_QUERY, APP_FACTORY_QUERY, pythonFlaskAdapter;
|
|
15729
|
+
var init_python_flask = __esm({
|
|
15730
|
+
"src/detect/adapters/python-flask.ts"() {
|
|
15731
|
+
"use strict";
|
|
15732
|
+
init_tree_sitter();
|
|
15733
|
+
init_query_helpers();
|
|
15734
|
+
init_tree_sitter_loader();
|
|
15735
|
+
init_parse_guard();
|
|
15736
|
+
AUTH_DECORATOR_QUERY = `
|
|
15737
|
+
(decorator
|
|
15738
|
+
(identifier) @auth_decorator (#match? @auth_decorator "_required$"))
|
|
15739
|
+
`;
|
|
15740
|
+
BLUEPRINT_URL_PREFIX_QUERY = `
|
|
15741
|
+
(call
|
|
15742
|
+
function: (identifier) @_callee (#eq? @_callee "Blueprint")
|
|
15743
|
+
arguments: (argument_list
|
|
15744
|
+
(keyword_argument
|
|
15745
|
+
name: (identifier) @_kw (#eq? @_kw "url_prefix")
|
|
15746
|
+
value: (string) @url_prefix)))
|
|
15747
|
+
`;
|
|
15748
|
+
APP_FACTORY_QUERY = `
|
|
15749
|
+
(function_definition
|
|
15750
|
+
name: (identifier) @factory_name (#match? @factory_name "^create_")
|
|
15751
|
+
body: (block
|
|
15752
|
+
(expression_statement
|
|
15753
|
+
(assignment
|
|
15754
|
+
right: (call
|
|
15755
|
+
function: (identifier) @_flask_call (#eq? @_flask_call "Flask"))))))
|
|
15756
|
+
`;
|
|
15757
|
+
pythonFlaskAdapter = {
|
|
15758
|
+
id: "python-flask",
|
|
15759
|
+
languages: ["python"],
|
|
15760
|
+
matches(signals) {
|
|
15761
|
+
const pyToml = signals.pyprojectToml;
|
|
15762
|
+
if (pyToml?.__raw && /\bflask\b/i.test(pyToml.__raw)) return true;
|
|
15763
|
+
if (signals.presentDirs.has("app") && signals.presentFiles.has("app.py")) return true;
|
|
15764
|
+
if (signals.presentDirs.has("app") && signals.presentFiles.has("wsgi.py")) return true;
|
|
15765
|
+
return false;
|
|
15766
|
+
},
|
|
15767
|
+
async introspect(files, _rootDir) {
|
|
15768
|
+
if (files.length === 0) {
|
|
15769
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15770
|
+
}
|
|
15771
|
+
let language;
|
|
15772
|
+
try {
|
|
15773
|
+
language = await loadGrammar("python");
|
|
15774
|
+
} catch (e2) {
|
|
15775
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15776
|
+
}
|
|
15777
|
+
const parser = new Parser();
|
|
15778
|
+
parser.setLanguage(language);
|
|
15779
|
+
const authDecorators = /* @__PURE__ */ new Map();
|
|
15780
|
+
const urlPrefixes = /* @__PURE__ */ new Map();
|
|
15781
|
+
const appFactories = /* @__PURE__ */ new Map();
|
|
15782
|
+
try {
|
|
15783
|
+
for (const file of files) {
|
|
15784
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15785
|
+
if (skip) {
|
|
15786
|
+
process.stderr.write(
|
|
15787
|
+
`[massu/ast] WARN: python-flask skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15788
|
+
`
|
|
15789
|
+
);
|
|
15790
|
+
continue;
|
|
15791
|
+
}
|
|
15792
|
+
try {
|
|
15793
|
+
for (const hit of runQuery(parser, file.content, AUTH_DECORATOR_QUERY, "flask-auth-decorator", file.path)) {
|
|
15794
|
+
const name2 = hit.captures.auth_decorator;
|
|
15795
|
+
if (name2 && !authDecorators.has(name2)) {
|
|
15796
|
+
authDecorators.set(name2, { line: hit.line, file: file.path });
|
|
15797
|
+
}
|
|
15798
|
+
}
|
|
15799
|
+
for (const hit of runQuery(parser, file.content, BLUEPRINT_URL_PREFIX_QUERY, "flask-blueprint-url-prefix", file.path)) {
|
|
15800
|
+
const raw = hit.captures.url_prefix;
|
|
15801
|
+
if (!raw) continue;
|
|
15802
|
+
const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
|
|
15803
|
+
const base = extractPrefixBase3(literal);
|
|
15804
|
+
if (base && !urlPrefixes.has(base)) {
|
|
15805
|
+
urlPrefixes.set(base, { line: hit.line, file: file.path });
|
|
15806
|
+
}
|
|
15807
|
+
}
|
|
15808
|
+
for (const hit of runQuery(parser, file.content, APP_FACTORY_QUERY, "flask-app-factory", file.path)) {
|
|
15809
|
+
const name2 = hit.captures.factory_name;
|
|
15810
|
+
if (name2 && !appFactories.has(name2)) {
|
|
15811
|
+
appFactories.set(name2, { line: hit.line, file: file.path });
|
|
15812
|
+
}
|
|
15813
|
+
}
|
|
15814
|
+
} catch (e2) {
|
|
15815
|
+
if (e2 instanceof InvalidQueryError) {
|
|
15816
|
+
throw e2;
|
|
15817
|
+
}
|
|
15818
|
+
continue;
|
|
15819
|
+
}
|
|
15820
|
+
}
|
|
15821
|
+
} finally {
|
|
15822
|
+
try {
|
|
15823
|
+
parser.delete();
|
|
15824
|
+
} catch {
|
|
15825
|
+
}
|
|
15826
|
+
}
|
|
15827
|
+
const conventions = {};
|
|
15828
|
+
const provenance = [];
|
|
15829
|
+
if (authDecorators.size === 1) {
|
|
15830
|
+
const [name2, { line, file }] = authDecorators.entries().next().value;
|
|
15831
|
+
conventions.auth_decorator = name2;
|
|
15832
|
+
provenance.push({ field: "auth_decorator", sourceFile: file, line, query: "flask-auth-decorator" });
|
|
15833
|
+
} else if (authDecorators.size >= 2) {
|
|
15834
|
+
const [name2, { line, file }] = authDecorators.entries().next().value;
|
|
15835
|
+
conventions.auth_decorator = name2;
|
|
15836
|
+
provenance.push({ field: "auth_decorator", sourceFile: file, line, query: "flask-auth-decorator" });
|
|
15837
|
+
}
|
|
15838
|
+
if (urlPrefixes.size >= 1) {
|
|
15839
|
+
const [base, { line, file }] = urlPrefixes.entries().next().value;
|
|
15840
|
+
conventions.blueprint_url_prefix = base;
|
|
15841
|
+
provenance.push({ field: "blueprint_url_prefix", sourceFile: file, line, query: "flask-blueprint-url-prefix" });
|
|
15842
|
+
}
|
|
15843
|
+
if (appFactories.size >= 1) {
|
|
15844
|
+
const [name2, { line, file }] = appFactories.entries().next().value;
|
|
15845
|
+
conventions.app_factory = name2;
|
|
15846
|
+
provenance.push({ field: "app_factory", sourceFile: file, line, query: "flask-app-factory" });
|
|
15847
|
+
}
|
|
15848
|
+
let confidence;
|
|
15849
|
+
if (Object.keys(conventions).length === 0) {
|
|
15850
|
+
confidence = "none";
|
|
15851
|
+
} else if (authDecorators.size === 1) {
|
|
15852
|
+
confidence = "high";
|
|
15853
|
+
} else if (authDecorators.size >= 2) {
|
|
15854
|
+
confidence = "low";
|
|
15855
|
+
} else {
|
|
15856
|
+
confidence = "medium";
|
|
15857
|
+
}
|
|
15858
|
+
return { conventions, provenance, confidence };
|
|
15859
|
+
}
|
|
15860
|
+
};
|
|
15861
|
+
}
|
|
15862
|
+
});
|
|
15863
|
+
|
|
15864
|
+
// src/detect/adapters/go-chi.ts
|
|
15865
|
+
function extractPrefixBase4(prefix3) {
|
|
15866
|
+
if (!prefix3.startsWith("/")) return null;
|
|
15867
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
15868
|
+
const firstSeg = stripped.split("/")[0];
|
|
15869
|
+
if (!firstSeg) return null;
|
|
15870
|
+
return "/" + firstSeg;
|
|
15871
|
+
}
|
|
15872
|
+
var ROUTE_METHOD_QUERY, MOUNT_PREFIX_QUERY, MIDDLEWARE_USE_QUERY, goChiAdapter;
|
|
15873
|
+
var init_go_chi = __esm({
|
|
15874
|
+
"src/detect/adapters/go-chi.ts"() {
|
|
15875
|
+
"use strict";
|
|
15876
|
+
init_tree_sitter();
|
|
15877
|
+
init_query_helpers();
|
|
15878
|
+
init_tree_sitter_loader();
|
|
15879
|
+
init_parse_guard();
|
|
15880
|
+
ROUTE_METHOD_QUERY = `
|
|
15881
|
+
(call_expression
|
|
15882
|
+
function: (selector_expression
|
|
15883
|
+
field: (field_identifier) @method (#match? @method "^(Get|Post|Put|Delete|Patch|Head|Options|Connect|Trace)$"))
|
|
15884
|
+
arguments: (argument_list
|
|
15885
|
+
.
|
|
15886
|
+
(interpreted_string_literal) @route_path))
|
|
15887
|
+
`;
|
|
15888
|
+
MOUNT_PREFIX_QUERY = `
|
|
15889
|
+
(call_expression
|
|
15890
|
+
function: (selector_expression
|
|
15891
|
+
field: (field_identifier) @_field (#eq? @_field "Mount"))
|
|
15892
|
+
arguments: (argument_list
|
|
15893
|
+
.
|
|
15894
|
+
(interpreted_string_literal) @mount_path))
|
|
15895
|
+
`;
|
|
15896
|
+
MIDDLEWARE_USE_QUERY = `
|
|
15897
|
+
(call_expression
|
|
15898
|
+
function: (selector_expression
|
|
15899
|
+
field: (field_identifier) @_use (#eq? @_use "Use"))
|
|
15900
|
+
arguments: (argument_list
|
|
15901
|
+
.
|
|
15902
|
+
(selector_expression
|
|
15903
|
+
operand: (identifier) @_pkg (#eq? @_pkg "middleware")
|
|
15904
|
+
field: (field_identifier) @middleware_name)))
|
|
15905
|
+
`;
|
|
15906
|
+
goChiAdapter = {
|
|
15907
|
+
id: "go-chi",
|
|
15908
|
+
languages: ["go"],
|
|
15909
|
+
matches(signals) {
|
|
15910
|
+
if (!signals.goMod) return false;
|
|
15911
|
+
if (/github\.com\/go-chi\/chi/i.test(signals.goMod)) return true;
|
|
15912
|
+
return false;
|
|
15913
|
+
},
|
|
15914
|
+
async introspect(files, _rootDir) {
|
|
15915
|
+
if (files.length === 0) {
|
|
15916
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15917
|
+
}
|
|
15918
|
+
let language;
|
|
15919
|
+
try {
|
|
15920
|
+
language = await loadGrammar("go");
|
|
15921
|
+
} catch (e2) {
|
|
15922
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15923
|
+
}
|
|
15924
|
+
const parser = new Parser();
|
|
15925
|
+
parser.setLanguage(language);
|
|
15926
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
15927
|
+
const mountBases = /* @__PURE__ */ new Map();
|
|
15928
|
+
const middlewareNames = /* @__PURE__ */ new Map();
|
|
15929
|
+
try {
|
|
15930
|
+
for (const file of files) {
|
|
15931
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15932
|
+
if (skip) {
|
|
15933
|
+
process.stderr.write(
|
|
15934
|
+
`[massu/ast] WARN: go-chi skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15935
|
+
`
|
|
15936
|
+
);
|
|
15937
|
+
continue;
|
|
15938
|
+
}
|
|
15939
|
+
try {
|
|
15940
|
+
for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY, "chi-route-method", file.path)) {
|
|
15941
|
+
const method = hit.captures.method;
|
|
15942
|
+
if (method && !routeMethods.has(method)) {
|
|
15943
|
+
routeMethods.set(method, { line: hit.line, file: file.path });
|
|
15944
|
+
}
|
|
15945
|
+
}
|
|
15946
|
+
for (const hit of runQuery(parser, file.content, MOUNT_PREFIX_QUERY, "chi-mount-prefix", file.path)) {
|
|
15947
|
+
const raw = hit.captures.mount_path;
|
|
15948
|
+
if (!raw) continue;
|
|
15949
|
+
const literal = raw.replace(/^["`]/, "").replace(/["`]$/, "");
|
|
15950
|
+
const base = extractPrefixBase4(literal);
|
|
15951
|
+
if (base && !mountBases.has(base)) {
|
|
15952
|
+
mountBases.set(base, { line: hit.line, file: file.path });
|
|
15953
|
+
}
|
|
15954
|
+
}
|
|
15955
|
+
for (const hit of runQuery(parser, file.content, MIDDLEWARE_USE_QUERY, "chi-middleware-use", file.path)) {
|
|
15956
|
+
const name2 = hit.captures.middleware_name;
|
|
15957
|
+
if (name2 && !middlewareNames.has(name2)) {
|
|
15958
|
+
middlewareNames.set(name2, { line: hit.line, file: file.path });
|
|
15959
|
+
}
|
|
15960
|
+
}
|
|
15961
|
+
} catch (e2) {
|
|
15962
|
+
if (e2 instanceof InvalidQueryError) {
|
|
15963
|
+
throw e2;
|
|
15964
|
+
}
|
|
15965
|
+
continue;
|
|
15966
|
+
}
|
|
15967
|
+
}
|
|
15968
|
+
} finally {
|
|
15969
|
+
try {
|
|
15970
|
+
parser.delete();
|
|
15971
|
+
} catch {
|
|
15972
|
+
}
|
|
15973
|
+
}
|
|
15974
|
+
const conventions = {};
|
|
15975
|
+
const provenance = [];
|
|
15976
|
+
if (routeMethods.size === 1) {
|
|
15977
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
15978
|
+
conventions.route_method = name2;
|
|
15979
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "chi-route-method" });
|
|
15980
|
+
} else if (routeMethods.size >= 2) {
|
|
15981
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
15982
|
+
conventions.route_method = name2;
|
|
15983
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "chi-route-method" });
|
|
15984
|
+
}
|
|
15985
|
+
if (mountBases.size >= 1) {
|
|
15986
|
+
const [base, { line, file }] = mountBases.entries().next().value;
|
|
15987
|
+
conventions.mount_prefix_base = base;
|
|
15988
|
+
provenance.push({ field: "mount_prefix_base", sourceFile: file, line, query: "chi-mount-prefix" });
|
|
15989
|
+
}
|
|
15990
|
+
if (middlewareNames.size >= 1) {
|
|
15991
|
+
const [name2, { line, file }] = middlewareNames.entries().next().value;
|
|
15992
|
+
conventions.middleware_name = name2;
|
|
15993
|
+
provenance.push({ field: "middleware_name", sourceFile: file, line, query: "chi-middleware-use" });
|
|
15994
|
+
}
|
|
15995
|
+
let confidence;
|
|
15996
|
+
if (Object.keys(conventions).length === 0) {
|
|
15997
|
+
confidence = "none";
|
|
15998
|
+
} else if (routeMethods.size === 1) {
|
|
15999
|
+
confidence = "high";
|
|
16000
|
+
} else if (routeMethods.size >= 2) {
|
|
16001
|
+
confidence = "low";
|
|
16002
|
+
} else {
|
|
16003
|
+
confidence = "medium";
|
|
16004
|
+
}
|
|
16005
|
+
return { conventions, provenance, confidence };
|
|
16006
|
+
}
|
|
16007
|
+
};
|
|
16008
|
+
}
|
|
16009
|
+
});
|
|
16010
|
+
|
|
16011
|
+
// src/detect/adapters/rails.ts
|
|
16012
|
+
function extractRootController(target) {
|
|
16013
|
+
const idx = target.indexOf("#");
|
|
16014
|
+
if (idx <= 0) return null;
|
|
16015
|
+
const controller = target.slice(0, idx).trim();
|
|
16016
|
+
return controller || null;
|
|
16017
|
+
}
|
|
16018
|
+
var ROUTE_METHOD_QUERY2, NAMESPACE_QUERY, ROOT_ROUTE_QUERY, railsAdapter;
|
|
16019
|
+
var init_rails = __esm({
|
|
16020
|
+
"src/detect/adapters/rails.ts"() {
|
|
16021
|
+
"use strict";
|
|
16022
|
+
init_tree_sitter();
|
|
16023
|
+
init_query_helpers();
|
|
16024
|
+
init_tree_sitter_loader();
|
|
16025
|
+
init_parse_guard();
|
|
16026
|
+
ROUTE_METHOD_QUERY2 = `
|
|
16027
|
+
(call
|
|
16028
|
+
method: (identifier) @method (#match? @method "^(get|post|put|patch|delete|options|head)$")
|
|
16029
|
+
arguments: (argument_list
|
|
16030
|
+
.
|
|
16031
|
+
(string) @route_path))
|
|
16032
|
+
`;
|
|
16033
|
+
NAMESPACE_QUERY = `
|
|
16034
|
+
(call
|
|
16035
|
+
method: (identifier) @_method (#eq? @_method "namespace")
|
|
16036
|
+
arguments: (argument_list
|
|
16037
|
+
.
|
|
16038
|
+
[
|
|
16039
|
+
(simple_symbol) @namespace_symbol
|
|
16040
|
+
(string) @namespace_string
|
|
16041
|
+
]))
|
|
16042
|
+
`;
|
|
16043
|
+
ROOT_ROUTE_QUERY = `
|
|
16044
|
+
(call
|
|
16045
|
+
method: (identifier) @_method (#eq? @_method "root")
|
|
16046
|
+
arguments: (argument_list
|
|
16047
|
+
.
|
|
16048
|
+
(string) @root_target))
|
|
16049
|
+
|
|
16050
|
+
(call
|
|
16051
|
+
method: (identifier) @_method (#eq? @_method "root")
|
|
16052
|
+
arguments: (argument_list
|
|
16053
|
+
(pair
|
|
16054
|
+
key: (hash_key_symbol) @_key (#eq? @_key "to")
|
|
16055
|
+
value: (string) @root_target)))
|
|
16056
|
+
`;
|
|
16057
|
+
railsAdapter = {
|
|
16058
|
+
id: "rails",
|
|
16059
|
+
languages: ["ruby"],
|
|
16060
|
+
matches(signals) {
|
|
16061
|
+
if (!signals.gemfile) return false;
|
|
16062
|
+
return /^\s*gem\s+['"]rails['"]/im.test(signals.gemfile);
|
|
16063
|
+
},
|
|
16064
|
+
async introspect(files, _rootDir) {
|
|
16065
|
+
if (files.length === 0) {
|
|
16066
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16067
|
+
}
|
|
16068
|
+
let language;
|
|
16069
|
+
try {
|
|
16070
|
+
language = await loadGrammar("ruby");
|
|
16071
|
+
} catch (e2) {
|
|
16072
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16073
|
+
}
|
|
16074
|
+
const parser = new Parser();
|
|
16075
|
+
parser.setLanguage(language);
|
|
16076
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16077
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
16078
|
+
const rootControllers = /* @__PURE__ */ new Map();
|
|
16079
|
+
try {
|
|
16080
|
+
for (const file of files) {
|
|
16081
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16082
|
+
if (skip) {
|
|
16083
|
+
process.stderr.write(
|
|
16084
|
+
`[massu/ast] WARN: rails skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16085
|
+
`
|
|
16086
|
+
);
|
|
16087
|
+
continue;
|
|
16088
|
+
}
|
|
16089
|
+
try {
|
|
16090
|
+
for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY2, "rails-route-method", file.path)) {
|
|
16091
|
+
const method = hit.captures.method;
|
|
16092
|
+
if (method && !routeMethods.has(method)) {
|
|
16093
|
+
routeMethods.set(method, { line: hit.line, file: file.path });
|
|
16094
|
+
}
|
|
16095
|
+
}
|
|
16096
|
+
for (const hit of runQuery(parser, file.content, NAMESPACE_QUERY, "rails-namespace", file.path)) {
|
|
16097
|
+
const symbolRaw = hit.captures.namespace_symbol;
|
|
16098
|
+
const stringRaw = hit.captures.namespace_string;
|
|
16099
|
+
const name2 = symbolRaw ? symbolRaw.replace(/^:/, "") : stringRaw ? stringRaw.replace(/^['"]/, "").replace(/['"]$/, "") : null;
|
|
16100
|
+
if (!name2) continue;
|
|
16101
|
+
const path = "/" + name2;
|
|
16102
|
+
if (!namespaces.has(path)) {
|
|
16103
|
+
namespaces.set(path, { line: hit.line, file: file.path });
|
|
16104
|
+
}
|
|
16105
|
+
}
|
|
16106
|
+
for (const hit of runQuery(parser, file.content, ROOT_ROUTE_QUERY, "rails-root", file.path)) {
|
|
16107
|
+
const raw = hit.captures.root_target;
|
|
16108
|
+
if (!raw) continue;
|
|
16109
|
+
const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
|
|
16110
|
+
const controller = extractRootController(literal);
|
|
16111
|
+
if (controller && !rootControllers.has(controller)) {
|
|
16112
|
+
rootControllers.set(controller, { line: hit.line, file: file.path });
|
|
16113
|
+
}
|
|
16114
|
+
}
|
|
16115
|
+
} catch (e2) {
|
|
16116
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16117
|
+
throw e2;
|
|
16118
|
+
}
|
|
16119
|
+
continue;
|
|
16120
|
+
}
|
|
16121
|
+
}
|
|
16122
|
+
} finally {
|
|
16123
|
+
try {
|
|
16124
|
+
parser.delete();
|
|
16125
|
+
} catch {
|
|
16126
|
+
}
|
|
16127
|
+
}
|
|
16128
|
+
const conventions = {};
|
|
16129
|
+
const provenance = [];
|
|
16130
|
+
if (routeMethods.size === 1) {
|
|
16131
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16132
|
+
conventions.route_method = name2;
|
|
16133
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "rails-route-method" });
|
|
16134
|
+
} else if (routeMethods.size >= 2) {
|
|
16135
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16136
|
+
conventions.route_method = name2;
|
|
16137
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "rails-route-method" });
|
|
16138
|
+
}
|
|
16139
|
+
if (namespaces.size >= 1) {
|
|
16140
|
+
const [path, { line, file }] = namespaces.entries().next().value;
|
|
16141
|
+
conventions.api_namespace = path;
|
|
16142
|
+
provenance.push({ field: "api_namespace", sourceFile: file, line, query: "rails-namespace" });
|
|
16143
|
+
}
|
|
16144
|
+
if (rootControllers.size >= 1) {
|
|
16145
|
+
const [name2, { line, file }] = rootControllers.entries().next().value;
|
|
16146
|
+
conventions.root_controller = name2;
|
|
16147
|
+
provenance.push({ field: "root_controller", sourceFile: file, line, query: "rails-root" });
|
|
16148
|
+
}
|
|
16149
|
+
let confidence;
|
|
16150
|
+
if (Object.keys(conventions).length === 0) {
|
|
16151
|
+
confidence = "none";
|
|
16152
|
+
} else if (routeMethods.size === 1) {
|
|
16153
|
+
confidence = "high";
|
|
16154
|
+
} else if (routeMethods.size >= 2) {
|
|
16155
|
+
confidence = "low";
|
|
16156
|
+
} else {
|
|
16157
|
+
confidence = "medium";
|
|
16158
|
+
}
|
|
16159
|
+
return { conventions, provenance, confidence };
|
|
16160
|
+
}
|
|
16161
|
+
};
|
|
16162
|
+
}
|
|
16163
|
+
});
|
|
16164
|
+
|
|
16165
|
+
// src/detect/adapters/phoenix.ts
|
|
16166
|
+
function extractPrefixBase5(prefix3) {
|
|
16167
|
+
if (!prefix3.startsWith("/")) return null;
|
|
16168
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
16169
|
+
const firstSeg = stripped.split("/")[0];
|
|
16170
|
+
if (!firstSeg) return null;
|
|
16171
|
+
return "/" + firstSeg;
|
|
16172
|
+
}
|
|
16173
|
+
var ROUTE_METHOD_QUERY3, SCOPE_PATH_QUERY, ROUTER_MODULE_QUERY, phoenixAdapter;
|
|
16174
|
+
var init_phoenix = __esm({
|
|
16175
|
+
"src/detect/adapters/phoenix.ts"() {
|
|
16176
|
+
"use strict";
|
|
16177
|
+
init_tree_sitter();
|
|
16178
|
+
init_query_helpers();
|
|
16179
|
+
init_tree_sitter_loader();
|
|
16180
|
+
init_parse_guard();
|
|
16181
|
+
ROUTE_METHOD_QUERY3 = `
|
|
16182
|
+
(call
|
|
16183
|
+
(identifier) @method (#match? @method "^(get|post|put|patch|delete|options|head)$")
|
|
16184
|
+
(arguments
|
|
16185
|
+
.
|
|
16186
|
+
(string) @route_path))
|
|
16187
|
+
`;
|
|
16188
|
+
SCOPE_PATH_QUERY = `
|
|
16189
|
+
(call
|
|
16190
|
+
(identifier) @_method (#eq? @_method "scope")
|
|
16191
|
+
(arguments
|
|
16192
|
+
.
|
|
16193
|
+
(string) @scope_path)
|
|
16194
|
+
(do_block))
|
|
16195
|
+
`;
|
|
16196
|
+
ROUTER_MODULE_QUERY = `
|
|
16197
|
+
(call
|
|
16198
|
+
(identifier) @_method (#eq? @_method "defmodule")
|
|
16199
|
+
(arguments
|
|
16200
|
+
.
|
|
16201
|
+
(alias) @module_name (#match? @module_name "Router$"))
|
|
16202
|
+
(do_block))
|
|
16203
|
+
`;
|
|
16204
|
+
phoenixAdapter = {
|
|
16205
|
+
id: "phoenix",
|
|
16206
|
+
languages: ["elixir"],
|
|
16207
|
+
matches(signals) {
|
|
16208
|
+
if (!signals.mixExs) return false;
|
|
16209
|
+
return /\{\s*:phoenix\b(?!_)/.test(signals.mixExs);
|
|
16210
|
+
},
|
|
16211
|
+
async introspect(files, _rootDir) {
|
|
16212
|
+
if (files.length === 0) {
|
|
16213
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16214
|
+
}
|
|
16215
|
+
let language;
|
|
16216
|
+
try {
|
|
16217
|
+
language = await loadGrammar("elixir");
|
|
16218
|
+
} catch (e2) {
|
|
16219
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16220
|
+
}
|
|
16221
|
+
const parser = new Parser();
|
|
16222
|
+
parser.setLanguage(language);
|
|
16223
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16224
|
+
const scopePaths = /* @__PURE__ */ new Map();
|
|
16225
|
+
const routerModules = /* @__PURE__ */ new Map();
|
|
16226
|
+
try {
|
|
16227
|
+
for (const file of files) {
|
|
16228
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16229
|
+
if (skip) {
|
|
16230
|
+
process.stderr.write(
|
|
16231
|
+
`[massu/ast] WARN: phoenix skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16232
|
+
`
|
|
16233
|
+
);
|
|
16234
|
+
continue;
|
|
16235
|
+
}
|
|
16236
|
+
try {
|
|
16237
|
+
for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY3, "phoenix-route-method", file.path)) {
|
|
16238
|
+
const method = hit.captures.method;
|
|
16239
|
+
if (method && !routeMethods.has(method)) {
|
|
16240
|
+
routeMethods.set(method, { line: hit.line, file: file.path });
|
|
16241
|
+
}
|
|
16242
|
+
}
|
|
16243
|
+
for (const hit of runQuery(parser, file.content, SCOPE_PATH_QUERY, "phoenix-scope-path", file.path)) {
|
|
16244
|
+
const raw = hit.captures.scope_path;
|
|
16245
|
+
if (!raw) continue;
|
|
16246
|
+
const literal = raw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16247
|
+
const base = extractPrefixBase5(literal);
|
|
16248
|
+
if (base && !scopePaths.has(base)) {
|
|
16249
|
+
scopePaths.set(base, { line: hit.line, file: file.path });
|
|
16250
|
+
}
|
|
16251
|
+
}
|
|
16252
|
+
for (const hit of runQuery(parser, file.content, ROUTER_MODULE_QUERY, "phoenix-router-module", file.path)) {
|
|
16253
|
+
const name2 = hit.captures.module_name;
|
|
16254
|
+
if (name2 && !routerModules.has(name2)) {
|
|
16255
|
+
routerModules.set(name2, { line: hit.line, file: file.path });
|
|
16256
|
+
}
|
|
16257
|
+
}
|
|
16258
|
+
} catch (e2) {
|
|
16259
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16260
|
+
throw e2;
|
|
16261
|
+
}
|
|
16262
|
+
continue;
|
|
16263
|
+
}
|
|
16264
|
+
}
|
|
16265
|
+
} finally {
|
|
16266
|
+
try {
|
|
16267
|
+
parser.delete();
|
|
16268
|
+
} catch {
|
|
16269
|
+
}
|
|
16270
|
+
}
|
|
16271
|
+
const conventions = {};
|
|
16272
|
+
const provenance = [];
|
|
16273
|
+
if (routeMethods.size === 1) {
|
|
16274
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16275
|
+
conventions.route_method = name2;
|
|
16276
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "phoenix-route-method" });
|
|
16277
|
+
} else if (routeMethods.size >= 2) {
|
|
16278
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16279
|
+
conventions.route_method = name2;
|
|
16280
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "phoenix-route-method" });
|
|
16281
|
+
}
|
|
16282
|
+
if (scopePaths.size >= 1) {
|
|
16283
|
+
const [base, { line, file }] = scopePaths.entries().next().value;
|
|
16284
|
+
conventions.scope_prefix_base = base;
|
|
16285
|
+
provenance.push({ field: "scope_prefix_base", sourceFile: file, line, query: "phoenix-scope-path" });
|
|
16286
|
+
}
|
|
16287
|
+
if (routerModules.size >= 1) {
|
|
16288
|
+
const [name2, { line, file }] = routerModules.entries().next().value;
|
|
16289
|
+
conventions.router_module = name2;
|
|
16290
|
+
provenance.push({ field: "router_module", sourceFile: file, line, query: "phoenix-router-module" });
|
|
16291
|
+
}
|
|
16292
|
+
let confidence;
|
|
16293
|
+
if (Object.keys(conventions).length === 0) {
|
|
16294
|
+
confidence = "none";
|
|
16295
|
+
} else if (routeMethods.size === 1) {
|
|
16296
|
+
confidence = "high";
|
|
16297
|
+
} else if (routeMethods.size >= 2) {
|
|
16298
|
+
confidence = "low";
|
|
16299
|
+
} else {
|
|
16300
|
+
confidence = "medium";
|
|
16301
|
+
}
|
|
16302
|
+
return { conventions, provenance, confidence };
|
|
16303
|
+
}
|
|
16304
|
+
};
|
|
14614
16305
|
}
|
|
14615
16306
|
});
|
|
14616
16307
|
|
|
14617
|
-
// src/detect/adapters/
|
|
14618
|
-
|
|
14619
|
-
|
|
16308
|
+
// src/detect/adapters/aspnet.ts
|
|
16309
|
+
function extractPrefixBase6(prefix3) {
|
|
16310
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
16311
|
+
const firstSeg = stripped.split("/")[0];
|
|
16312
|
+
if (!firstSeg) return null;
|
|
16313
|
+
return "/" + firstSeg;
|
|
16314
|
+
}
|
|
16315
|
+
var MAP_VERB_QUERY, HTTP_ATTR_QUERY, ROUTE_ATTR_QUERY, CONTROLLER_CLASS_QUERY, aspnetAdapter;
|
|
16316
|
+
var init_aspnet = __esm({
|
|
16317
|
+
"src/detect/adapters/aspnet.ts"() {
|
|
14620
16318
|
"use strict";
|
|
14621
16319
|
init_tree_sitter();
|
|
14622
16320
|
init_query_helpers();
|
|
14623
16321
|
init_tree_sitter_loader();
|
|
14624
16322
|
init_parse_guard();
|
|
16323
|
+
MAP_VERB_QUERY = `
|
|
16324
|
+
(invocation_expression
|
|
16325
|
+
function: (member_access_expression
|
|
16326
|
+
name: (identifier) @method (#match? @method "^Map(Get|Post|Put|Patch|Delete|Head|Options)$"))
|
|
16327
|
+
arguments: (argument_list
|
|
16328
|
+
.
|
|
16329
|
+
(argument (string_literal) @route_path)))
|
|
16330
|
+
`;
|
|
16331
|
+
HTTP_ATTR_QUERY = `
|
|
16332
|
+
(attribute
|
|
16333
|
+
name: (identifier) @attr_name (#match? @attr_name "^Http(Get|Post|Put|Patch|Delete|Head|Options)$"))
|
|
16334
|
+
`;
|
|
16335
|
+
ROUTE_ATTR_QUERY = `
|
|
16336
|
+
(attribute
|
|
16337
|
+
name: (identifier) @_attr_name (#eq? @_attr_name "Route")
|
|
16338
|
+
(attribute_argument_list
|
|
16339
|
+
(attribute_argument (string_literal) @route_template)))
|
|
16340
|
+
`;
|
|
16341
|
+
CONTROLLER_CLASS_QUERY = `
|
|
16342
|
+
(class_declaration
|
|
16343
|
+
name: (identifier) @class_name (#match? @class_name "Controller$"))
|
|
16344
|
+
`;
|
|
16345
|
+
aspnetAdapter = {
|
|
16346
|
+
id: "aspnet",
|
|
16347
|
+
languages: ["csharp"],
|
|
16348
|
+
matches(signals) {
|
|
16349
|
+
if (!signals.csproj) return false;
|
|
16350
|
+
if (/Sdk\s*=\s*["']Microsoft\.NET\.Sdk\.Web["']/i.test(signals.csproj)) return true;
|
|
16351
|
+
if (/Microsoft\.AspNetCore\.App/i.test(signals.csproj)) return true;
|
|
16352
|
+
return false;
|
|
16353
|
+
},
|
|
16354
|
+
async introspect(files, _rootDir) {
|
|
16355
|
+
if (files.length === 0) {
|
|
16356
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16357
|
+
}
|
|
16358
|
+
let language;
|
|
16359
|
+
try {
|
|
16360
|
+
language = await loadGrammar("csharp");
|
|
16361
|
+
} catch (e2) {
|
|
16362
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16363
|
+
}
|
|
16364
|
+
const parser = new Parser();
|
|
16365
|
+
parser.setLanguage(language);
|
|
16366
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16367
|
+
const prefixBases = /* @__PURE__ */ new Map();
|
|
16368
|
+
const controllerClasses = /* @__PURE__ */ new Map();
|
|
16369
|
+
try {
|
|
16370
|
+
for (const file of files) {
|
|
16371
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16372
|
+
if (skip) {
|
|
16373
|
+
process.stderr.write(
|
|
16374
|
+
`[massu/ast] WARN: aspnet skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16375
|
+
`
|
|
16376
|
+
);
|
|
16377
|
+
continue;
|
|
16378
|
+
}
|
|
16379
|
+
try {
|
|
16380
|
+
for (const hit of runQuery(parser, file.content, MAP_VERB_QUERY, "aspnet-map-verb", file.path)) {
|
|
16381
|
+
const methodRaw = hit.captures.method;
|
|
16382
|
+
if (!methodRaw) continue;
|
|
16383
|
+
const verb = methodRaw.replace(/^Map/, "");
|
|
16384
|
+
if (!routeMethods.has(verb)) {
|
|
16385
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16386
|
+
}
|
|
16387
|
+
const pathRaw = hit.captures.route_path;
|
|
16388
|
+
if (pathRaw) {
|
|
16389
|
+
const literal = pathRaw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16390
|
+
const base = extractPrefixBase6(literal);
|
|
16391
|
+
if (base && !prefixBases.has(base)) {
|
|
16392
|
+
prefixBases.set(base, { line: hit.line, file: file.path });
|
|
16393
|
+
}
|
|
16394
|
+
}
|
|
16395
|
+
}
|
|
16396
|
+
for (const hit of runQuery(parser, file.content, HTTP_ATTR_QUERY, "aspnet-http-attr", file.path)) {
|
|
16397
|
+
const attrRaw = hit.captures.attr_name;
|
|
16398
|
+
if (!attrRaw) continue;
|
|
16399
|
+
const verb = attrRaw.replace(/^Http/, "");
|
|
16400
|
+
if (!routeMethods.has(verb)) {
|
|
16401
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16402
|
+
}
|
|
16403
|
+
}
|
|
16404
|
+
for (const hit of runQuery(parser, file.content, ROUTE_ATTR_QUERY, "aspnet-route-attr", file.path)) {
|
|
16405
|
+
const tplRaw = hit.captures.route_template;
|
|
16406
|
+
if (!tplRaw) continue;
|
|
16407
|
+
const literal = tplRaw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16408
|
+
const base = extractPrefixBase6(literal);
|
|
16409
|
+
if (base && !prefixBases.has(base)) {
|
|
16410
|
+
prefixBases.set(base, { line: hit.line, file: file.path });
|
|
16411
|
+
}
|
|
16412
|
+
}
|
|
16413
|
+
for (const hit of runQuery(parser, file.content, CONTROLLER_CLASS_QUERY, "aspnet-controller-class", file.path)) {
|
|
16414
|
+
const name2 = hit.captures.class_name;
|
|
16415
|
+
if (name2 && !controllerClasses.has(name2)) {
|
|
16416
|
+
controllerClasses.set(name2, { line: hit.line, file: file.path });
|
|
16417
|
+
}
|
|
16418
|
+
}
|
|
16419
|
+
} catch (e2) {
|
|
16420
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16421
|
+
throw e2;
|
|
16422
|
+
}
|
|
16423
|
+
continue;
|
|
16424
|
+
}
|
|
16425
|
+
}
|
|
16426
|
+
} finally {
|
|
16427
|
+
try {
|
|
16428
|
+
parser.delete();
|
|
16429
|
+
} catch {
|
|
16430
|
+
}
|
|
16431
|
+
}
|
|
16432
|
+
const conventions = {};
|
|
16433
|
+
const provenance = [];
|
|
16434
|
+
if (routeMethods.size === 1) {
|
|
16435
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16436
|
+
conventions.route_method = name2;
|
|
16437
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "aspnet-map-verb" });
|
|
16438
|
+
} else if (routeMethods.size >= 2) {
|
|
16439
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16440
|
+
conventions.route_method = name2;
|
|
16441
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "aspnet-map-verb" });
|
|
16442
|
+
}
|
|
16443
|
+
if (prefixBases.size >= 1) {
|
|
16444
|
+
const [base, { line, file }] = prefixBases.entries().next().value;
|
|
16445
|
+
conventions.route_prefix_base = base;
|
|
16446
|
+
provenance.push({ field: "route_prefix_base", sourceFile: file, line, query: "aspnet-route-prefix" });
|
|
16447
|
+
}
|
|
16448
|
+
if (controllerClasses.size >= 1) {
|
|
16449
|
+
const [name2, { line, file }] = controllerClasses.entries().next().value;
|
|
16450
|
+
conventions.controller_class = name2;
|
|
16451
|
+
provenance.push({ field: "controller_class", sourceFile: file, line, query: "aspnet-controller-class" });
|
|
16452
|
+
}
|
|
16453
|
+
let confidence;
|
|
16454
|
+
if (Object.keys(conventions).length === 0) {
|
|
16455
|
+
confidence = "none";
|
|
16456
|
+
} else if (routeMethods.size === 1) {
|
|
16457
|
+
confidence = "high";
|
|
16458
|
+
} else if (routeMethods.size >= 2) {
|
|
16459
|
+
confidence = "low";
|
|
16460
|
+
} else {
|
|
16461
|
+
confidence = "medium";
|
|
16462
|
+
}
|
|
16463
|
+
return { conventions, provenance, confidence };
|
|
16464
|
+
}
|
|
16465
|
+
};
|
|
14625
16466
|
}
|
|
14626
16467
|
});
|
|
14627
16468
|
|
|
14628
|
-
// src/detect/adapters/
|
|
14629
|
-
|
|
14630
|
-
|
|
16469
|
+
// src/detect/adapters/spring.ts
|
|
16470
|
+
function extractPrefixBase7(prefix3) {
|
|
16471
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
16472
|
+
const firstSeg = stripped.split("/")[0];
|
|
16473
|
+
if (!firstSeg) return null;
|
|
16474
|
+
return "/" + firstSeg;
|
|
16475
|
+
}
|
|
16476
|
+
var HTTP_MAPPING_QUERY, HTTP_MAPPING_NO_ARGS_QUERY, REQUEST_MAPPING_QUERY, CONTROLLER_CLASS_QUERY2, springAdapter;
|
|
16477
|
+
var init_spring = __esm({
|
|
16478
|
+
"src/detect/adapters/spring.ts"() {
|
|
14631
16479
|
"use strict";
|
|
14632
16480
|
init_tree_sitter();
|
|
14633
16481
|
init_query_helpers();
|
|
14634
16482
|
init_tree_sitter_loader();
|
|
14635
16483
|
init_parse_guard();
|
|
16484
|
+
HTTP_MAPPING_QUERY = `
|
|
16485
|
+
(annotation
|
|
16486
|
+
name: (identifier) @method (#match? @method "^(Get|Post|Put|Patch|Delete|Head|Options)Mapping$")
|
|
16487
|
+
arguments: (annotation_argument_list
|
|
16488
|
+
(string_literal) @route_path))
|
|
16489
|
+
`;
|
|
16490
|
+
HTTP_MAPPING_NO_ARGS_QUERY = `
|
|
16491
|
+
(marker_annotation
|
|
16492
|
+
name: (identifier) @method (#match? @method "^(Get|Post|Put|Patch|Delete|Head|Options)Mapping$"))
|
|
16493
|
+
`;
|
|
16494
|
+
REQUEST_MAPPING_QUERY = `
|
|
16495
|
+
(annotation
|
|
16496
|
+
name: (identifier) @_name (#eq? @_name "RequestMapping")
|
|
16497
|
+
arguments: (annotation_argument_list
|
|
16498
|
+
(string_literal) @route_template))
|
|
16499
|
+
`;
|
|
16500
|
+
CONTROLLER_CLASS_QUERY2 = `
|
|
16501
|
+
(class_declaration
|
|
16502
|
+
(modifiers
|
|
16503
|
+
(marker_annotation
|
|
16504
|
+
name: (identifier) @_anno (#match? @_anno "^(RestController|Controller)$")))
|
|
16505
|
+
name: (identifier) @class_name)
|
|
16506
|
+
|
|
16507
|
+
(class_declaration
|
|
16508
|
+
(modifiers
|
|
16509
|
+
(annotation
|
|
16510
|
+
name: (identifier) @_anno (#match? @_anno "^(RestController|Controller)$")))
|
|
16511
|
+
name: (identifier) @class_name)
|
|
16512
|
+
`;
|
|
16513
|
+
springAdapter = {
|
|
16514
|
+
id: "spring",
|
|
16515
|
+
languages: ["java"],
|
|
16516
|
+
matches(signals) {
|
|
16517
|
+
if (signals.pomXml && /\bspring-boot-starter[\w-]*\b/.test(signals.pomXml)) {
|
|
16518
|
+
return true;
|
|
16519
|
+
}
|
|
16520
|
+
if (signals.gradleBuild && /\bspring-boot-starter[\w-]*\b/.test(signals.gradleBuild)) {
|
|
16521
|
+
return true;
|
|
16522
|
+
}
|
|
16523
|
+
if (signals.pomXml && /\borg\.springframework\b/.test(signals.pomXml)) {
|
|
16524
|
+
return true;
|
|
16525
|
+
}
|
|
16526
|
+
if (signals.gradleBuild && /\borg\.springframework\b/.test(signals.gradleBuild)) {
|
|
16527
|
+
return true;
|
|
16528
|
+
}
|
|
16529
|
+
return false;
|
|
16530
|
+
},
|
|
16531
|
+
async introspect(files, _rootDir) {
|
|
16532
|
+
if (files.length === 0) {
|
|
16533
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16534
|
+
}
|
|
16535
|
+
let language;
|
|
16536
|
+
try {
|
|
16537
|
+
language = await loadGrammar("java");
|
|
16538
|
+
} catch (e2) {
|
|
16539
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16540
|
+
}
|
|
16541
|
+
const parser = new Parser();
|
|
16542
|
+
parser.setLanguage(language);
|
|
16543
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16544
|
+
const prefixBases = /* @__PURE__ */ new Map();
|
|
16545
|
+
const controllerClasses = /* @__PURE__ */ new Map();
|
|
16546
|
+
try {
|
|
16547
|
+
for (const file of files) {
|
|
16548
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16549
|
+
if (skip) {
|
|
16550
|
+
process.stderr.write(
|
|
16551
|
+
`[massu/ast] WARN: spring skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16552
|
+
`
|
|
16553
|
+
);
|
|
16554
|
+
continue;
|
|
16555
|
+
}
|
|
16556
|
+
try {
|
|
16557
|
+
for (const hit of runQuery(parser, file.content, HTTP_MAPPING_QUERY, "spring-http-mapping", file.path)) {
|
|
16558
|
+
const methodRaw = hit.captures.method;
|
|
16559
|
+
if (!methodRaw) continue;
|
|
16560
|
+
const verb = methodRaw.replace(/Mapping$/, "");
|
|
16561
|
+
if (!routeMethods.has(verb)) {
|
|
16562
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16563
|
+
}
|
|
16564
|
+
}
|
|
16565
|
+
for (const hit of runQuery(parser, file.content, HTTP_MAPPING_NO_ARGS_QUERY, "spring-http-mapping-marker", file.path)) {
|
|
16566
|
+
const methodRaw = hit.captures.method;
|
|
16567
|
+
if (!methodRaw) continue;
|
|
16568
|
+
const verb = methodRaw.replace(/Mapping$/, "");
|
|
16569
|
+
if (!routeMethods.has(verb)) {
|
|
16570
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16571
|
+
}
|
|
16572
|
+
}
|
|
16573
|
+
for (const hit of runQuery(parser, file.content, REQUEST_MAPPING_QUERY, "spring-request-mapping", file.path)) {
|
|
16574
|
+
const tplRaw = hit.captures.route_template;
|
|
16575
|
+
if (!tplRaw) continue;
|
|
16576
|
+
const literal = tplRaw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16577
|
+
const base = extractPrefixBase7(literal);
|
|
16578
|
+
if (base && !prefixBases.has(base)) {
|
|
16579
|
+
prefixBases.set(base, { line: hit.line, file: file.path });
|
|
16580
|
+
}
|
|
16581
|
+
}
|
|
16582
|
+
for (const hit of runQuery(parser, file.content, CONTROLLER_CLASS_QUERY2, "spring-controller-class", file.path)) {
|
|
16583
|
+
const name2 = hit.captures.class_name;
|
|
16584
|
+
if (name2 && !controllerClasses.has(name2)) {
|
|
16585
|
+
controllerClasses.set(name2, { line: hit.line, file: file.path });
|
|
16586
|
+
}
|
|
16587
|
+
}
|
|
16588
|
+
} catch (e2) {
|
|
16589
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16590
|
+
throw e2;
|
|
16591
|
+
}
|
|
16592
|
+
continue;
|
|
16593
|
+
}
|
|
16594
|
+
}
|
|
16595
|
+
} finally {
|
|
16596
|
+
try {
|
|
16597
|
+
parser.delete();
|
|
16598
|
+
} catch {
|
|
16599
|
+
}
|
|
16600
|
+
}
|
|
16601
|
+
const conventions = {};
|
|
16602
|
+
const provenance = [];
|
|
16603
|
+
if (routeMethods.size === 1) {
|
|
16604
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16605
|
+
conventions.route_method = name2;
|
|
16606
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "spring-http-mapping" });
|
|
16607
|
+
} else if (routeMethods.size >= 2) {
|
|
16608
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16609
|
+
conventions.route_method = name2;
|
|
16610
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "spring-http-mapping" });
|
|
16611
|
+
}
|
|
16612
|
+
if (prefixBases.size >= 1) {
|
|
16613
|
+
const [base, { line, file }] = prefixBases.entries().next().value;
|
|
16614
|
+
conventions.route_prefix_base = base;
|
|
16615
|
+
provenance.push({ field: "route_prefix_base", sourceFile: file, line, query: "spring-request-mapping" });
|
|
16616
|
+
}
|
|
16617
|
+
if (controllerClasses.size >= 1) {
|
|
16618
|
+
const [name2, { line, file }] = controllerClasses.entries().next().value;
|
|
16619
|
+
conventions.controller_class = name2;
|
|
16620
|
+
provenance.push({ field: "controller_class", sourceFile: file, line, query: "spring-controller-class" });
|
|
16621
|
+
}
|
|
16622
|
+
let confidence;
|
|
16623
|
+
if (Object.keys(conventions).length === 0) {
|
|
16624
|
+
confidence = "none";
|
|
16625
|
+
} else if (routeMethods.size === 1) {
|
|
16626
|
+
confidence = "high";
|
|
16627
|
+
} else if (routeMethods.size >= 2) {
|
|
16628
|
+
confidence = "low";
|
|
16629
|
+
} else {
|
|
16630
|
+
confidence = "medium";
|
|
16631
|
+
}
|
|
16632
|
+
return { conventions, provenance, confidence };
|
|
16633
|
+
}
|
|
16634
|
+
};
|
|
14636
16635
|
}
|
|
14637
16636
|
});
|
|
14638
16637
|
|
|
14639
|
-
// src/detect/adapters/
|
|
14640
|
-
var
|
|
14641
|
-
|
|
16638
|
+
// src/detect/adapters/file-sampler.ts
|
|
16639
|
+
var file_sampler_exports = {};
|
|
16640
|
+
__export(file_sampler_exports, {
|
|
16641
|
+
SAMPLE_EXTENSIONS: () => SAMPLE_EXTENSIONS,
|
|
16642
|
+
SAMPLE_TEST_FILE_PATTERNS: () => SAMPLE_TEST_FILE_PATTERNS,
|
|
16643
|
+
sampleFilesForAdapter: () => sampleFilesForAdapter
|
|
16644
|
+
});
|
|
16645
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync9, lstatSync as lstatSync4 } from "node:fs";
|
|
16646
|
+
import { join as join9, extname } from "node:path";
|
|
16647
|
+
function sampleFilesForAdapter(adapter, projectRoot, detection, options = {}) {
|
|
16648
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
16649
|
+
const maxFiles = options.maxFilesPerAdapter ?? DEFAULT_MAX_FILES;
|
|
16650
|
+
const out2 = [];
|
|
16651
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16652
|
+
for (const lang of adapter.languages) {
|
|
16653
|
+
if (out2.length >= maxFiles) break;
|
|
16654
|
+
const exts = SAMPLE_EXTENSIONS[lang];
|
|
16655
|
+
if (!exts || exts.length === 0) continue;
|
|
16656
|
+
const testPatterns = SAMPLE_TEST_FILE_PATTERNS[lang] ?? [];
|
|
16657
|
+
const langKey = lang;
|
|
16658
|
+
const langDetection = detection.sourceDirs[langKey];
|
|
16659
|
+
const candidateDirs = [];
|
|
16660
|
+
if (langDetection?.source_dirs && langDetection.source_dirs.length > 0) {
|
|
16661
|
+
candidateDirs.push(...langDetection.source_dirs.map((d2) => join9(projectRoot, d2)));
|
|
16662
|
+
} else {
|
|
16663
|
+
candidateDirs.push(projectRoot);
|
|
16664
|
+
}
|
|
16665
|
+
for (const dir of candidateDirs) {
|
|
16666
|
+
if (out2.length >= maxFiles) break;
|
|
16667
|
+
walkDir(dir, exts, testPatterns, lang, maxDepth, 0, out2, seen, maxFiles);
|
|
16668
|
+
}
|
|
16669
|
+
}
|
|
16670
|
+
return out2;
|
|
16671
|
+
}
|
|
16672
|
+
function walkDir(dir, exts, testPatterns, lang, maxDepth, curDepth, out2, seen, maxFiles) {
|
|
16673
|
+
if (curDepth > maxDepth) return;
|
|
16674
|
+
if (out2.length >= maxFiles) return;
|
|
16675
|
+
let entries;
|
|
16676
|
+
try {
|
|
16677
|
+
entries = readdirSync9(dir);
|
|
16678
|
+
} catch {
|
|
16679
|
+
return;
|
|
16680
|
+
}
|
|
16681
|
+
for (const entry of entries) {
|
|
16682
|
+
if (out2.length >= maxFiles) return;
|
|
16683
|
+
if (entry.startsWith(".")) continue;
|
|
16684
|
+
if (IGNORED_DIRS3.has(entry)) continue;
|
|
16685
|
+
const fullPath = join9(dir, entry);
|
|
16686
|
+
let st;
|
|
16687
|
+
try {
|
|
16688
|
+
st = lstatSync4(fullPath);
|
|
16689
|
+
} catch {
|
|
16690
|
+
continue;
|
|
16691
|
+
}
|
|
16692
|
+
if (st.isSymbolicLink()) continue;
|
|
16693
|
+
if (st.isDirectory()) {
|
|
16694
|
+
walkDir(fullPath, exts, testPatterns, lang, maxDepth, curDepth + 1, out2, seen, maxFiles);
|
|
16695
|
+
continue;
|
|
16696
|
+
}
|
|
16697
|
+
if (!st.isFile()) continue;
|
|
16698
|
+
if (st.size > MAX_AST_FILE_BYTES) continue;
|
|
16699
|
+
const ext = extname(entry).slice(1);
|
|
16700
|
+
if (!exts.includes(ext)) continue;
|
|
16701
|
+
if (testPatterns.some((p19) => p19.test(fullPath))) continue;
|
|
16702
|
+
if (seen.has(fullPath)) continue;
|
|
16703
|
+
seen.add(fullPath);
|
|
16704
|
+
let content;
|
|
16705
|
+
try {
|
|
16706
|
+
content = readFileSync9(fullPath, "utf-8");
|
|
16707
|
+
} catch {
|
|
16708
|
+
continue;
|
|
16709
|
+
}
|
|
16710
|
+
out2.push({
|
|
16711
|
+
path: fullPath,
|
|
16712
|
+
content,
|
|
16713
|
+
language: lang,
|
|
16714
|
+
size: st.size
|
|
16715
|
+
});
|
|
16716
|
+
}
|
|
16717
|
+
}
|
|
16718
|
+
var SAMPLE_EXTENSIONS, SAMPLE_TEST_FILE_PATTERNS, IGNORED_DIRS3, DEFAULT_MAX_DEPTH, DEFAULT_MAX_FILES;
|
|
16719
|
+
var init_file_sampler = __esm({
|
|
16720
|
+
"src/detect/adapters/file-sampler.ts"() {
|
|
14642
16721
|
"use strict";
|
|
14643
|
-
init_tree_sitter();
|
|
14644
|
-
init_query_helpers();
|
|
14645
|
-
init_tree_sitter_loader();
|
|
14646
16722
|
init_parse_guard();
|
|
16723
|
+
SAMPLE_EXTENSIONS = {
|
|
16724
|
+
python: ["py"],
|
|
16725
|
+
typescript: ["ts", "tsx"],
|
|
16726
|
+
javascript: ["js", "jsx", "mjs", "cjs"],
|
|
16727
|
+
swift: ["swift"],
|
|
16728
|
+
rust: ["rs"],
|
|
16729
|
+
go: ["go"],
|
|
16730
|
+
ruby: ["rb"],
|
|
16731
|
+
php: ["php"],
|
|
16732
|
+
java: ["java", "kt"],
|
|
16733
|
+
kotlin: ["kt", "kts"],
|
|
16734
|
+
elixir: ["ex", "exs"],
|
|
16735
|
+
erlang: ["erl", "hrl"],
|
|
16736
|
+
csharp: ["cs"],
|
|
16737
|
+
cpp: ["cpp", "cc", "cxx", "h", "hpp"],
|
|
16738
|
+
haskell: ["hs", "lhs"],
|
|
16739
|
+
ocaml: ["ml", "mli"]
|
|
16740
|
+
};
|
|
16741
|
+
SAMPLE_TEST_FILE_PATTERNS = {
|
|
16742
|
+
python: [/_test\.py$/, /test_[^/]*\.py$/, /\/tests?\//],
|
|
16743
|
+
typescript: [/\.test\.tsx?$/, /\.spec\.tsx?$/, /\/__tests__\//],
|
|
16744
|
+
javascript: [/\.test\.[mc]?jsx?$/, /\.spec\.[mc]?jsx?$/, /\/__tests__\//],
|
|
16745
|
+
swift: [/Tests\//],
|
|
16746
|
+
rust: [/tests\/.*\.rs$/],
|
|
16747
|
+
go: [/_test\.go$/],
|
|
16748
|
+
ruby: [/_spec\.rb$/, /_test\.rb$/, /\/spec\//],
|
|
16749
|
+
php: [/Test\.php$/, /\/tests?\//i],
|
|
16750
|
+
java: [/Test[^/]*\.(java|kt)$/, /[^/]*Test\.(java|kt)$/, /\/test\//],
|
|
16751
|
+
kotlin: [/Test[^/]*\.kt$/, /[^/]*Test\.kt$/],
|
|
16752
|
+
elixir: [/_test\.exs$/, /\/test\//],
|
|
16753
|
+
erlang: [/_SUITE\.erl$/],
|
|
16754
|
+
csharp: [/Tests?\.cs$/, /\.Tests?\//],
|
|
16755
|
+
cpp: [/_test\.(cpp|cc)$/i, /\/tests?\//i],
|
|
16756
|
+
haskell: [/Spec\.hs$/, /\/test\//],
|
|
16757
|
+
ocaml: [/_test\.ml$/, /\/test\//]
|
|
16758
|
+
};
|
|
16759
|
+
IGNORED_DIRS3 = /* @__PURE__ */ new Set([
|
|
16760
|
+
"node_modules",
|
|
16761
|
+
".venv",
|
|
16762
|
+
"venv",
|
|
16763
|
+
"__pycache__",
|
|
16764
|
+
"dist",
|
|
16765
|
+
"build",
|
|
16766
|
+
".build",
|
|
16767
|
+
"target",
|
|
16768
|
+
".next",
|
|
16769
|
+
".nuxt",
|
|
16770
|
+
"coverage",
|
|
16771
|
+
".git",
|
|
16772
|
+
".massu",
|
|
16773
|
+
".turbo",
|
|
16774
|
+
".cache",
|
|
16775
|
+
".pytest_cache",
|
|
16776
|
+
".mypy_cache",
|
|
16777
|
+
"DerivedData",
|
|
16778
|
+
"Pods",
|
|
16779
|
+
"_build",
|
|
16780
|
+
"deps",
|
|
16781
|
+
"priv",
|
|
16782
|
+
"cover",
|
|
16783
|
+
".elixir_ls",
|
|
16784
|
+
// Elixir/Phoenix
|
|
16785
|
+
"bin",
|
|
16786
|
+
"obj",
|
|
16787
|
+
".vs",
|
|
16788
|
+
"packages",
|
|
16789
|
+
"publish",
|
|
16790
|
+
"TestResults"
|
|
16791
|
+
// .NET
|
|
16792
|
+
]);
|
|
16793
|
+
DEFAULT_MAX_DEPTH = 3;
|
|
16794
|
+
DEFAULT_MAX_FILES = 50;
|
|
14647
16795
|
}
|
|
14648
16796
|
});
|
|
14649
16797
|
|
|
14650
16798
|
// src/detect/codebase-introspector.ts
|
|
16799
|
+
var codebase_introspector_exports = {};
|
|
16800
|
+
__export(codebase_introspector_exports, {
|
|
16801
|
+
introspect: () => introspect,
|
|
16802
|
+
introspectAsync: () => introspectAsync
|
|
16803
|
+
});
|
|
14651
16804
|
function introspect(detection, projectRoot) {
|
|
14652
16805
|
const out2 = {};
|
|
14653
16806
|
const languages = Array.from(
|
|
@@ -14667,6 +16820,35 @@ function introspect(detection, projectRoot) {
|
|
|
14667
16820
|
}
|
|
14668
16821
|
return out2;
|
|
14669
16822
|
}
|
|
16823
|
+
async function introspectAsync(detection, projectRoot) {
|
|
16824
|
+
const out2 = introspect(detection, projectRoot);
|
|
16825
|
+
const signals = buildDetectionSignals(projectRoot);
|
|
16826
|
+
const { sampleFilesForAdapter: sampleFilesForAdapter2 } = await Promise.resolve().then(() => (init_file_sampler(), file_sampler_exports));
|
|
16827
|
+
let merged;
|
|
16828
|
+
try {
|
|
16829
|
+
merged = await runAdapters(FIRST_PARTY_ADAPTERS, projectRoot, signals, {
|
|
16830
|
+
sampleFiles: async (adapter, root) => {
|
|
16831
|
+
return sampleFilesForAdapter2(adapter, root, detection);
|
|
16832
|
+
}
|
|
16833
|
+
});
|
|
16834
|
+
} catch {
|
|
16835
|
+
return out2;
|
|
16836
|
+
}
|
|
16837
|
+
for (const [adapterId, resolved] of Object.entries(merged.byAdapter)) {
|
|
16838
|
+
if (resolved.confidence === "none") continue;
|
|
16839
|
+
out2[adapterId] = serializeAdapterBlock(resolved);
|
|
16840
|
+
}
|
|
16841
|
+
return out2;
|
|
16842
|
+
}
|
|
16843
|
+
function serializeAdapterBlock(r2) {
|
|
16844
|
+
const block = { ...r2.conventions };
|
|
16845
|
+
if (Object.keys(r2._provenance).length > 0) {
|
|
16846
|
+
block._provenance = r2._provenance;
|
|
16847
|
+
}
|
|
16848
|
+
block._confidence = r2.confidence;
|
|
16849
|
+
return block;
|
|
16850
|
+
}
|
|
16851
|
+
var FIRST_PARTY_ADAPTERS;
|
|
14670
16852
|
var init_codebase_introspector = __esm({
|
|
14671
16853
|
"src/detect/codebase-introspector.ts"() {
|
|
14672
16854
|
"use strict";
|
|
@@ -14676,6 +16858,24 @@ var init_codebase_introspector = __esm({
|
|
|
14676
16858
|
init_python_django();
|
|
14677
16859
|
init_nextjs_trpc();
|
|
14678
16860
|
init_swift_swiftui();
|
|
16861
|
+
init_python_flask();
|
|
16862
|
+
init_go_chi();
|
|
16863
|
+
init_rails();
|
|
16864
|
+
init_phoenix();
|
|
16865
|
+
init_aspnet();
|
|
16866
|
+
init_spring();
|
|
16867
|
+
FIRST_PARTY_ADAPTERS = [
|
|
16868
|
+
pythonFastApiAdapter,
|
|
16869
|
+
pythonDjangoAdapter,
|
|
16870
|
+
pythonFlaskAdapter,
|
|
16871
|
+
nextjsTrpcAdapter,
|
|
16872
|
+
swiftSwiftUiAdapter,
|
|
16873
|
+
goChiAdapter,
|
|
16874
|
+
railsAdapter,
|
|
16875
|
+
phoenixAdapter,
|
|
16876
|
+
aspnetAdapter,
|
|
16877
|
+
springAdapter
|
|
16878
|
+
];
|
|
14679
16879
|
}
|
|
14680
16880
|
});
|
|
14681
16881
|
|
|
@@ -14741,7 +16941,7 @@ var init_detect = __esm({
|
|
|
14741
16941
|
});
|
|
14742
16942
|
|
|
14743
16943
|
// src/detect/drift.ts
|
|
14744
|
-
import { createHash as
|
|
16944
|
+
import { createHash as createHash3 } from "crypto";
|
|
14745
16945
|
function summarizeDetection(det) {
|
|
14746
16946
|
const languages = Array.from(new Set(det.manifests.map((m3) => m3.language))).sort();
|
|
14747
16947
|
const frameworks = {};
|
|
@@ -14772,7 +16972,7 @@ function summarizeDetection(det) {
|
|
|
14772
16972
|
function computeFingerprint(det) {
|
|
14773
16973
|
const data = summarizeDetection(det);
|
|
14774
16974
|
const stable = JSON.stringify(data, Object.keys(data).sort());
|
|
14775
|
-
return
|
|
16975
|
+
return createHash3("sha256").update(stable).digest("hex");
|
|
14776
16976
|
}
|
|
14777
16977
|
function stringOf(v3) {
|
|
14778
16978
|
if (typeof v3 === "string") return v3;
|
|
@@ -15931,10 +18131,10 @@ __export(init_exports, {
|
|
|
15931
18131
|
validateWrittenConfig: () => validateWrittenConfig,
|
|
15932
18132
|
writeConfigAtomic: () => writeConfigAtomic
|
|
15933
18133
|
});
|
|
15934
|
-
import { closeSync as closeSync2, existsSync as
|
|
15935
|
-
import { resolve as resolve6, basename as
|
|
18134
|
+
import { closeSync as closeSync2, existsSync as existsSync10, fsyncSync as fsyncSync2, openSync as openSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync2, writeSync as writeSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync10, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync7, chmodSync as chmodSync2 } from "fs";
|
|
18135
|
+
import { resolve as resolve6, basename as basename4, dirname as dirname5 } from "path";
|
|
15936
18136
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15937
|
-
import { homedir as
|
|
18137
|
+
import { homedir as homedir3 } from "os";
|
|
15938
18138
|
import { stringify as yamlStringify, parse as yamlParse } from "yaml";
|
|
15939
18139
|
function detectFramework(projectRoot) {
|
|
15940
18140
|
const result = {
|
|
@@ -15944,9 +18144,9 @@ function detectFramework(projectRoot) {
|
|
|
15944
18144
|
ui: "none"
|
|
15945
18145
|
};
|
|
15946
18146
|
const pkgPath = resolve6(projectRoot, "package.json");
|
|
15947
|
-
if (!
|
|
18147
|
+
if (!existsSync10(pkgPath)) return result;
|
|
15948
18148
|
try {
|
|
15949
|
-
const pkg = JSON.parse(
|
|
18149
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
15950
18150
|
const allDeps = {
|
|
15951
18151
|
...pkg.dependencies,
|
|
15952
18152
|
...pkg.devDependencies
|
|
@@ -15980,7 +18180,7 @@ function detectPython(projectRoot) {
|
|
|
15980
18180
|
alembicDir: null
|
|
15981
18181
|
};
|
|
15982
18182
|
const markers = ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"];
|
|
15983
|
-
const hasMarker = markers.some((m3) =>
|
|
18183
|
+
const hasMarker = markers.some((m3) => existsSync10(resolve6(projectRoot, m3)));
|
|
15984
18184
|
if (!hasMarker) return result;
|
|
15985
18185
|
result.detected = true;
|
|
15986
18186
|
const depFiles = [
|
|
@@ -15991,34 +18191,34 @@ function detectPython(projectRoot) {
|
|
|
15991
18191
|
];
|
|
15992
18192
|
for (const { file } of depFiles) {
|
|
15993
18193
|
const filePath = resolve6(projectRoot, file);
|
|
15994
|
-
if (
|
|
18194
|
+
if (existsSync10(filePath)) {
|
|
15995
18195
|
try {
|
|
15996
|
-
const content =
|
|
18196
|
+
const content = readFileSync10(filePath, "utf-8").toLowerCase();
|
|
15997
18197
|
if (content.includes("fastapi")) result.hasFastapi = true;
|
|
15998
18198
|
if (content.includes("sqlalchemy")) result.hasSqlalchemy = true;
|
|
15999
18199
|
} catch {
|
|
16000
18200
|
}
|
|
16001
18201
|
}
|
|
16002
18202
|
}
|
|
16003
|
-
if (
|
|
18203
|
+
if (existsSync10(resolve6(projectRoot, "alembic.ini"))) {
|
|
16004
18204
|
result.hasAlembic = true;
|
|
16005
|
-
if (
|
|
18205
|
+
if (existsSync10(resolve6(projectRoot, "alembic"))) {
|
|
16006
18206
|
result.alembicDir = "alembic";
|
|
16007
18207
|
}
|
|
16008
|
-
} else if (
|
|
18208
|
+
} else if (existsSync10(resolve6(projectRoot, "alembic"))) {
|
|
16009
18209
|
result.hasAlembic = true;
|
|
16010
18210
|
result.alembicDir = "alembic";
|
|
16011
18211
|
}
|
|
16012
18212
|
const candidateRoots = ["app", "src", "backend", "api"];
|
|
16013
18213
|
for (const candidate of candidateRoots) {
|
|
16014
18214
|
const candidatePath = resolve6(projectRoot, candidate);
|
|
16015
|
-
if (
|
|
18215
|
+
if (existsSync10(candidatePath) && existsSync10(resolve6(candidatePath, "__init__.py"))) {
|
|
16016
18216
|
result.root = candidate;
|
|
16017
18217
|
break;
|
|
16018
18218
|
}
|
|
16019
|
-
if (
|
|
18219
|
+
if (existsSync10(candidatePath)) {
|
|
16020
18220
|
try {
|
|
16021
|
-
const files =
|
|
18221
|
+
const files = readdirSync10(candidatePath);
|
|
16022
18222
|
if (files.some((f2) => f2.endsWith(".py"))) {
|
|
16023
18223
|
result.root = candidate;
|
|
16024
18224
|
break;
|
|
@@ -16037,10 +18237,10 @@ function generateConfig(projectRoot, framework) {
|
|
|
16037
18237
|
"[@massu/core] generateConfig() is deprecated since 1.2.1 \u2014 use buildConfigFromDetection instead. It cannot produce valid configs for monorepos."
|
|
16038
18238
|
);
|
|
16039
18239
|
const configPath = resolve6(projectRoot, "massu.config.yaml");
|
|
16040
|
-
if (
|
|
18240
|
+
if (existsSync10(configPath)) {
|
|
16041
18241
|
return false;
|
|
16042
18242
|
}
|
|
16043
|
-
const projectName =
|
|
18243
|
+
const projectName = basename4(projectRoot);
|
|
16044
18244
|
const config = {
|
|
16045
18245
|
project: {
|
|
16046
18246
|
name: projectName,
|
|
@@ -16083,7 +18283,7 @@ function generateConfig(projectRoot, framework) {
|
|
|
16083
18283
|
# Documentation: https://massu.ai/docs/getting-started/configuration
|
|
16084
18284
|
|
|
16085
18285
|
${yamlStringify(config)}`;
|
|
16086
|
-
|
|
18286
|
+
writeFileSync2(configPath, yamlContent, "utf-8");
|
|
16087
18287
|
return true;
|
|
16088
18288
|
}
|
|
16089
18289
|
function monorepoCommonRoot(packages) {
|
|
@@ -16105,7 +18305,7 @@ function buildConfigFromDetection(opts) {
|
|
|
16105
18305
|
if (!detection) {
|
|
16106
18306
|
throw new Error("buildConfigFromDetection requires a detection result");
|
|
16107
18307
|
}
|
|
16108
|
-
const projectName = opts.projectName ??
|
|
18308
|
+
const projectName = opts.projectName ?? basename4(projectRoot);
|
|
16109
18309
|
const languages = Array.from(
|
|
16110
18310
|
new Set(detection.manifests.map((m3) => m3.language))
|
|
16111
18311
|
);
|
|
@@ -16226,7 +18426,7 @@ function buildConfigFromDetection(opts) {
|
|
|
16226
18426
|
};
|
|
16227
18427
|
if (pyFw?.framework) pythonBlock.framework = pyFw.framework;
|
|
16228
18428
|
if (pyFw?.orm) pythonBlock.orm = pyFw.orm;
|
|
16229
|
-
if (
|
|
18429
|
+
if (existsSync10(resolve6(projectRoot, "alembic.ini")) || existsSync10(resolve6(projectRoot, "alembic"))) {
|
|
16230
18430
|
pythonBlock.alembic_dir = "alembic";
|
|
16231
18431
|
}
|
|
16232
18432
|
config.python = pythonBlock;
|
|
@@ -16251,10 +18451,10 @@ function applyVariantTemplate(config, templatesDir) {
|
|
|
16251
18451
|
}
|
|
16252
18452
|
if (templateId === null) return config;
|
|
16253
18453
|
const templatePath = resolve6(templatesDir, templateId, "massu.config.yaml");
|
|
16254
|
-
if (!
|
|
18454
|
+
if (!existsSync10(templatePath)) return config;
|
|
16255
18455
|
let template;
|
|
16256
18456
|
try {
|
|
16257
|
-
template = yamlParse(
|
|
18457
|
+
template = yamlParse(readFileSync10(templatePath, "utf-8"));
|
|
16258
18458
|
} catch {
|
|
16259
18459
|
return config;
|
|
16260
18460
|
}
|
|
@@ -16313,15 +18513,15 @@ ${yamlStringify(config)}`;
|
|
|
16313
18513
|
function writeConfigAtomic(configPath, content) {
|
|
16314
18514
|
const tmpPath = `${configPath}.tmp`;
|
|
16315
18515
|
let existingMode;
|
|
16316
|
-
if (
|
|
18516
|
+
if (existsSync10(configPath)) {
|
|
16317
18517
|
try {
|
|
16318
|
-
existingMode =
|
|
18518
|
+
existingMode = statSync7(configPath).mode;
|
|
16319
18519
|
} catch {
|
|
16320
18520
|
existingMode = void 0;
|
|
16321
18521
|
}
|
|
16322
18522
|
}
|
|
16323
18523
|
try {
|
|
16324
|
-
|
|
18524
|
+
mkdirSync4(dirname5(configPath), { recursive: true });
|
|
16325
18525
|
const fd = openSync2(tmpPath, "w", 420);
|
|
16326
18526
|
try {
|
|
16327
18527
|
const buf = Buffer.from(content, "utf-8");
|
|
@@ -16334,16 +18534,16 @@ function writeConfigAtomic(configPath, content) {
|
|
|
16334
18534
|
if (parsed === null || typeof parsed !== "object") {
|
|
16335
18535
|
throw new Error("Generated config is not a valid YAML object");
|
|
16336
18536
|
}
|
|
16337
|
-
|
|
18537
|
+
renameSync3(tmpPath, configPath);
|
|
16338
18538
|
if (existingMode !== void 0) {
|
|
16339
18539
|
try {
|
|
16340
|
-
|
|
18540
|
+
chmodSync2(configPath, existingMode);
|
|
16341
18541
|
} catch {
|
|
16342
18542
|
}
|
|
16343
18543
|
}
|
|
16344
18544
|
return { validated: true };
|
|
16345
18545
|
} catch (err2) {
|
|
16346
|
-
if (
|
|
18546
|
+
if (existsSync10(tmpPath)) {
|
|
16347
18547
|
try {
|
|
16348
18548
|
rmSync2(tmpPath, { force: true });
|
|
16349
18549
|
} catch {
|
|
@@ -16354,8 +18554,8 @@ function writeConfigAtomic(configPath, content) {
|
|
|
16354
18554
|
}
|
|
16355
18555
|
function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
|
|
16356
18556
|
try {
|
|
16357
|
-
if (!
|
|
16358
|
-
const content =
|
|
18557
|
+
if (!existsSync10(configPath)) return "Config file does not exist after write";
|
|
18558
|
+
const content = readFileSync10(configPath, "utf-8");
|
|
16359
18559
|
const parsed = yamlParse(content);
|
|
16360
18560
|
if (parsed === null || typeof parsed !== "object") {
|
|
16361
18561
|
return "Config is not a valid YAML object";
|
|
@@ -16376,7 +18576,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
|
|
|
16376
18576
|
const src = cfg.paths.source;
|
|
16377
18577
|
if (src && src !== ".") {
|
|
16378
18578
|
const srcAbs = resolve6(projectRoot, src);
|
|
16379
|
-
if (!
|
|
18579
|
+
if (!existsSync10(srcAbs)) {
|
|
16380
18580
|
return `paths.source '${src}' does not exist on disk`;
|
|
16381
18581
|
}
|
|
16382
18582
|
}
|
|
@@ -16387,7 +18587,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
|
|
|
16387
18587
|
for (const d2 of rawDirs) {
|
|
16388
18588
|
if (typeof d2 !== "string" || d2 === ".") continue;
|
|
16389
18589
|
const abs = resolve6(projectRoot, d2);
|
|
16390
|
-
if (!
|
|
18590
|
+
if (!existsSync10(abs)) {
|
|
16391
18591
|
return `framework.languages.${lang}.source_dirs '${d2}' does not exist on disk`;
|
|
16392
18592
|
}
|
|
16393
18593
|
}
|
|
@@ -16396,7 +18596,7 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
|
|
|
16396
18596
|
if (Array.isArray(mRoots)) {
|
|
16397
18597
|
for (const r2 of mRoots) {
|
|
16398
18598
|
if (typeof r2 !== "string" || r2 === ".") continue;
|
|
16399
|
-
if (!
|
|
18599
|
+
if (!existsSync10(resolve6(projectRoot, r2))) {
|
|
16400
18600
|
return `paths.monorepo_roots '${r2}' does not exist on disk`;
|
|
16401
18601
|
}
|
|
16402
18602
|
}
|
|
@@ -16442,7 +18642,7 @@ function resolveTemplatesDir() {
|
|
|
16442
18642
|
resolve6(__dirname2, "../../../templates")
|
|
16443
18643
|
];
|
|
16444
18644
|
for (const c2 of candidates) {
|
|
16445
|
-
if (
|
|
18645
|
+
if (existsSync10(c2)) return c2;
|
|
16446
18646
|
}
|
|
16447
18647
|
return null;
|
|
16448
18648
|
}
|
|
@@ -16452,13 +18652,13 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
|
|
|
16452
18652
|
return { success: false, error: `Templates directory not found (looked in node_modules and dist/src)` };
|
|
16453
18653
|
}
|
|
16454
18654
|
const srcPath = resolve6(templatesDir, templateName, "massu.config.yaml");
|
|
16455
|
-
if (!
|
|
18655
|
+
if (!existsSync10(srcPath)) {
|
|
16456
18656
|
return { success: false, error: `Template '${templateName}' not found at ${srcPath}` };
|
|
16457
18657
|
}
|
|
16458
18658
|
try {
|
|
16459
|
-
let content =
|
|
18659
|
+
let content = readFileSync10(srcPath, "utf-8");
|
|
16460
18660
|
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
16461
|
-
|
|
18661
|
+
writeFileSync2(targetPath, content, "utf-8");
|
|
16462
18662
|
return { success: true };
|
|
16463
18663
|
} catch (err2) {
|
|
16464
18664
|
return { success: false, error: err2 instanceof Error ? err2.message : String(err2) };
|
|
@@ -16467,9 +18667,9 @@ function copyTemplateConfig(templateName, targetPath, projectName) {
|
|
|
16467
18667
|
function registerMcpServer(projectRoot) {
|
|
16468
18668
|
const mcpPath = resolve6(projectRoot, ".mcp.json");
|
|
16469
18669
|
let existing = {};
|
|
16470
|
-
if (
|
|
18670
|
+
if (existsSync10(mcpPath)) {
|
|
16471
18671
|
try {
|
|
16472
|
-
existing = JSON.parse(
|
|
18672
|
+
existing = JSON.parse(readFileSync10(mcpPath, "utf-8"));
|
|
16473
18673
|
} catch {
|
|
16474
18674
|
existing = {};
|
|
16475
18675
|
}
|
|
@@ -16484,17 +18684,17 @@ function registerMcpServer(projectRoot) {
|
|
|
16484
18684
|
args: ["-y", "@massu/core"]
|
|
16485
18685
|
};
|
|
16486
18686
|
existing.mcpServers = servers;
|
|
16487
|
-
|
|
18687
|
+
writeFileSync2(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
16488
18688
|
return true;
|
|
16489
18689
|
}
|
|
16490
18690
|
function resolveHooksDir() {
|
|
16491
18691
|
const cwd = process.cwd();
|
|
16492
18692
|
const nodeModulesPath = resolve6(cwd, "node_modules/@massu/core/dist/hooks");
|
|
16493
|
-
if (
|
|
18693
|
+
if (existsSync10(nodeModulesPath)) {
|
|
16494
18694
|
return "node_modules/@massu/core/dist/hooks";
|
|
16495
18695
|
}
|
|
16496
18696
|
const localPath = resolve6(__dirname2, "../dist/hooks");
|
|
16497
|
-
if (
|
|
18697
|
+
if (existsSync10(localPath)) {
|
|
16498
18698
|
return localPath;
|
|
16499
18699
|
}
|
|
16500
18700
|
return "node_modules/@massu/core/dist/hooks";
|
|
@@ -16588,13 +18788,13 @@ function installHooks(projectRoot) {
|
|
|
16588
18788
|
}
|
|
16589
18789
|
const claudeDir = resolve6(projectRoot, claudeDirName);
|
|
16590
18790
|
const settingsPath = resolve6(claudeDir, "settings.local.json");
|
|
16591
|
-
if (!
|
|
16592
|
-
|
|
18791
|
+
if (!existsSync10(claudeDir)) {
|
|
18792
|
+
mkdirSync4(claudeDir, { recursive: true });
|
|
16593
18793
|
}
|
|
16594
18794
|
let settings = {};
|
|
16595
|
-
if (
|
|
18795
|
+
if (existsSync10(settingsPath)) {
|
|
16596
18796
|
try {
|
|
16597
|
-
settings = JSON.parse(
|
|
18797
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
|
|
16598
18798
|
} catch {
|
|
16599
18799
|
settings = {};
|
|
16600
18800
|
}
|
|
@@ -16608,21 +18808,21 @@ function installHooks(projectRoot) {
|
|
|
16608
18808
|
}
|
|
16609
18809
|
}
|
|
16610
18810
|
settings.hooks = hooksConfig;
|
|
16611
|
-
|
|
18811
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
16612
18812
|
return { installed: true, count: hookCount };
|
|
16613
18813
|
}
|
|
16614
18814
|
function initMemoryDir(projectRoot) {
|
|
16615
18815
|
const encodedRoot = "-" + projectRoot.replace(/\//g, "-");
|
|
16616
|
-
const memoryDir = resolve6(
|
|
18816
|
+
const memoryDir = resolve6(homedir3(), `.claude/projects/${encodedRoot}/memory`);
|
|
16617
18817
|
let created = false;
|
|
16618
|
-
if (!
|
|
16619
|
-
|
|
18818
|
+
if (!existsSync10(memoryDir)) {
|
|
18819
|
+
mkdirSync4(memoryDir, { recursive: true });
|
|
16620
18820
|
created = true;
|
|
16621
18821
|
}
|
|
16622
18822
|
const memoryMdPath = resolve6(memoryDir, "MEMORY.md");
|
|
16623
18823
|
let memoryMdCreated = false;
|
|
16624
|
-
if (!
|
|
16625
|
-
const projectName =
|
|
18824
|
+
if (!existsSync10(memoryMdPath)) {
|
|
18825
|
+
const projectName = basename4(projectRoot);
|
|
16626
18826
|
const memoryContent = `# ${projectName} - Massu Memory
|
|
16627
18827
|
|
|
16628
18828
|
## Key Learnings
|
|
@@ -16637,7 +18837,7 @@ function initMemoryDir(projectRoot) {
|
|
|
16637
18837
|
## File Index
|
|
16638
18838
|
<!-- Significant files and directories -->
|
|
16639
18839
|
`;
|
|
16640
|
-
|
|
18840
|
+
writeFileSync2(memoryMdPath, memoryContent, "utf-8");
|
|
16641
18841
|
memoryMdCreated = true;
|
|
16642
18842
|
}
|
|
16643
18843
|
return { created, memoryMdCreated };
|
|
@@ -16649,6 +18849,7 @@ function parseInitArgs(argv) {
|
|
|
16649
18849
|
if (a2 === "--ci") opts.ci = true;
|
|
16650
18850
|
else if (a2 === "--force") opts.force = true;
|
|
16651
18851
|
else if (a2 === "--skip-commands") opts.skipCommands = true;
|
|
18852
|
+
else if (a2 === "--no-introspect") opts.skipIntrospect = true;
|
|
16652
18853
|
else if (a2 === "--help" || a2 === "-h") opts.help = true;
|
|
16653
18854
|
else if (a2 === "--template") {
|
|
16654
18855
|
const next = argv[i2 + 1];
|
|
@@ -16727,7 +18928,7 @@ async function runInit(argv, overrides) {
|
|
|
16727
18928
|
log("========================");
|
|
16728
18929
|
log("");
|
|
16729
18930
|
const configPath = resolve6(projectRoot, "massu.config.yaml");
|
|
16730
|
-
if (
|
|
18931
|
+
if (existsSync10(configPath)) {
|
|
16731
18932
|
if (opts.ci && !opts.force) {
|
|
16732
18933
|
errLog(`error: massu.config.yaml already exists at ${configPath}`);
|
|
16733
18934
|
errLog(" rerun with --force to overwrite, or remove the file first");
|
|
@@ -16746,7 +18947,7 @@ async function runInit(argv, overrides) {
|
|
|
16746
18947
|
errLog(`error: unknown template '${opts.template}'. Available: ${TEMPLATE_NAMES.join(", ")}`);
|
|
16747
18948
|
throw new Error(`Unknown template: ${opts.template}`);
|
|
16748
18949
|
}
|
|
16749
|
-
const projectName =
|
|
18950
|
+
const projectName = basename4(projectRoot);
|
|
16750
18951
|
const res = copyTemplateConfig(opts.template, configPath, projectName);
|
|
16751
18952
|
if (!res.success) {
|
|
16752
18953
|
errLog(`error: template copy failed: ${res.error}`);
|
|
@@ -16805,7 +19006,25 @@ async function runInit(argv, overrides) {
|
|
|
16805
19006
|
}
|
|
16806
19007
|
}
|
|
16807
19008
|
const baseConfig = buildConfigFromDetection({ projectRoot, detection });
|
|
16808
|
-
const
|
|
19009
|
+
const withVariant = applyVariantTemplate(baseConfig, resolveTemplatesDir());
|
|
19010
|
+
let config = withVariant;
|
|
19011
|
+
if (!opts.skipIntrospect) {
|
|
19012
|
+
try {
|
|
19013
|
+
const { introspectAsync: introspectAsync2 } = await Promise.resolve().then(() => (init_codebase_introspector(), codebase_introspector_exports));
|
|
19014
|
+
const introspected = await introspectAsync2(detection, projectRoot);
|
|
19015
|
+
const detectedBlocks = {};
|
|
19016
|
+
for (const [key, block] of Object.entries(introspected)) {
|
|
19017
|
+
if (block && typeof block === "object" && "_confidence" in block) {
|
|
19018
|
+
detectedBlocks[key] = block;
|
|
19019
|
+
}
|
|
19020
|
+
}
|
|
19021
|
+
if (Object.keys(detectedBlocks).length > 0) {
|
|
19022
|
+
config = { ...config, detected: detectedBlocks };
|
|
19023
|
+
}
|
|
19024
|
+
} catch (err2) {
|
|
19025
|
+
errLog(`warning: AST adapter introspection failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
19026
|
+
}
|
|
19027
|
+
}
|
|
16809
19028
|
const content = renderConfigYaml(config);
|
|
16810
19029
|
const writeRes = writeConfigAtomic(configPath, content);
|
|
16811
19030
|
if (!writeRes.validated) {
|
|
@@ -16849,7 +19068,7 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
|
|
|
16849
19068
|
const stackResolved = !emptyStack && commandStats && (commandStats.installed > 0 || commandStats.updated > 0 || commandStats.kept > 0);
|
|
16850
19069
|
if (!stackResolved) {
|
|
16851
19070
|
const placeholderPath = resolve6(cmdResult.claudeDir, "commands", "_massu-needs-stack.md");
|
|
16852
|
-
if (!
|
|
19071
|
+
if (!existsSync10(placeholderPath)) {
|
|
16853
19072
|
const placeholderBody = [
|
|
16854
19073
|
"# Massu \u2014 stack not yet detected",
|
|
16855
19074
|
"",
|
|
@@ -16870,8 +19089,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
|
|
|
16870
19089
|
"\u2014 Massu"
|
|
16871
19090
|
].join("\n");
|
|
16872
19091
|
try {
|
|
16873
|
-
|
|
16874
|
-
|
|
19092
|
+
mkdirSync4(resolve6(cmdResult.claudeDir, "commands"), { recursive: true });
|
|
19093
|
+
writeFileSync2(placeholderPath, placeholderBody, "utf-8");
|
|
16875
19094
|
log(" Wrote _massu-needs-stack.md placeholder (no stack detected yet)");
|
|
16876
19095
|
} catch {
|
|
16877
19096
|
}
|
|
@@ -16890,8 +19109,8 @@ function installSideEffects(projectRoot, log, skipCommands = false, emptyStack =
|
|
|
16890
19109
|
(async () => {
|
|
16891
19110
|
try {
|
|
16892
19111
|
const encodedRoot = projectRoot.replace(/\//g, "-");
|
|
16893
|
-
const memoryDir = resolve6(
|
|
16894
|
-
const memFiles =
|
|
19112
|
+
const memoryDir = resolve6(homedir3(), ".claude", "projects", encodedRoot, "memory");
|
|
19113
|
+
const memFiles = existsSync10(memoryDir) ? readdirSync10(memoryDir).filter((f2) => f2.endsWith(".md") && f2 !== "MEMORY.md") : [];
|
|
16895
19114
|
if (memFiles.length > 0) {
|
|
16896
19115
|
const { getMemoryDb: getMemoryDb2 } = await Promise.resolve().then(() => (init_memory_db(), memory_db_exports));
|
|
16897
19116
|
const db = getMemoryDb2();
|
|
@@ -16951,7 +19170,7 @@ var init_init = __esm({
|
|
|
16951
19170
|
init_detect();
|
|
16952
19171
|
init_drift();
|
|
16953
19172
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
16954
|
-
__dirname2 =
|
|
19173
|
+
__dirname2 = dirname5(__filename2);
|
|
16955
19174
|
FRAMEWORK_TO_TEMPLATE_ID = {
|
|
16956
19175
|
rails: "rails",
|
|
16957
19176
|
phoenix: "phoenix",
|
|
@@ -16973,7 +19192,7 @@ var init_init = __esm({
|
|
|
16973
19192
|
});
|
|
16974
19193
|
|
|
16975
19194
|
// src/license.ts
|
|
16976
|
-
import { createHash as
|
|
19195
|
+
import { createHash as createHash4 } from "crypto";
|
|
16977
19196
|
function tierLevel(tier) {
|
|
16978
19197
|
return TIER_LEVELS[tier] ?? 0;
|
|
16979
19198
|
}
|
|
@@ -16998,7 +19217,7 @@ function annotateToolDefinitions(defs) {
|
|
|
16998
19217
|
});
|
|
16999
19218
|
}
|
|
17000
19219
|
async function validateLicense(apiKey) {
|
|
17001
|
-
const keyHash =
|
|
19220
|
+
const keyHash = createHash4("sha256").update(apiKey).digest("hex");
|
|
17002
19221
|
const memDb = getMemoryDb();
|
|
17003
19222
|
try {
|
|
17004
19223
|
const cached = memDb.prepare(
|
|
@@ -17059,7 +19278,7 @@ async function validateLicense(apiKey) {
|
|
|
17059
19278
|
}
|
|
17060
19279
|
}
|
|
17061
19280
|
function updateLicenseCache(apiKey, tier, validUntil, features = []) {
|
|
17062
|
-
const keyHash =
|
|
19281
|
+
const keyHash = createHash4("sha256").update(apiKey).digest("hex");
|
|
17063
19282
|
const memDb = getMemoryDb();
|
|
17064
19283
|
try {
|
|
17065
19284
|
memDb.prepare(`
|
|
@@ -17267,17 +19486,17 @@ __export(doctor_exports, {
|
|
|
17267
19486
|
runDoctor: () => runDoctor,
|
|
17268
19487
|
runValidateConfig: () => runValidateConfig
|
|
17269
19488
|
});
|
|
17270
|
-
import { existsSync as
|
|
17271
|
-
import { resolve as resolve7, dirname as
|
|
19489
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, readdirSync as readdirSync11 } from "fs";
|
|
19490
|
+
import { resolve as resolve7, dirname as dirname6 } from "path";
|
|
17272
19491
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
17273
19492
|
import { parse as parseYaml4 } from "yaml";
|
|
17274
19493
|
function checkConfig(projectRoot) {
|
|
17275
19494
|
const configPath = resolve7(projectRoot, "massu.config.yaml");
|
|
17276
|
-
if (!
|
|
19495
|
+
if (!existsSync11(configPath)) {
|
|
17277
19496
|
return { name: "Configuration", status: "fail", detail: "massu.config.yaml not found. Run: npx massu init" };
|
|
17278
19497
|
}
|
|
17279
19498
|
try {
|
|
17280
|
-
const content =
|
|
19499
|
+
const content = readFileSync11(configPath, "utf-8");
|
|
17281
19500
|
const parsed = parseYaml4(content);
|
|
17282
19501
|
if (!parsed || typeof parsed !== "object") {
|
|
17283
19502
|
return { name: "Configuration", status: "fail", detail: "massu.config.yaml is empty or invalid YAML" };
|
|
@@ -17289,11 +19508,11 @@ function checkConfig(projectRoot) {
|
|
|
17289
19508
|
}
|
|
17290
19509
|
function checkMcpServer(projectRoot) {
|
|
17291
19510
|
const mcpPath = getResolvedPaths().mcpJsonPath;
|
|
17292
|
-
if (!
|
|
19511
|
+
if (!existsSync11(mcpPath)) {
|
|
17293
19512
|
return { name: "MCP Server", status: "fail", detail: ".mcp.json not found. Run: npx massu init" };
|
|
17294
19513
|
}
|
|
17295
19514
|
try {
|
|
17296
|
-
const content = JSON.parse(
|
|
19515
|
+
const content = JSON.parse(readFileSync11(mcpPath, "utf-8"));
|
|
17297
19516
|
const servers = content.mcpServers ?? {};
|
|
17298
19517
|
if (!servers.massu) {
|
|
17299
19518
|
return { name: "MCP Server", status: "fail", detail: "massu not registered in .mcp.json. Run: npx massu init" };
|
|
@@ -17305,11 +19524,11 @@ function checkMcpServer(projectRoot) {
|
|
|
17305
19524
|
}
|
|
17306
19525
|
function checkHooksConfig(projectRoot) {
|
|
17307
19526
|
const settingsPath = getResolvedPaths().settingsLocalPath;
|
|
17308
|
-
if (!
|
|
19527
|
+
if (!existsSync11(settingsPath)) {
|
|
17309
19528
|
return { name: "Hooks Config", status: "fail", detail: ".claude/settings.local.json not found. Run: npx massu init" };
|
|
17310
19529
|
}
|
|
17311
19530
|
try {
|
|
17312
|
-
const content = JSON.parse(
|
|
19531
|
+
const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
17313
19532
|
if (!content.hooks) {
|
|
17314
19533
|
return { name: "Hooks Config", status: "fail", detail: "No hooks configured. Run: npx massu install-hooks" };
|
|
17315
19534
|
}
|
|
@@ -17335,9 +19554,9 @@ function checkHooksConfig(projectRoot) {
|
|
|
17335
19554
|
function checkHookFiles(projectRoot) {
|
|
17336
19555
|
const nodeModulesHooksDir = resolve7(projectRoot, "node_modules/@massu/core/dist/hooks");
|
|
17337
19556
|
let hooksDir = nodeModulesHooksDir;
|
|
17338
|
-
if (!
|
|
19557
|
+
if (!existsSync11(nodeModulesHooksDir)) {
|
|
17339
19558
|
const devHooksDir = resolve7(__dirname3, "../../dist/hooks");
|
|
17340
|
-
if (
|
|
19559
|
+
if (existsSync11(devHooksDir)) {
|
|
17341
19560
|
hooksDir = devHooksDir;
|
|
17342
19561
|
} else {
|
|
17343
19562
|
return { name: "Hook Files", status: "fail", detail: "Compiled hooks not found. Run: npm install @massu/core" };
|
|
@@ -17345,7 +19564,7 @@ function checkHookFiles(projectRoot) {
|
|
|
17345
19564
|
}
|
|
17346
19565
|
const missing = [];
|
|
17347
19566
|
for (const hookFile of EXPECTED_HOOKS) {
|
|
17348
|
-
if (!
|
|
19567
|
+
if (!existsSync11(resolve7(hooksDir, hookFile))) {
|
|
17349
19568
|
missing.push(hookFile);
|
|
17350
19569
|
}
|
|
17351
19570
|
}
|
|
@@ -17372,7 +19591,7 @@ function checkNodeVersion() {
|
|
|
17372
19591
|
}
|
|
17373
19592
|
async function checkGitRepo(projectRoot) {
|
|
17374
19593
|
const gitDir = resolve7(projectRoot, ".git");
|
|
17375
|
-
if (!
|
|
19594
|
+
if (!existsSync11(gitDir)) {
|
|
17376
19595
|
return { name: "Git Repository", status: "warn", detail: "Not a git repository (optional but recommended)" };
|
|
17377
19596
|
}
|
|
17378
19597
|
try {
|
|
@@ -17390,7 +19609,7 @@ async function checkGitRepo(projectRoot) {
|
|
|
17390
19609
|
}
|
|
17391
19610
|
function checkKnowledgeDb(projectRoot) {
|
|
17392
19611
|
const knowledgeDbPath = getResolvedPaths().memoryDbPath;
|
|
17393
|
-
if (!
|
|
19612
|
+
if (!existsSync11(knowledgeDbPath)) {
|
|
17394
19613
|
return {
|
|
17395
19614
|
name: "Knowledge DB",
|
|
17396
19615
|
status: "warn",
|
|
@@ -17401,7 +19620,7 @@ function checkKnowledgeDb(projectRoot) {
|
|
|
17401
19620
|
}
|
|
17402
19621
|
function checkMemoryDir(_projectRoot2) {
|
|
17403
19622
|
const memoryDir = getResolvedPaths().memoryDir;
|
|
17404
|
-
if (!
|
|
19623
|
+
if (!existsSync11(memoryDir)) {
|
|
17405
19624
|
return {
|
|
17406
19625
|
name: "Memory Directory",
|
|
17407
19626
|
status: "warn",
|
|
@@ -17412,7 +19631,7 @@ function checkMemoryDir(_projectRoot2) {
|
|
|
17412
19631
|
}
|
|
17413
19632
|
function checkShellHooksWired(_projectRoot2) {
|
|
17414
19633
|
const settingsPath = getResolvedPaths().settingsLocalPath;
|
|
17415
|
-
if (!
|
|
19634
|
+
if (!existsSync11(settingsPath)) {
|
|
17416
19635
|
return {
|
|
17417
19636
|
name: "Shell Hooks",
|
|
17418
19637
|
status: "fail",
|
|
@@ -17420,7 +19639,7 @@ function checkShellHooksWired(_projectRoot2) {
|
|
|
17420
19639
|
};
|
|
17421
19640
|
}
|
|
17422
19641
|
try {
|
|
17423
|
-
const content = JSON.parse(
|
|
19642
|
+
const content = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
17424
19643
|
const hooks = content.hooks ?? {};
|
|
17425
19644
|
const hasSessionStart = Array.isArray(hooks.SessionStart) && hooks.SessionStart.length > 0;
|
|
17426
19645
|
const hasPreToolUse = Array.isArray(hooks.PreToolUse) && hooks.PreToolUse.length > 0;
|
|
@@ -17472,7 +19691,7 @@ function checkPythonHealth(projectRoot) {
|
|
|
17472
19691
|
const config = getConfig();
|
|
17473
19692
|
if (!config.python?.root) return null;
|
|
17474
19693
|
const pythonRoot = resolve7(projectRoot, config.python.root);
|
|
17475
|
-
if (!
|
|
19694
|
+
if (!existsSync11(pythonRoot)) {
|
|
17476
19695
|
return {
|
|
17477
19696
|
name: "Python",
|
|
17478
19697
|
status: "fail",
|
|
@@ -17486,15 +19705,15 @@ function checkPythonHealth(projectRoot) {
|
|
|
17486
19705
|
function scanDir(dir, depth) {
|
|
17487
19706
|
if (depth > 5) return;
|
|
17488
19707
|
try {
|
|
17489
|
-
const entries =
|
|
19708
|
+
const entries = readdirSync11(dir, { withFileTypes: true });
|
|
17490
19709
|
for (const entry of entries) {
|
|
17491
19710
|
if (entry.isDirectory()) {
|
|
17492
19711
|
const excludeDirs = config.python?.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
|
|
17493
19712
|
if (!excludeDirs.includes(entry.name)) {
|
|
17494
19713
|
const subdir = resolve7(dir, entry.name);
|
|
17495
|
-
if (depth <= 2 && !
|
|
19714
|
+
if (depth <= 2 && !existsSync11(resolve7(subdir, "__init__.py"))) {
|
|
17496
19715
|
try {
|
|
17497
|
-
const subEntries =
|
|
19716
|
+
const subEntries = readdirSync11(subdir);
|
|
17498
19717
|
if (subEntries.some((f2) => f2.endsWith(".py") && f2 !== "__init__.py")) {
|
|
17499
19718
|
initPyMissing.push(entry.name);
|
|
17500
19719
|
}
|
|
@@ -17588,14 +19807,14 @@ async function runDoctor() {
|
|
|
17588
19807
|
async function runValidateConfig() {
|
|
17589
19808
|
const projectRoot = process.cwd();
|
|
17590
19809
|
const configPath = resolve7(projectRoot, "massu.config.yaml");
|
|
17591
|
-
if (!
|
|
19810
|
+
if (!existsSync11(configPath)) {
|
|
17592
19811
|
console.error("Error: massu.config.yaml not found in current directory");
|
|
17593
19812
|
console.error("Run: npx massu init");
|
|
17594
19813
|
process.exit(1);
|
|
17595
19814
|
return;
|
|
17596
19815
|
}
|
|
17597
19816
|
try {
|
|
17598
|
-
const content =
|
|
19817
|
+
const content = readFileSync11(configPath, "utf-8");
|
|
17599
19818
|
const parsed = parseYaml4(content);
|
|
17600
19819
|
if (!parsed || typeof parsed !== "object") {
|
|
17601
19820
|
console.error("Error: massu.config.yaml is empty or not a valid YAML object");
|
|
@@ -17634,7 +19853,7 @@ var init_doctor = __esm({
|
|
|
17634
19853
|
init_config();
|
|
17635
19854
|
init_license();
|
|
17636
19855
|
__filename3 = fileURLToPath3(import.meta.url);
|
|
17637
|
-
__dirname3 =
|
|
19856
|
+
__dirname3 = dirname6(__filename3);
|
|
17638
19857
|
EXPECTED_HOOKS = [
|
|
17639
19858
|
"session-start.js",
|
|
17640
19859
|
"session-end.js",
|
|
@@ -17680,7 +19899,7 @@ var show_template_exports = {};
|
|
|
17680
19899
|
__export(show_template_exports, {
|
|
17681
19900
|
runShowTemplate: () => runShowTemplate
|
|
17682
19901
|
});
|
|
17683
|
-
import { existsSync as
|
|
19902
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
17684
19903
|
import { resolve as resolve8 } from "path";
|
|
17685
19904
|
function normalizeBaseName(input) {
|
|
17686
19905
|
return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
|
|
@@ -17710,13 +19929,13 @@ async function runShowTemplate(args3) {
|
|
|
17710
19929
|
}
|
|
17711
19930
|
const suffix = choice.kind === "hit" ? choice.suffix : "";
|
|
17712
19931
|
const file = suffix === "" ? resolve8(sourceDir, `${baseName}.md`) : resolve8(sourceDir, `${baseName}${suffix}.md`);
|
|
17713
|
-
if (!
|
|
19932
|
+
if (!existsSync12(file)) {
|
|
17714
19933
|
process.stderr.write(`massu: resolved template "${file}" no longer exists
|
|
17715
19934
|
`);
|
|
17716
19935
|
process.exit(1);
|
|
17717
19936
|
return;
|
|
17718
19937
|
}
|
|
17719
|
-
process.stdout.write(
|
|
19938
|
+
process.stdout.write(readFileSync12(file, "utf-8"));
|
|
17720
19939
|
}
|
|
17721
19940
|
var init_show_template = __esm({
|
|
17722
19941
|
"src/commands/show-template.ts"() {
|
|
@@ -17769,12 +19988,12 @@ var init_passthrough = __esm({
|
|
|
17769
19988
|
});
|
|
17770
19989
|
|
|
17771
19990
|
// src/lib/fileLock.ts
|
|
17772
|
-
import { mkdirSync as
|
|
17773
|
-
import { dirname as
|
|
19991
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync13, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
19992
|
+
import { dirname as dirname7 } from "path";
|
|
17774
19993
|
import * as lockfile from "proper-lockfile";
|
|
17775
19994
|
function readLockHolderPid(lockPath) {
|
|
17776
19995
|
try {
|
|
17777
|
-
const raw =
|
|
19996
|
+
const raw = readFileSync13(`${lockPath}.pid`, "utf-8").trim();
|
|
17778
19997
|
const pid = Number.parseInt(raw, 10);
|
|
17779
19998
|
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
17780
19999
|
return pid;
|
|
@@ -17793,7 +20012,7 @@ function busyWaitSync(ms) {
|
|
|
17793
20012
|
Atomics.wait(view, 0, 0, ms);
|
|
17794
20013
|
}
|
|
17795
20014
|
function withFileLockSync(lockPath, fn, opts = {}) {
|
|
17796
|
-
|
|
20015
|
+
mkdirSync5(dirname7(lockPath), { recursive: true });
|
|
17797
20016
|
const staleMs = opts.staleMs ?? 3e4;
|
|
17798
20017
|
const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
|
|
17799
20018
|
const pollIntervalMs = opts.pollIntervalMs ?? 100;
|
|
@@ -17810,7 +20029,7 @@ function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
17810
20029
|
realpath: false
|
|
17811
20030
|
});
|
|
17812
20031
|
try {
|
|
17813
|
-
|
|
20032
|
+
writeFileSync3(`${lockPath}.pid`, String(process.pid), "utf-8");
|
|
17814
20033
|
} catch {
|
|
17815
20034
|
}
|
|
17816
20035
|
break;
|
|
@@ -17899,7 +20118,7 @@ __export(config_refresh_exports, {
|
|
|
17899
20118
|
mergeRefresh: () => mergeRefresh,
|
|
17900
20119
|
runConfigRefresh: () => runConfigRefresh
|
|
17901
20120
|
});
|
|
17902
|
-
import { existsSync as
|
|
20121
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14, rmSync as rmSync4 } from "fs";
|
|
17903
20122
|
import { resolve as resolve10 } from "path";
|
|
17904
20123
|
import { parse as parseYaml5 } from "yaml";
|
|
17905
20124
|
function flatten(obj, prefix3 = "") {
|
|
@@ -18031,14 +20250,14 @@ async function runConfigRefresh(opts = {}) {
|
|
|
18031
20250
|
const configPath = resolve10(cwd, "massu.config.yaml");
|
|
18032
20251
|
const log = opts.silent ? () => {
|
|
18033
20252
|
} : (s) => process.stdout.write(s);
|
|
18034
|
-
if (!
|
|
20253
|
+
if (!existsSync13(configPath)) {
|
|
18035
20254
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
18036
20255
|
if (!opts.silent) process.stderr.write(message + "\n");
|
|
18037
20256
|
return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
|
|
18038
20257
|
}
|
|
18039
20258
|
let existing;
|
|
18040
20259
|
try {
|
|
18041
|
-
const content =
|
|
20260
|
+
const content = readFileSync14(configPath, "utf-8");
|
|
18042
20261
|
const parsed = parseYaml5(content);
|
|
18043
20262
|
if (!parsed || typeof parsed !== "object") {
|
|
18044
20263
|
throw new Error("config is not a YAML object");
|
|
@@ -18112,7 +20331,7 @@ async function runConfigRefresh(opts = {}) {
|
|
|
18112
20331
|
const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
|
|
18113
20332
|
if (stackResolved) {
|
|
18114
20333
|
const placeholderPath = resolve10(installResult.claudeDir, "commands", "_massu-needs-stack.md");
|
|
18115
|
-
if (
|
|
20334
|
+
if (existsSync13(placeholderPath)) {
|
|
18116
20335
|
try {
|
|
18117
20336
|
rmSync4(placeholderPath, { force: true });
|
|
18118
20337
|
log("Removed _massu-needs-stack.md (stack now declared).\n");
|
|
@@ -18221,14 +20440,14 @@ var init_gitToplevel = __esm({
|
|
|
18221
20440
|
});
|
|
18222
20441
|
|
|
18223
20442
|
// src/watch/lockfile-detector.ts
|
|
18224
|
-
import { existsSync as
|
|
20443
|
+
import { existsSync as existsSync14, statSync as statSync8 } from "fs";
|
|
18225
20444
|
import { resolve as resolve11 } from "path";
|
|
18226
20445
|
function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
|
|
18227
20446
|
for (const lf of KNOWN_LOCKFILES) {
|
|
18228
20447
|
const p19 = resolve11(projectRoot, lf);
|
|
18229
|
-
if (!
|
|
20448
|
+
if (!existsSync14(p19)) continue;
|
|
18230
20449
|
try {
|
|
18231
|
-
const stat =
|
|
20450
|
+
const stat = statSync8(p19);
|
|
18232
20451
|
const delta = now - stat.mtimeMs;
|
|
18233
20452
|
if (delta >= 0 && delta < windowMs) return true;
|
|
18234
20453
|
} catch {
|
|
@@ -18239,7 +20458,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
|
|
|
18239
20458
|
function gitMidOperation(projectRoot) {
|
|
18240
20459
|
const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
|
|
18241
20460
|
for (const s of sentinels) {
|
|
18242
|
-
if (
|
|
20461
|
+
if (existsSync14(resolve11(projectRoot, ".git", s))) return true;
|
|
18243
20462
|
}
|
|
18244
20463
|
return false;
|
|
18245
20464
|
}
|
|
@@ -18436,8 +20655,8 @@ var init_paths = __esm({
|
|
|
18436
20655
|
});
|
|
18437
20656
|
|
|
18438
20657
|
// src/watch/state.ts
|
|
18439
|
-
import { closeSync as closeSync3, existsSync as
|
|
18440
|
-
import { dirname as
|
|
20658
|
+
import { closeSync as closeSync3, existsSync as existsSync15, fsyncSync as fsyncSync3, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync4, writeSync as writeSync3 } from "fs";
|
|
20659
|
+
import { dirname as dirname8, resolve as resolve12 } from "path";
|
|
18441
20660
|
function watchStatePath(projectRoot) {
|
|
18442
20661
|
return resolve12(projectRoot, ".massu", "watch-state.json");
|
|
18443
20662
|
}
|
|
@@ -18446,8 +20665,8 @@ function backupStatePath(projectRoot) {
|
|
|
18446
20665
|
}
|
|
18447
20666
|
function readState(projectRoot) {
|
|
18448
20667
|
const path = watchStatePath(projectRoot);
|
|
18449
|
-
if (!
|
|
18450
|
-
const content =
|
|
20668
|
+
if (!existsSync15(path)) return { ...DEFAULT_STATE };
|
|
20669
|
+
const content = readFileSync15(path, "utf-8");
|
|
18451
20670
|
let raw;
|
|
18452
20671
|
try {
|
|
18453
20672
|
raw = JSON.parse(content);
|
|
@@ -18489,14 +20708,14 @@ function readState(projectRoot) {
|
|
|
18489
20708
|
}
|
|
18490
20709
|
function archiveCorrupt(projectRoot, content) {
|
|
18491
20710
|
const bak = backupStatePath(projectRoot);
|
|
18492
|
-
|
|
18493
|
-
|
|
20711
|
+
mkdirSync6(dirname8(bak), { recursive: true });
|
|
20712
|
+
writeFileSync4(bak, content, "utf-8");
|
|
18494
20713
|
}
|
|
18495
20714
|
function writeStateAtomic(projectRoot, state) {
|
|
18496
20715
|
const path = watchStatePath(projectRoot);
|
|
18497
20716
|
writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
|
|
18498
20717
|
const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
|
|
18499
|
-
|
|
20718
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
18500
20719
|
let renamed = false;
|
|
18501
20720
|
try {
|
|
18502
20721
|
const fd = openSync3(tmp, "w");
|
|
@@ -18507,10 +20726,10 @@ function writeStateAtomic(projectRoot, state) {
|
|
|
18507
20726
|
} finally {
|
|
18508
20727
|
closeSync3(fd);
|
|
18509
20728
|
}
|
|
18510
|
-
|
|
20729
|
+
renameSync4(tmp, path);
|
|
18511
20730
|
renamed = true;
|
|
18512
20731
|
} finally {
|
|
18513
|
-
if (!renamed &&
|
|
20732
|
+
if (!renamed && existsSync15(tmp)) {
|
|
18514
20733
|
try {
|
|
18515
20734
|
rmSync5(tmp, { force: true });
|
|
18516
20735
|
} catch {
|
|
@@ -18790,8 +21009,8 @@ __export(watch_exports, {
|
|
|
18790
21009
|
runWatch: () => runWatch
|
|
18791
21010
|
});
|
|
18792
21011
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
18793
|
-
import { basename as
|
|
18794
|
-
import { appendFileSync, existsSync as
|
|
21012
|
+
import { basename as basename5, dirname as dirname9, resolve as resolve13 } from "path";
|
|
21013
|
+
import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync16 } from "fs";
|
|
18795
21014
|
function parseFlags(args3) {
|
|
18796
21015
|
const out2 = {
|
|
18797
21016
|
foreground: false,
|
|
@@ -18817,7 +21036,7 @@ function parseFlags(args3) {
|
|
|
18817
21036
|
function findClaudeBg() {
|
|
18818
21037
|
const home = process.env.HOME ?? "";
|
|
18819
21038
|
const fixed = home ? resolve13(home, ".claude", "bin", "claude-bg") : null;
|
|
18820
|
-
if (fixed &&
|
|
21039
|
+
if (fixed && existsSync16(fixed)) return fixed;
|
|
18821
21040
|
const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
|
|
18822
21041
|
if (which.status === 0 && which.stdout) {
|
|
18823
21042
|
const p19 = which.stdout.trim();
|
|
@@ -18826,7 +21045,7 @@ function findClaudeBg() {
|
|
|
18826
21045
|
return null;
|
|
18827
21046
|
}
|
|
18828
21047
|
function watchName(root) {
|
|
18829
|
-
return `massu-watch-${
|
|
21048
|
+
return `massu-watch-${basename5(root)}`;
|
|
18830
21049
|
}
|
|
18831
21050
|
function printHelp(out2) {
|
|
18832
21051
|
out2(`
|
|
@@ -18878,7 +21097,7 @@ async function runWatch(args3) {
|
|
|
18878
21097
|
}
|
|
18879
21098
|
function runStatus(root) {
|
|
18880
21099
|
const path = watchStatePath(root);
|
|
18881
|
-
if (!
|
|
21100
|
+
if (!existsSync16(path)) {
|
|
18882
21101
|
process.stdout.write("massu watch: not running (no state file)\n");
|
|
18883
21102
|
return { exitCode: 0 };
|
|
18884
21103
|
}
|
|
@@ -19036,18 +21255,18 @@ function refreshLogPath(projectRoot) {
|
|
|
19036
21255
|
function appendRefreshLog(projectRoot, event) {
|
|
19037
21256
|
const path = refreshLogPath(projectRoot);
|
|
19038
21257
|
try {
|
|
19039
|
-
|
|
21258
|
+
mkdirSync7(dirname9(path), { recursive: true });
|
|
19040
21259
|
appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
|
|
19041
21260
|
} catch {
|
|
19042
21261
|
}
|
|
19043
21262
|
}
|
|
19044
21263
|
function readRefreshLog(projectRoot, limit = 10, opts = {}) {
|
|
19045
21264
|
const path = refreshLogPath(projectRoot);
|
|
19046
|
-
if (!
|
|
21265
|
+
if (!existsSync16(path)) return [];
|
|
19047
21266
|
const warn = opts.warn ?? ((s) => {
|
|
19048
21267
|
process.stderr.write(s);
|
|
19049
21268
|
});
|
|
19050
|
-
const lines =
|
|
21269
|
+
const lines = readFileSync16(path, "utf-8").split("\n").filter(Boolean);
|
|
19051
21270
|
const tail = lines.slice(-limit);
|
|
19052
21271
|
const out2 = [];
|
|
19053
21272
|
let corrupt = 0;
|
|
@@ -19118,28 +21337,28 @@ var init_refresh_log = __esm({
|
|
|
19118
21337
|
|
|
19119
21338
|
// src/security/atomic-write.ts
|
|
19120
21339
|
import {
|
|
19121
|
-
chmodSync as
|
|
21340
|
+
chmodSync as chmodSync3,
|
|
19122
21341
|
closeSync as closeSync4,
|
|
19123
|
-
existsSync as
|
|
21342
|
+
existsSync as existsSync17,
|
|
19124
21343
|
fsyncSync as fsyncSync4,
|
|
19125
|
-
mkdirSync as
|
|
21344
|
+
mkdirSync as mkdirSync8,
|
|
19126
21345
|
openSync as openSync4,
|
|
19127
|
-
renameSync as
|
|
21346
|
+
renameSync as renameSync5,
|
|
19128
21347
|
rmSync as rmSync6,
|
|
19129
|
-
statSync as
|
|
21348
|
+
statSync as statSync9,
|
|
19130
21349
|
writeSync as writeSync4
|
|
19131
21350
|
} from "node:fs";
|
|
19132
|
-
import { dirname as
|
|
21351
|
+
import { dirname as dirname10 } from "node:path";
|
|
19133
21352
|
function atomicWrite(path, content, opts = {}) {
|
|
19134
21353
|
const tmpPath = `${path}.tmp`;
|
|
19135
|
-
const parentDir =
|
|
21354
|
+
const parentDir = dirname10(path);
|
|
19136
21355
|
try {
|
|
19137
|
-
if (!
|
|
21356
|
+
if (!existsSync17(parentDir)) {
|
|
19138
21357
|
const mkdirOpts = { recursive: true };
|
|
19139
21358
|
if (opts.ensureParentDirMode !== void 0) {
|
|
19140
21359
|
mkdirOpts.mode = opts.ensureParentDirMode;
|
|
19141
21360
|
}
|
|
19142
|
-
|
|
21361
|
+
mkdirSync8(parentDir, mkdirOpts);
|
|
19143
21362
|
}
|
|
19144
21363
|
const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
19145
21364
|
const openMode = opts.mode ?? 420;
|
|
@@ -19151,12 +21370,12 @@ function atomicWrite(path, content, opts = {}) {
|
|
|
19151
21370
|
closeSync4(fd);
|
|
19152
21371
|
}
|
|
19153
21372
|
if (opts.mode !== void 0) {
|
|
19154
|
-
|
|
21373
|
+
chmodSync3(tmpPath, opts.mode);
|
|
19155
21374
|
}
|
|
19156
|
-
|
|
21375
|
+
renameSync5(tmpPath, path);
|
|
19157
21376
|
return { written: true };
|
|
19158
21377
|
} catch (err2) {
|
|
19159
|
-
if (
|
|
21378
|
+
if (existsSync17(tmpPath)) {
|
|
19160
21379
|
try {
|
|
19161
21380
|
rmSync6(tmpPath, { force: true });
|
|
19162
21381
|
} catch {
|
|
@@ -21460,9 +23679,9 @@ var init_registry_pubkey_generated = __esm({
|
|
|
21460
23679
|
});
|
|
21461
23680
|
|
|
21462
23681
|
// src/security/adapter-verifier.ts
|
|
21463
|
-
import { createHash as
|
|
23682
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
21464
23683
|
function sha256Hex(bytes) {
|
|
21465
|
-
return
|
|
23684
|
+
return createHash5("sha256").update(bytes).digest("hex");
|
|
21466
23685
|
}
|
|
21467
23686
|
function reviver(key, value) {
|
|
21468
23687
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
@@ -21724,24 +23943,24 @@ var init_fetcher = __esm({
|
|
|
21724
23943
|
});
|
|
21725
23944
|
|
|
21726
23945
|
// src/security/manifest-cache.ts
|
|
21727
|
-
import { existsSync as
|
|
21728
|
-
import { homedir as
|
|
23946
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17, statSync as statSync10 } from "node:fs";
|
|
23947
|
+
import { homedir as homedir4 } from "node:os";
|
|
21729
23948
|
import { resolve as resolve14 } from "node:path";
|
|
21730
23949
|
import { z as z4 } from "zod";
|
|
21731
23950
|
function defaultCachePaths() {
|
|
21732
|
-
const dir = resolve14(
|
|
23951
|
+
const dir = resolve14(homedir4(), ".massu");
|
|
21733
23952
|
return {
|
|
21734
23953
|
cachePath: resolve14(dir, "adapter-manifest.json"),
|
|
21735
23954
|
lockPath: resolve14(dir, ".adapter-manifest.lock")
|
|
21736
23955
|
};
|
|
21737
23956
|
}
|
|
21738
23957
|
function loadCachedManifest(paths = defaultCachePaths()) {
|
|
21739
|
-
if (!
|
|
23958
|
+
if (!existsSync18(paths.cachePath)) {
|
|
21740
23959
|
return { kind: "absent" };
|
|
21741
23960
|
}
|
|
21742
23961
|
let raw;
|
|
21743
23962
|
try {
|
|
21744
|
-
const content =
|
|
23963
|
+
const content = readFileSync17(paths.cachePath, "utf-8");
|
|
21745
23964
|
raw = JSON.parse(content);
|
|
21746
23965
|
} catch (err2) {
|
|
21747
23966
|
return {
|
|
@@ -21930,10 +24149,10 @@ var init_adapter_origin = __esm({
|
|
|
21930
24149
|
});
|
|
21931
24150
|
|
|
21932
24151
|
// src/security/local-fingerprint.ts
|
|
21933
|
-
import { existsSync as
|
|
21934
|
-
import { homedir as
|
|
24152
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18, lstatSync as lstatSync5 } from "node:fs";
|
|
24153
|
+
import { homedir as homedir5 } from "node:os";
|
|
21935
24154
|
import { resolve as resolve15, isAbsolute } from "node:path";
|
|
21936
|
-
import { createHash as
|
|
24155
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
21937
24156
|
import { z as z5 } from "zod";
|
|
21938
24157
|
function computeLocalFingerprint(localPaths, projectRoot) {
|
|
21939
24158
|
const tuples = [];
|
|
@@ -21941,13 +24160,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
21941
24160
|
const abs = isAbsolute(p19) ? p19 : resolve15(projectRoot, p19);
|
|
21942
24161
|
let contentTag;
|
|
21943
24162
|
try {
|
|
21944
|
-
const lst =
|
|
24163
|
+
const lst = lstatSync5(abs);
|
|
21945
24164
|
if (lst.isSymbolicLink()) {
|
|
21946
24165
|
contentTag = "<symlink>";
|
|
21947
24166
|
} else if (!lst.isFile()) {
|
|
21948
24167
|
contentTag = "<not-a-file>";
|
|
21949
24168
|
} else {
|
|
21950
|
-
contentTag =
|
|
24169
|
+
contentTag = createHash6("sha256").update(readFileSync18(abs)).digest("hex");
|
|
21951
24170
|
}
|
|
21952
24171
|
} catch {
|
|
21953
24172
|
contentTag = "<missing>";
|
|
@@ -21956,13 +24175,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
|
|
|
21956
24175
|
}
|
|
21957
24176
|
tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
|
|
21958
24177
|
const canonical = JSON.stringify(tuples);
|
|
21959
|
-
return
|
|
24178
|
+
return createHash6("sha256").update(canonical).digest("hex");
|
|
21960
24179
|
}
|
|
21961
24180
|
function readFingerprintSentinel(path = FINGERPRINT_PATH) {
|
|
21962
|
-
if (!
|
|
24181
|
+
if (!existsSync19(path)) return null;
|
|
21963
24182
|
let raw;
|
|
21964
24183
|
try {
|
|
21965
|
-
raw = JSON.parse(
|
|
24184
|
+
raw = JSON.parse(readFileSync18(path, "utf-8"));
|
|
21966
24185
|
} catch {
|
|
21967
24186
|
return null;
|
|
21968
24187
|
}
|
|
@@ -22010,7 +24229,7 @@ var init_local_fingerprint = __esm({
|
|
|
22010
24229
|
"use strict";
|
|
22011
24230
|
init_atomic_write();
|
|
22012
24231
|
init_manifest_schema();
|
|
22013
|
-
FINGERPRINT_PATH = resolve15(
|
|
24232
|
+
FINGERPRINT_PATH = resolve15(homedir5(), ".massu", "adapters-local-fingerprint.json");
|
|
22014
24233
|
FingerprintSentinelSchema = z5.object({
|
|
22015
24234
|
fingerprint: z5.string().regex(/^[0-9a-f]{64}$/),
|
|
22016
24235
|
source: z5.enum(["cli", "cli-resync"]),
|
|
@@ -22026,15 +24245,15 @@ var init_local_fingerprint = __esm({
|
|
|
22026
24245
|
});
|
|
22027
24246
|
|
|
22028
24247
|
// src/security/install-tracking.ts
|
|
22029
|
-
import { readFileSync as
|
|
22030
|
-
import { join as
|
|
22031
|
-
import { homedir as
|
|
24248
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync12, lstatSync as lstatSync6, existsSync as existsSync20 } from "node:fs";
|
|
24249
|
+
import { join as join10, relative as relative5, sep } from "node:path";
|
|
24250
|
+
import { homedir as homedir6 } from "node:os";
|
|
22032
24251
|
import { resolve as resolve16 } from "node:path";
|
|
22033
|
-
import { createHash as
|
|
24252
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
22034
24253
|
import { z as z6 } from "zod";
|
|
22035
24254
|
function containsHiddenDirs(packageDir) {
|
|
22036
24255
|
for (const hidden of EXCLUDED_DIR_NAMES) {
|
|
22037
|
-
if (
|
|
24256
|
+
if (existsSync20(`${packageDir}/${hidden}`)) {
|
|
22038
24257
|
return hidden;
|
|
22039
24258
|
}
|
|
22040
24259
|
}
|
|
@@ -22046,15 +24265,15 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
22046
24265
|
function walk(currentDir) {
|
|
22047
24266
|
let entries;
|
|
22048
24267
|
try {
|
|
22049
|
-
entries =
|
|
24268
|
+
entries = readdirSync12(currentDir);
|
|
22050
24269
|
} catch {
|
|
22051
24270
|
return;
|
|
22052
24271
|
}
|
|
22053
24272
|
for (const entry of entries.sort()) {
|
|
22054
|
-
const absPath =
|
|
24273
|
+
const absPath = join10(currentDir, entry);
|
|
22055
24274
|
let lst;
|
|
22056
24275
|
try {
|
|
22057
|
-
lst =
|
|
24276
|
+
lst = lstatSync6(absPath);
|
|
22058
24277
|
} catch {
|
|
22059
24278
|
continue;
|
|
22060
24279
|
}
|
|
@@ -22072,15 +24291,15 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
22072
24291
|
`sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
|
|
22073
24292
|
);
|
|
22074
24293
|
}
|
|
22075
|
-
const rel =
|
|
24294
|
+
const rel = relative5(dir, absPath).split(sep).join("/");
|
|
22076
24295
|
files.push({ relativePath: rel, absPath });
|
|
22077
24296
|
}
|
|
22078
24297
|
}
|
|
22079
24298
|
walk(dir);
|
|
22080
24299
|
files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
|
|
22081
|
-
const top =
|
|
24300
|
+
const top = createHash7("sha256");
|
|
22082
24301
|
for (const f2 of files) {
|
|
22083
|
-
const fileHash =
|
|
24302
|
+
const fileHash = createHash7("sha256").update(readFileSync19(f2.absPath)).digest("hex");
|
|
22084
24303
|
top.update(f2.relativePath, "utf-8");
|
|
22085
24304
|
top.update("\0", "utf-8");
|
|
22086
24305
|
top.update(fileHash, "utf-8");
|
|
@@ -22089,10 +24308,10 @@ function sha256OfDir(dir, opts = {}) {
|
|
|
22089
24308
|
return top.digest("hex");
|
|
22090
24309
|
}
|
|
22091
24310
|
function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
|
|
22092
|
-
if (!
|
|
24311
|
+
if (!existsSync20(path)) return {};
|
|
22093
24312
|
let raw;
|
|
22094
24313
|
try {
|
|
22095
|
-
raw = JSON.parse(
|
|
24314
|
+
raw = JSON.parse(readFileSync19(path, "utf-8"));
|
|
22096
24315
|
} catch {
|
|
22097
24316
|
return {};
|
|
22098
24317
|
}
|
|
@@ -22156,7 +24375,7 @@ var init_install_tracking = __esm({
|
|
|
22156
24375
|
"src/security/install-tracking.ts"() {
|
|
22157
24376
|
"use strict";
|
|
22158
24377
|
init_atomic_write();
|
|
22159
|
-
INSTALLED_MANIFEST_PATH = resolve16(
|
|
24378
|
+
INSTALLED_MANIFEST_PATH = resolve16(homedir6(), ".massu", "adapter-manifest-installed.json");
|
|
22160
24379
|
DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
22161
24380
|
EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
|
|
22162
24381
|
InstallEntrySchema = z6.object({
|
|
@@ -22179,18 +24398,18 @@ var init_install_tracking = __esm({
|
|
|
22179
24398
|
});
|
|
22180
24399
|
|
|
22181
24400
|
// src/detect/adapters/discover.ts
|
|
22182
|
-
import { existsSync as
|
|
24401
|
+
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync20, lstatSync as lstatSync7 } from "node:fs";
|
|
22183
24402
|
import { resolve as resolve17, isAbsolute as isAbsolute2 } from "node:path";
|
|
22184
24403
|
import { z as z7 } from "zod";
|
|
22185
24404
|
function walkNodeModules(projectRoot, warnings) {
|
|
22186
24405
|
const nodeModulesDir = resolve17(projectRoot, "node_modules");
|
|
22187
|
-
if (!
|
|
24406
|
+
if (!existsSync21(nodeModulesDir)) {
|
|
22188
24407
|
return [];
|
|
22189
24408
|
}
|
|
22190
24409
|
const candidates = [];
|
|
22191
24410
|
let topLevelEntries;
|
|
22192
24411
|
try {
|
|
22193
|
-
topLevelEntries =
|
|
24412
|
+
topLevelEntries = readdirSync13(nodeModulesDir);
|
|
22194
24413
|
} catch (err2) {
|
|
22195
24414
|
warnings.push(`failed to read node_modules: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
22196
24415
|
return [];
|
|
@@ -22200,7 +24419,7 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
22200
24419
|
const entryPath = resolve17(nodeModulesDir, entry);
|
|
22201
24420
|
let entryStat;
|
|
22202
24421
|
try {
|
|
22203
|
-
entryStat =
|
|
24422
|
+
entryStat = lstatSync7(entryPath);
|
|
22204
24423
|
} catch {
|
|
22205
24424
|
continue;
|
|
22206
24425
|
}
|
|
@@ -22214,7 +24433,7 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
22214
24433
|
if (entry.startsWith("@")) {
|
|
22215
24434
|
let scopedEntries;
|
|
22216
24435
|
try {
|
|
22217
|
-
scopedEntries =
|
|
24436
|
+
scopedEntries = readdirSync13(entryPath);
|
|
22218
24437
|
} catch {
|
|
22219
24438
|
continue;
|
|
22220
24439
|
}
|
|
@@ -22222,7 +24441,7 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
22222
24441
|
const subPath = resolve17(entryPath, sub);
|
|
22223
24442
|
let subStat;
|
|
22224
24443
|
try {
|
|
22225
|
-
subStat =
|
|
24444
|
+
subStat = lstatSync7(subPath);
|
|
22226
24445
|
} catch {
|
|
22227
24446
|
continue;
|
|
22228
24447
|
}
|
|
@@ -22239,10 +24458,10 @@ function walkNodeModules(projectRoot, warnings) {
|
|
|
22239
24458
|
}
|
|
22240
24459
|
function tryReadAdapterPackage(packageDir, warnings) {
|
|
22241
24460
|
const pkgJsonPath = resolve17(packageDir, "package.json");
|
|
22242
|
-
if (!
|
|
24461
|
+
if (!existsSync21(pkgJsonPath)) return null;
|
|
22243
24462
|
let raw;
|
|
22244
24463
|
try {
|
|
22245
|
-
raw = JSON.parse(
|
|
24464
|
+
raw = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
|
|
22246
24465
|
} catch (err2) {
|
|
22247
24466
|
warnings.push(
|
|
22248
24467
|
`skipping ${packageDir}: package.json parse failed (${err2 instanceof Error ? err2.message : String(err2)})`
|
|
@@ -22377,7 +24596,7 @@ function discoverAdapters(opts) {
|
|
|
22377
24596
|
for (const localPath of opts.configLocalPaths) {
|
|
22378
24597
|
if (seenIds.has(localPath)) continue;
|
|
22379
24598
|
const absPath = isAbsolute2(localPath) ? localPath : resolve17(opts.projectRoot, localPath);
|
|
22380
|
-
if (!
|
|
24599
|
+
if (!existsSync21(absPath)) {
|
|
22381
24600
|
warnings.push(
|
|
22382
24601
|
`local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
|
|
22383
24602
|
);
|
|
@@ -22458,7 +24677,7 @@ __export(adapters_exports, {
|
|
|
22458
24677
|
runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
|
|
22459
24678
|
runAdaptersSearch: () => runAdaptersSearch
|
|
22460
24679
|
});
|
|
22461
|
-
import { existsSync as
|
|
24680
|
+
import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
|
|
22462
24681
|
import { resolve as resolve18 } from "node:path";
|
|
22463
24682
|
import { parseDocument } from "yaml";
|
|
22464
24683
|
async function handleAdaptersSubcommand(args3) {
|
|
@@ -22727,7 +24946,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
22727
24946
|
return { exitCode: 2 };
|
|
22728
24947
|
}
|
|
22729
24948
|
const yamlPath = resolve18(projectRoot, "massu.config.yaml");
|
|
22730
|
-
if (!
|
|
24949
|
+
if (!existsSync22(yamlPath)) {
|
|
22731
24950
|
process.stderr.write(
|
|
22732
24951
|
`${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
|
|
22733
24952
|
`
|
|
@@ -22736,7 +24955,7 @@ function mutateLocalArray(mutator, command) {
|
|
|
22736
24955
|
}
|
|
22737
24956
|
let yamlText;
|
|
22738
24957
|
try {
|
|
22739
|
-
yamlText =
|
|
24958
|
+
yamlText = readFileSync21(yamlPath, "utf-8");
|
|
22740
24959
|
} catch (err2) {
|
|
22741
24960
|
process.stderr.write(`${command}: failed to read ${yamlPath}: ${err2 instanceof Error ? err2.message : String(err2)}
|
|
22742
24961
|
`);
|
|
@@ -22827,7 +25046,7 @@ async function runAdaptersInstall(args3) {
|
|
|
22827
25046
|
return { exitCode: 1 };
|
|
22828
25047
|
}
|
|
22829
25048
|
const packageDir = resolve18(projectRoot, "node_modules", ...packageName.split("/"));
|
|
22830
|
-
if (!
|
|
25049
|
+
if (!existsSync22(packageDir)) {
|
|
22831
25050
|
process.stderr.write(
|
|
22832
25051
|
`install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
|
|
22833
25052
|
`
|
|
@@ -22835,14 +25054,14 @@ async function runAdaptersInstall(args3) {
|
|
|
22835
25054
|
return { exitCode: 1 };
|
|
22836
25055
|
}
|
|
22837
25056
|
const pkgJsonPath = resolve18(packageDir, "package.json");
|
|
22838
|
-
if (!
|
|
25057
|
+
if (!existsSync22(pkgJsonPath)) {
|
|
22839
25058
|
process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
|
|
22840
25059
|
`);
|
|
22841
25060
|
return { exitCode: 1 };
|
|
22842
25061
|
}
|
|
22843
25062
|
let pkgJson;
|
|
22844
25063
|
try {
|
|
22845
|
-
pkgJson = JSON.parse(
|
|
25064
|
+
pkgJson = JSON.parse(readFileSync21(pkgJsonPath, "utf-8"));
|
|
22846
25065
|
} catch (err2) {
|
|
22847
25066
|
process.stderr.write(`install: ${packageName} has malformed package.json: ${err2 instanceof Error ? err2.message : String(err2)}
|
|
22848
25067
|
`);
|
|
@@ -22962,7 +25181,7 @@ async function runAdaptersResign(_args) {
|
|
|
22962
25181
|
continue;
|
|
22963
25182
|
}
|
|
22964
25183
|
const packageDir = resolve18(projectRoot, "node_modules", ...name2.split("/"));
|
|
22965
|
-
if (!
|
|
25184
|
+
if (!existsSync22(packageDir)) {
|
|
22966
25185
|
removed++;
|
|
22967
25186
|
warnings.push(`${name2}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
|
|
22968
25187
|
removeInstalledManifestEntry(name2);
|
|
@@ -23049,11 +25268,11 @@ var init_adapters2 = __esm({
|
|
|
23049
25268
|
|
|
23050
25269
|
// src/db.ts
|
|
23051
25270
|
import Database2 from "better-sqlite3";
|
|
23052
|
-
import { dirname as
|
|
23053
|
-
import { existsSync as
|
|
25271
|
+
import { dirname as dirname11, join as join11 } from "path";
|
|
25272
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync9, readdirSync as readdirSync14, statSync as statSync11 } from "fs";
|
|
23054
25273
|
function getCodeGraphDb() {
|
|
23055
25274
|
const dbPath = getResolvedPaths().codegraphDbPath;
|
|
23056
|
-
if (!
|
|
25275
|
+
if (!existsSync23(dbPath)) {
|
|
23057
25276
|
throw new Error(`CodeGraph database not found at ${dbPath}. Run 'npx @colbymchenry/codegraph sync' first.`);
|
|
23058
25277
|
}
|
|
23059
25278
|
const db = new Database2(dbPath, { readonly: true });
|
|
@@ -23062,9 +25281,9 @@ function getCodeGraphDb() {
|
|
|
23062
25281
|
}
|
|
23063
25282
|
function getDataDb() {
|
|
23064
25283
|
const dbPath = getResolvedPaths().dataDbPath;
|
|
23065
|
-
const dir =
|
|
23066
|
-
if (!
|
|
23067
|
-
|
|
25284
|
+
const dir = dirname11(dbPath);
|
|
25285
|
+
if (!existsSync23(dir)) {
|
|
25286
|
+
mkdirSync9(dir, { recursive: true });
|
|
23068
25287
|
}
|
|
23069
25288
|
const db = new Database2(dbPath);
|
|
23070
25289
|
db.pragma("journal_mode = WAL");
|
|
@@ -23334,14 +25553,14 @@ function isPythonDataStale(dataDb2, pythonRoot) {
|
|
|
23334
25553
|
const lastBuildTime = new Date(lastBuild.value).getTime();
|
|
23335
25554
|
function checkDir(dir) {
|
|
23336
25555
|
try {
|
|
23337
|
-
const entries =
|
|
25556
|
+
const entries = readdirSync14(dir, { withFileTypes: true });
|
|
23338
25557
|
for (const entry of entries) {
|
|
23339
|
-
const fullPath =
|
|
25558
|
+
const fullPath = join11(dir, entry.name);
|
|
23340
25559
|
if (entry.isDirectory()) {
|
|
23341
25560
|
if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
|
|
23342
25561
|
if (checkDir(fullPath)) return true;
|
|
23343
25562
|
} else if (entry.name.endsWith(".py")) {
|
|
23344
|
-
if (
|
|
25563
|
+
if (statSync11(fullPath).mtimeMs > lastBuildTime) return true;
|
|
23345
25564
|
}
|
|
23346
25565
|
}
|
|
23347
25566
|
} catch {
|
|
@@ -23437,8 +25656,8 @@ var init_rules = __esm({
|
|
|
23437
25656
|
});
|
|
23438
25657
|
|
|
23439
25658
|
// src/import-resolver.ts
|
|
23440
|
-
import { readFileSync as
|
|
23441
|
-
import { resolve as resolve20, dirname as
|
|
25659
|
+
import { readFileSync as readFileSync22, existsSync as existsSync24, statSync as statSync12 } from "fs";
|
|
25660
|
+
import { resolve as resolve20, dirname as dirname12, join as join12 } from "path";
|
|
23442
25661
|
function parseImports(source) {
|
|
23443
25662
|
const imports = [];
|
|
23444
25663
|
const lines = source.split("\n");
|
|
@@ -23496,21 +25715,21 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
23496
25715
|
const paths = getResolvedPaths();
|
|
23497
25716
|
basePath = resolve20(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
23498
25717
|
} else {
|
|
23499
|
-
basePath = resolve20(
|
|
25718
|
+
basePath = resolve20(dirname12(fromFile), specifier);
|
|
23500
25719
|
}
|
|
23501
|
-
if (
|
|
25720
|
+
if (existsSync24(basePath) && !isDirectory(basePath)) {
|
|
23502
25721
|
return toRelative(basePath);
|
|
23503
25722
|
}
|
|
23504
25723
|
const resolvedPaths = getResolvedPaths();
|
|
23505
25724
|
for (const ext of resolvedPaths.extensions) {
|
|
23506
25725
|
const withExt = basePath + ext;
|
|
23507
|
-
if (
|
|
25726
|
+
if (existsSync24(withExt)) {
|
|
23508
25727
|
return toRelative(withExt);
|
|
23509
25728
|
}
|
|
23510
25729
|
}
|
|
23511
25730
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
23512
|
-
const indexPath =
|
|
23513
|
-
if (
|
|
25731
|
+
const indexPath = join12(basePath, indexFile);
|
|
25732
|
+
if (existsSync24(indexPath)) {
|
|
23514
25733
|
return toRelative(indexPath);
|
|
23515
25734
|
}
|
|
23516
25735
|
}
|
|
@@ -23518,7 +25737,7 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
23518
25737
|
}
|
|
23519
25738
|
function isDirectory(path) {
|
|
23520
25739
|
try {
|
|
23521
|
-
return
|
|
25740
|
+
return statSync12(path).isDirectory();
|
|
23522
25741
|
} catch {
|
|
23523
25742
|
return false;
|
|
23524
25743
|
}
|
|
@@ -23547,10 +25766,10 @@ function buildImportIndex(dataDb2, codegraphDb2) {
|
|
|
23547
25766
|
let batch = [];
|
|
23548
25767
|
for (const file of files) {
|
|
23549
25768
|
const absPath = ensureWithinRoot(resolve20(projectRoot, file.path), projectRoot);
|
|
23550
|
-
if (!
|
|
25769
|
+
if (!existsSync24(absPath)) continue;
|
|
23551
25770
|
let source;
|
|
23552
25771
|
try {
|
|
23553
|
-
source =
|
|
25772
|
+
source = readFileSync22(absPath, "utf-8");
|
|
23554
25773
|
} catch {
|
|
23555
25774
|
continue;
|
|
23556
25775
|
}
|
|
@@ -23586,15 +25805,15 @@ var init_import_resolver = __esm({
|
|
|
23586
25805
|
});
|
|
23587
25806
|
|
|
23588
25807
|
// src/trpc-index.ts
|
|
23589
|
-
import { readFileSync as
|
|
23590
|
-
import { resolve as resolve21, join as
|
|
25808
|
+
import { readFileSync as readFileSync23, existsSync as existsSync25, readdirSync as readdirSync15 } from "fs";
|
|
25809
|
+
import { resolve as resolve21, join as join13 } from "path";
|
|
23591
25810
|
function parseRootRouter() {
|
|
23592
25811
|
const paths = getResolvedPaths();
|
|
23593
25812
|
const rootPath = paths.rootRouterPath;
|
|
23594
|
-
if (!
|
|
25813
|
+
if (!existsSync25(rootPath)) {
|
|
23595
25814
|
throw new Error(`Root router not found at ${rootPath}`);
|
|
23596
25815
|
}
|
|
23597
|
-
const source =
|
|
25816
|
+
const source = readFileSync23(rootPath, "utf-8");
|
|
23598
25817
|
const mappings = [];
|
|
23599
25818
|
const importMap = /* @__PURE__ */ new Map();
|
|
23600
25819
|
const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
|
|
@@ -23606,12 +25825,12 @@ function parseRootRouter() {
|
|
|
23606
25825
|
for (const ext of [".ts", ".tsx", ""]) {
|
|
23607
25826
|
const candidate = fullPath + ext;
|
|
23608
25827
|
const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
|
|
23609
|
-
if (
|
|
25828
|
+
if (existsSync25(candidate)) {
|
|
23610
25829
|
filePath = routersRelPath + "/" + filePath + ext;
|
|
23611
25830
|
break;
|
|
23612
25831
|
}
|
|
23613
|
-
const indexCandidate =
|
|
23614
|
-
if (
|
|
25832
|
+
const indexCandidate = join13(fullPath, "index.ts");
|
|
25833
|
+
if (existsSync25(indexCandidate)) {
|
|
23615
25834
|
filePath = routersRelPath + "/" + filePath + "/index.ts";
|
|
23616
25835
|
break;
|
|
23617
25836
|
}
|
|
@@ -23631,8 +25850,8 @@ function parseRootRouter() {
|
|
|
23631
25850
|
}
|
|
23632
25851
|
function extractProcedures(routerFilePath) {
|
|
23633
25852
|
const absPath = resolve21(getProjectRoot(), routerFilePath);
|
|
23634
|
-
if (!
|
|
23635
|
-
const source =
|
|
25853
|
+
if (!existsSync25(absPath)) return [];
|
|
25854
|
+
const source = readFileSync23(absPath, "utf-8");
|
|
23636
25855
|
const procedures = [];
|
|
23637
25856
|
const seen = /* @__PURE__ */ new Set();
|
|
23638
25857
|
const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
|
|
@@ -23661,21 +25880,21 @@ function findUICallSites(routerKey, procedureName) {
|
|
|
23661
25880
|
];
|
|
23662
25881
|
const searchPattern = `api.${routerKey}.${procedureName}`;
|
|
23663
25882
|
for (const dir of searchDirs) {
|
|
23664
|
-
if (!
|
|
25883
|
+
if (!existsSync25(dir)) continue;
|
|
23665
25884
|
searchDirectory(dir, searchPattern, callSites);
|
|
23666
25885
|
}
|
|
23667
25886
|
return callSites;
|
|
23668
25887
|
}
|
|
23669
25888
|
function searchDirectory(dir, pattern, results) {
|
|
23670
|
-
const entries =
|
|
25889
|
+
const entries = readdirSync15(dir, { withFileTypes: true });
|
|
23671
25890
|
for (const entry of entries) {
|
|
23672
|
-
const fullPath =
|
|
25891
|
+
const fullPath = join13(dir, entry.name);
|
|
23673
25892
|
if (entry.isDirectory()) {
|
|
23674
25893
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
23675
25894
|
searchDirectory(fullPath, pattern, results);
|
|
23676
25895
|
} else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
23677
25896
|
try {
|
|
23678
|
-
const source =
|
|
25897
|
+
const source = readFileSync23(fullPath, "utf-8");
|
|
23679
25898
|
const lines = source.split("\n");
|
|
23680
25899
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
23681
25900
|
if (lines[i2].includes(pattern)) {
|
|
@@ -23738,7 +25957,7 @@ var init_trpc_index = __esm({
|
|
|
23738
25957
|
});
|
|
23739
25958
|
|
|
23740
25959
|
// src/page-deps.ts
|
|
23741
|
-
import { readFileSync as
|
|
25960
|
+
import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
|
|
23742
25961
|
import { resolve as resolve22 } from "path";
|
|
23743
25962
|
function deriveRoute(pageFile) {
|
|
23744
25963
|
let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
|
|
@@ -23778,9 +25997,9 @@ function findRouterCalls(files) {
|
|
|
23778
25997
|
const projectRoot = getProjectRoot();
|
|
23779
25998
|
for (const file of files) {
|
|
23780
25999
|
const absPath = ensureWithinRoot(resolve22(projectRoot, file), projectRoot);
|
|
23781
|
-
if (!
|
|
26000
|
+
if (!existsSync26(absPath)) continue;
|
|
23782
26001
|
try {
|
|
23783
|
-
const source =
|
|
26002
|
+
const source = readFileSync24(absPath, "utf-8");
|
|
23784
26003
|
const apiCallRegex = /api\.(\w+)\.\w+/g;
|
|
23785
26004
|
let match;
|
|
23786
26005
|
while ((match = apiCallRegex.exec(source)) !== null) {
|
|
@@ -23799,9 +26018,9 @@ function findTablesFromRouters(routerNames, dataDb2) {
|
|
|
23799
26018
|
).all(routerName);
|
|
23800
26019
|
for (const proc of procs) {
|
|
23801
26020
|
const absPath = ensureWithinRoot(resolve22(getProjectRoot(), proc.router_file), getProjectRoot());
|
|
23802
|
-
if (!
|
|
26021
|
+
if (!existsSync26(absPath)) continue;
|
|
23803
26022
|
try {
|
|
23804
|
-
const source =
|
|
26023
|
+
const source = readFileSync24(absPath, "utf-8");
|
|
23805
26024
|
const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
|
|
23806
26025
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
23807
26026
|
const tableRegex = new RegExp(regexStr + "\\.", "g");
|
|
@@ -24070,14 +26289,14 @@ var init_domains = __esm({
|
|
|
24070
26289
|
});
|
|
24071
26290
|
|
|
24072
26291
|
// src/schema-mapper.ts
|
|
24073
|
-
import { readFileSync as
|
|
24074
|
-
import { join as
|
|
26292
|
+
import { readFileSync as readFileSync25, existsSync as existsSync27, readdirSync as readdirSync16 } from "fs";
|
|
26293
|
+
import { join as join14 } from "path";
|
|
24075
26294
|
function parsePrismaSchema() {
|
|
24076
26295
|
const schemaPath = getResolvedPaths().prismaSchemaPath;
|
|
24077
|
-
if (!
|
|
26296
|
+
if (!existsSync27(schemaPath)) {
|
|
24078
26297
|
throw new Error(`Prisma schema not found at ${schemaPath}`);
|
|
24079
26298
|
}
|
|
24080
|
-
const source =
|
|
26299
|
+
const source = readFileSync25(schemaPath, "utf-8");
|
|
24081
26300
|
const models = [];
|
|
24082
26301
|
const sourceLines = source.split("\n");
|
|
24083
26302
|
let i2 = 0;
|
|
@@ -24135,14 +26354,14 @@ function toSnakeCase(str) {
|
|
|
24135
26354
|
function findColumnUsageInRouters(tableName) {
|
|
24136
26355
|
const usage = /* @__PURE__ */ new Map();
|
|
24137
26356
|
const routersDir = getResolvedPaths().routersDir;
|
|
24138
|
-
if (!
|
|
26357
|
+
if (!existsSync27(routersDir)) return usage;
|
|
24139
26358
|
scanDirectory(routersDir, tableName, usage);
|
|
24140
26359
|
return usage;
|
|
24141
26360
|
}
|
|
24142
26361
|
function scanDirectory(dir, tableName, usage) {
|
|
24143
|
-
const entries =
|
|
26362
|
+
const entries = readdirSync16(dir, { withFileTypes: true });
|
|
24144
26363
|
for (const entry of entries) {
|
|
24145
|
-
const fullPath =
|
|
26364
|
+
const fullPath = join14(dir, entry.name);
|
|
24146
26365
|
if (entry.isDirectory()) {
|
|
24147
26366
|
scanDirectory(fullPath, tableName, usage);
|
|
24148
26367
|
} else if (entry.name.endsWith(".ts")) {
|
|
@@ -24152,7 +26371,7 @@ function scanDirectory(dir, tableName, usage) {
|
|
|
24152
26371
|
}
|
|
24153
26372
|
function scanFile(absPath, tableName, usage) {
|
|
24154
26373
|
try {
|
|
24155
|
-
const source =
|
|
26374
|
+
const source = readFileSync25(absPath, "utf-8");
|
|
24156
26375
|
if (!source.includes(tableName)) return;
|
|
24157
26376
|
const relPath = absPath.slice(getProjectRoot().length + 1);
|
|
24158
26377
|
const lines = source.split("\n");
|
|
@@ -24197,15 +26416,15 @@ function detectMismatches(models) {
|
|
|
24197
26416
|
}
|
|
24198
26417
|
function findFilesUsingColumn(dir, column, tableName) {
|
|
24199
26418
|
const result = [];
|
|
24200
|
-
if (!
|
|
24201
|
-
const entries =
|
|
26419
|
+
if (!existsSync27(dir)) return result;
|
|
26420
|
+
const entries = readdirSync16(dir, { withFileTypes: true });
|
|
24202
26421
|
for (const entry of entries) {
|
|
24203
|
-
const fullPath =
|
|
26422
|
+
const fullPath = join14(dir, entry.name);
|
|
24204
26423
|
if (entry.isDirectory()) {
|
|
24205
26424
|
result.push(...findFilesUsingColumn(fullPath, column, tableName));
|
|
24206
26425
|
} else if (entry.name.endsWith(".ts")) {
|
|
24207
26426
|
try {
|
|
24208
|
-
const source =
|
|
26427
|
+
const source = readFileSync25(fullPath, "utf-8");
|
|
24209
26428
|
if (source.includes(tableName) && source.includes(column)) {
|
|
24210
26429
|
result.push(fullPath.slice(getProjectRoot().length + 1));
|
|
24211
26430
|
}
|
|
@@ -24350,48 +26569,48 @@ var init_import_parser = __esm({
|
|
|
24350
26569
|
});
|
|
24351
26570
|
|
|
24352
26571
|
// src/python/import-resolver.ts
|
|
24353
|
-
import { readFileSync as
|
|
24354
|
-
import { resolve as resolve24, join as
|
|
26572
|
+
import { readFileSync as readFileSync26, existsSync as existsSync28, readdirSync as readdirSync17 } from "fs";
|
|
26573
|
+
import { resolve as resolve24, join as join15, relative as relative6, dirname as dirname13 } from "path";
|
|
24355
26574
|
function resolvePythonModulePath(module2, fromFile, pythonRoot, level) {
|
|
24356
26575
|
const projectRoot = getProjectRoot();
|
|
24357
26576
|
if (level > 0) {
|
|
24358
|
-
let baseDir =
|
|
26577
|
+
let baseDir = dirname13(resolve24(projectRoot, fromFile));
|
|
24359
26578
|
for (let i2 = 1; i2 < level; i2++) {
|
|
24360
|
-
baseDir =
|
|
26579
|
+
baseDir = dirname13(baseDir);
|
|
24361
26580
|
}
|
|
24362
26581
|
const modulePart = module2.replace(/^\.+/, "");
|
|
24363
26582
|
if (modulePart) {
|
|
24364
26583
|
const parts3 = modulePart.split(".");
|
|
24365
|
-
return tryResolvePythonPath(
|
|
26584
|
+
return tryResolvePythonPath(join15(baseDir, ...parts3), projectRoot);
|
|
24366
26585
|
}
|
|
24367
26586
|
return tryResolvePythonPath(baseDir, projectRoot);
|
|
24368
26587
|
}
|
|
24369
26588
|
const parts2 = module2.split(".");
|
|
24370
|
-
const candidate =
|
|
26589
|
+
const candidate = join15(resolve24(projectRoot, pythonRoot), ...parts2);
|
|
24371
26590
|
return tryResolvePythonPath(candidate, projectRoot);
|
|
24372
26591
|
}
|
|
24373
26592
|
function tryResolvePythonPath(basePath, projectRoot) {
|
|
24374
|
-
if (
|
|
24375
|
-
return
|
|
26593
|
+
if (existsSync28(basePath + ".py")) {
|
|
26594
|
+
return relative6(projectRoot, basePath + ".py");
|
|
24376
26595
|
}
|
|
24377
|
-
if (
|
|
24378
|
-
return
|
|
26596
|
+
if (existsSync28(join15(basePath, "__init__.py"))) {
|
|
26597
|
+
return relative6(projectRoot, join15(basePath, "__init__.py"));
|
|
24379
26598
|
}
|
|
24380
|
-
if (basePath.endsWith(".py") &&
|
|
24381
|
-
return
|
|
26599
|
+
if (basePath.endsWith(".py") && existsSync28(basePath)) {
|
|
26600
|
+
return relative6(projectRoot, basePath);
|
|
24382
26601
|
}
|
|
24383
26602
|
return null;
|
|
24384
26603
|
}
|
|
24385
26604
|
function walkPythonFiles(dir, excludeDirs) {
|
|
24386
26605
|
const files = [];
|
|
24387
26606
|
try {
|
|
24388
|
-
const entries =
|
|
26607
|
+
const entries = readdirSync17(dir, { withFileTypes: true });
|
|
24389
26608
|
for (const entry of entries) {
|
|
24390
26609
|
if (entry.isDirectory()) {
|
|
24391
26610
|
if (excludeDirs.includes(entry.name)) continue;
|
|
24392
|
-
files.push(...walkPythonFiles(
|
|
26611
|
+
files.push(...walkPythonFiles(join15(dir, entry.name), excludeDirs));
|
|
24393
26612
|
} else if (entry.name.endsWith(".py")) {
|
|
24394
|
-
files.push(
|
|
26613
|
+
files.push(join15(dir, entry.name));
|
|
24395
26614
|
}
|
|
24396
26615
|
}
|
|
24397
26616
|
} catch {
|
|
@@ -24414,10 +26633,10 @@ function buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__
|
|
|
24414
26633
|
});
|
|
24415
26634
|
const batch = [];
|
|
24416
26635
|
for (const absFile of files) {
|
|
24417
|
-
const relFile =
|
|
26636
|
+
const relFile = relative6(projectRoot, absFile);
|
|
24418
26637
|
let source;
|
|
24419
26638
|
try {
|
|
24420
|
-
source =
|
|
26639
|
+
source = readFileSync26(absFile, "utf-8");
|
|
24421
26640
|
} catch {
|
|
24422
26641
|
continue;
|
|
24423
26642
|
}
|
|
@@ -24683,18 +26902,18 @@ var init_route_parser = __esm({
|
|
|
24683
26902
|
});
|
|
24684
26903
|
|
|
24685
26904
|
// src/python/route-indexer.ts
|
|
24686
|
-
import { readFileSync as
|
|
24687
|
-
import { join as
|
|
26905
|
+
import { readFileSync as readFileSync27, readdirSync as readdirSync18 } from "fs";
|
|
26906
|
+
import { join as join16, relative as relative7 } from "path";
|
|
24688
26907
|
function walkPyFiles(dir, excludeDirs) {
|
|
24689
26908
|
const files = [];
|
|
24690
26909
|
try {
|
|
24691
|
-
const entries =
|
|
26910
|
+
const entries = readdirSync18(dir, { withFileTypes: true });
|
|
24692
26911
|
for (const entry of entries) {
|
|
24693
26912
|
if (entry.isDirectory()) {
|
|
24694
26913
|
if (excludeDirs.includes(entry.name)) continue;
|
|
24695
|
-
files.push(...walkPyFiles(
|
|
26914
|
+
files.push(...walkPyFiles(join16(dir, entry.name), excludeDirs));
|
|
24696
26915
|
} else if (entry.name.endsWith(".py")) {
|
|
24697
|
-
files.push(
|
|
26916
|
+
files.push(join16(dir, entry.name));
|
|
24698
26917
|
}
|
|
24699
26918
|
}
|
|
24700
26919
|
} catch {
|
|
@@ -24703,7 +26922,7 @@ function walkPyFiles(dir, excludeDirs) {
|
|
|
24703
26922
|
}
|
|
24704
26923
|
function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
24705
26924
|
const projectRoot = getProjectRoot();
|
|
24706
|
-
const absRoot =
|
|
26925
|
+
const absRoot = join16(projectRoot, pythonRoot);
|
|
24707
26926
|
dataDb2.exec("DELETE FROM massu_py_routes");
|
|
24708
26927
|
dataDb2.exec("DELETE FROM massu_py_route_callers");
|
|
24709
26928
|
const insertStmt = dataDb2.prepare(
|
|
@@ -24713,10 +26932,10 @@ function buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
|
|
|
24713
26932
|
let count = 0;
|
|
24714
26933
|
dataDb2.transaction(() => {
|
|
24715
26934
|
for (const absFile of files) {
|
|
24716
|
-
const relFile =
|
|
26935
|
+
const relFile = relative7(projectRoot, absFile);
|
|
24717
26936
|
let source;
|
|
24718
26937
|
try {
|
|
24719
|
-
source =
|
|
26938
|
+
source = readFileSync27(absFile, "utf-8");
|
|
24720
26939
|
} catch {
|
|
24721
26940
|
continue;
|
|
24722
26941
|
}
|
|
@@ -24927,18 +27146,18 @@ var init_model_parser = __esm({
|
|
|
24927
27146
|
});
|
|
24928
27147
|
|
|
24929
27148
|
// src/python/model-indexer.ts
|
|
24930
|
-
import { readFileSync as
|
|
24931
|
-
import { join as
|
|
27149
|
+
import { readFileSync as readFileSync28, readdirSync as readdirSync19 } from "fs";
|
|
27150
|
+
import { join as join17, relative as relative8 } from "path";
|
|
24932
27151
|
function walkPyFiles2(dir, excludeDirs) {
|
|
24933
27152
|
const files = [];
|
|
24934
27153
|
try {
|
|
24935
|
-
const entries =
|
|
27154
|
+
const entries = readdirSync19(dir, { withFileTypes: true });
|
|
24936
27155
|
for (const entry of entries) {
|
|
24937
27156
|
if (entry.isDirectory()) {
|
|
24938
27157
|
if (excludeDirs.includes(entry.name)) continue;
|
|
24939
|
-
files.push(...walkPyFiles2(
|
|
27158
|
+
files.push(...walkPyFiles2(join17(dir, entry.name), excludeDirs));
|
|
24940
27159
|
} else if (entry.name.endsWith(".py")) {
|
|
24941
|
-
files.push(
|
|
27160
|
+
files.push(join17(dir, entry.name));
|
|
24942
27161
|
}
|
|
24943
27162
|
}
|
|
24944
27163
|
} catch {
|
|
@@ -24947,7 +27166,7 @@ function walkPyFiles2(dir, excludeDirs) {
|
|
|
24947
27166
|
}
|
|
24948
27167
|
function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
|
|
24949
27168
|
const projectRoot = getProjectRoot();
|
|
24950
|
-
const absRoot =
|
|
27169
|
+
const absRoot = join17(projectRoot, pythonRoot);
|
|
24951
27170
|
dataDb2.exec("DELETE FROM massu_py_models");
|
|
24952
27171
|
dataDb2.exec("DELETE FROM massu_py_fk_edges");
|
|
24953
27172
|
const insertModel = dataDb2.prepare(
|
|
@@ -24960,10 +27179,10 @@ function buildPythonModelIndex(dataDb2, pythonRoot, excludeDirs = ["__pycache__"
|
|
|
24960
27179
|
let count = 0;
|
|
24961
27180
|
dataDb2.transaction(() => {
|
|
24962
27181
|
for (const absFile of files) {
|
|
24963
|
-
const relFile =
|
|
27182
|
+
const relFile = relative8(projectRoot, absFile);
|
|
24964
27183
|
let source;
|
|
24965
27184
|
try {
|
|
24966
|
-
source =
|
|
27185
|
+
source = readFileSync28(absFile, "utf-8");
|
|
24967
27186
|
} catch {
|
|
24968
27187
|
continue;
|
|
24969
27188
|
}
|
|
@@ -25223,19 +27442,19 @@ var init_migration_parser = __esm({
|
|
|
25223
27442
|
});
|
|
25224
27443
|
|
|
25225
27444
|
// src/python/migration-indexer.ts
|
|
25226
|
-
import { readFileSync as
|
|
25227
|
-
import { join as
|
|
27445
|
+
import { readFileSync as readFileSync29, readdirSync as readdirSync20 } from "fs";
|
|
27446
|
+
import { join as join18, relative as relative9 } from "path";
|
|
25228
27447
|
function buildPythonMigrationIndex(dataDb2, alembicDir) {
|
|
25229
27448
|
const projectRoot = getProjectRoot();
|
|
25230
|
-
const absDir =
|
|
27449
|
+
const absDir = join18(projectRoot, alembicDir);
|
|
25231
27450
|
dataDb2.exec("DELETE FROM massu_py_migrations");
|
|
25232
|
-
const versionsDir =
|
|
27451
|
+
const versionsDir = join18(absDir, "versions");
|
|
25233
27452
|
let files = [];
|
|
25234
27453
|
try {
|
|
25235
|
-
files =
|
|
27454
|
+
files = readdirSync20(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join18(versionsDir, f2));
|
|
25236
27455
|
} catch {
|
|
25237
27456
|
try {
|
|
25238
|
-
files =
|
|
27457
|
+
files = readdirSync20(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join18(absDir, f2));
|
|
25239
27458
|
} catch {
|
|
25240
27459
|
}
|
|
25241
27460
|
}
|
|
@@ -25249,7 +27468,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
|
|
|
25249
27468
|
for (const absFile of files) {
|
|
25250
27469
|
let source;
|
|
25251
27470
|
try {
|
|
25252
|
-
source =
|
|
27471
|
+
source = readFileSync29(absFile, "utf-8");
|
|
25253
27472
|
} catch {
|
|
25254
27473
|
continue;
|
|
25255
27474
|
}
|
|
@@ -25260,7 +27479,7 @@ function buildPythonMigrationIndex(dataDb2, alembicDir) {
|
|
|
25260
27479
|
rows.push({
|
|
25261
27480
|
revision: parsed.revision,
|
|
25262
27481
|
downRevision: parsed.downRevision,
|
|
25263
|
-
file:
|
|
27482
|
+
file: relative9(projectRoot, absFile),
|
|
25264
27483
|
description: parsed.description,
|
|
25265
27484
|
operations: JSON.stringify(parsed.operations)
|
|
25266
27485
|
});
|
|
@@ -25283,12 +27502,12 @@ var init_migration_indexer = __esm({
|
|
|
25283
27502
|
});
|
|
25284
27503
|
|
|
25285
27504
|
// src/python/coupling-detector.ts
|
|
25286
|
-
import { readFileSync as
|
|
25287
|
-
import { join as
|
|
27505
|
+
import { readFileSync as readFileSync30, readdirSync as readdirSync21 } from "fs";
|
|
27506
|
+
import { join as join19, relative as relative10 } from "path";
|
|
25288
27507
|
function buildPythonCouplingIndex(dataDb2) {
|
|
25289
27508
|
const projectRoot = getProjectRoot();
|
|
25290
27509
|
const config = getConfig();
|
|
25291
|
-
const srcDir =
|
|
27510
|
+
const srcDir = join19(projectRoot, config.paths.source);
|
|
25292
27511
|
const routes = dataDb2.prepare("SELECT id, method, path FROM massu_py_routes").all();
|
|
25293
27512
|
if (routes.length === 0) return 0;
|
|
25294
27513
|
dataDb2.exec("DELETE FROM massu_py_route_callers");
|
|
@@ -25317,10 +27536,10 @@ function buildPythonCouplingIndex(dataDb2) {
|
|
|
25317
27536
|
];
|
|
25318
27537
|
dataDb2.transaction(() => {
|
|
25319
27538
|
for (const absFile of frontendFiles) {
|
|
25320
|
-
const relFile =
|
|
27539
|
+
const relFile = relative10(projectRoot, absFile);
|
|
25321
27540
|
let source;
|
|
25322
27541
|
try {
|
|
25323
|
-
source =
|
|
27542
|
+
source = readFileSync30(absFile, "utf-8");
|
|
25324
27543
|
} catch {
|
|
25325
27544
|
continue;
|
|
25326
27545
|
}
|
|
@@ -25348,13 +27567,13 @@ function walkFrontendFiles(dir) {
|
|
|
25348
27567
|
const files = [];
|
|
25349
27568
|
const exclude = ["node_modules", ".next", "dist", ".git", "__pycache__", ".venv", "venv"];
|
|
25350
27569
|
try {
|
|
25351
|
-
const entries =
|
|
27570
|
+
const entries = readdirSync21(dir, { withFileTypes: true });
|
|
25352
27571
|
for (const entry of entries) {
|
|
25353
27572
|
if (entry.isDirectory()) {
|
|
25354
27573
|
if (exclude.includes(entry.name)) continue;
|
|
25355
|
-
files.push(...walkFrontendFiles(
|
|
27574
|
+
files.push(...walkFrontendFiles(join19(dir, entry.name)));
|
|
25356
27575
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
25357
|
-
files.push(
|
|
27576
|
+
files.push(join19(dir, entry.name));
|
|
25358
27577
|
}
|
|
25359
27578
|
}
|
|
25360
27579
|
} catch {
|
|
@@ -25725,8 +27944,8 @@ var init_memory_tools = __esm({
|
|
|
25725
27944
|
});
|
|
25726
27945
|
|
|
25727
27946
|
// src/docs-tools.ts
|
|
25728
|
-
import { readFileSync as
|
|
25729
|
-
import { resolve as resolve25, basename as
|
|
27947
|
+
import { readFileSync as readFileSync31, existsSync as existsSync29 } from "fs";
|
|
27948
|
+
import { resolve as resolve25, basename as basename6 } from "path";
|
|
25730
27949
|
function p3(baseName) {
|
|
25731
27950
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
25732
27951
|
}
|
|
@@ -25781,10 +28000,10 @@ function handleDocsToolCall(name2, args3) {
|
|
|
25781
28000
|
}
|
|
25782
28001
|
function loadDocsMap() {
|
|
25783
28002
|
const mapPath = getResolvedPaths().docsMapPath;
|
|
25784
|
-
if (!
|
|
28003
|
+
if (!existsSync29(mapPath)) {
|
|
25785
28004
|
throw new Error(`docs-map.json not found at ${mapPath}`);
|
|
25786
28005
|
}
|
|
25787
|
-
return JSON.parse(
|
|
28006
|
+
return JSON.parse(readFileSync31(mapPath, "utf-8"));
|
|
25788
28007
|
}
|
|
25789
28008
|
function matchesPattern(filePath, pattern) {
|
|
25790
28009
|
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
@@ -25793,7 +28012,7 @@ function matchesPattern(filePath, pattern) {
|
|
|
25793
28012
|
function findAffectedMappings(docsMap, changedFiles) {
|
|
25794
28013
|
const affected = /* @__PURE__ */ new Map();
|
|
25795
28014
|
for (const file of changedFiles) {
|
|
25796
|
-
const fileName =
|
|
28015
|
+
const fileName = basename6(file);
|
|
25797
28016
|
for (const mapping of docsMap.mappings) {
|
|
25798
28017
|
let matched = false;
|
|
25799
28018
|
for (const routePattern of mapping.appRoutes) {
|
|
@@ -25855,12 +28074,12 @@ function extractFrontmatter(content) {
|
|
|
25855
28074
|
function extractProcedureNames(routerPath) {
|
|
25856
28075
|
const root = getProjectRoot();
|
|
25857
28076
|
const absPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "..", routerPath), root);
|
|
25858
|
-
if (!
|
|
25859
|
-
const altPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "../server/api/routers",
|
|
25860
|
-
if (!
|
|
25861
|
-
return extractProcedureNamesFromContent(
|
|
28077
|
+
if (!existsSync29(absPath)) {
|
|
28078
|
+
const altPath = ensureWithinRoot(resolve25(getResolvedPaths().srcDir, "../server/api/routers", basename6(routerPath)), root);
|
|
28079
|
+
if (!existsSync29(altPath)) return [];
|
|
28080
|
+
return extractProcedureNamesFromContent(readFileSync31(altPath, "utf-8"));
|
|
25862
28081
|
}
|
|
25863
|
-
return extractProcedureNamesFromContent(
|
|
28082
|
+
return extractProcedureNamesFromContent(readFileSync31(absPath, "utf-8"));
|
|
25864
28083
|
}
|
|
25865
28084
|
function extractProcedureNamesFromContent(content) {
|
|
25866
28085
|
const procRegex = /\.(?:query|mutation)\s*\(/g;
|
|
@@ -25901,7 +28120,7 @@ function handleDocsAudit(args3) {
|
|
|
25901
28120
|
const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
|
|
25902
28121
|
if (!mapping) continue;
|
|
25903
28122
|
const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
25904
|
-
if (!
|
|
28123
|
+
if (!existsSync29(helpPagePath)) {
|
|
25905
28124
|
results.push({
|
|
25906
28125
|
helpPage: mapping.helpPage,
|
|
25907
28126
|
mappingId,
|
|
@@ -25913,12 +28132,12 @@ function handleDocsAudit(args3) {
|
|
|
25913
28132
|
});
|
|
25914
28133
|
continue;
|
|
25915
28134
|
}
|
|
25916
|
-
const content =
|
|
28135
|
+
const content = readFileSync31(helpPagePath, "utf-8");
|
|
25917
28136
|
const sections = extractSections(content);
|
|
25918
28137
|
const frontmatter = extractFrontmatter(content);
|
|
25919
28138
|
const staleReasons = [];
|
|
25920
28139
|
for (const file of triggeringFiles) {
|
|
25921
|
-
const fileName =
|
|
28140
|
+
const fileName = basename6(file);
|
|
25922
28141
|
if (mapping.routers.includes(fileName)) {
|
|
25923
28142
|
const procedures = extractProcedureNames(file);
|
|
25924
28143
|
const undocumented = procedures.filter((p19) => !contentMentions(content, p19));
|
|
@@ -25949,13 +28168,13 @@ function handleDocsAudit(args3) {
|
|
|
25949
28168
|
reason: staleReasons.length > 0 ? staleReasons.join("; ") : "Content appears current",
|
|
25950
28169
|
sections,
|
|
25951
28170
|
changedFiles: triggeringFiles,
|
|
25952
|
-
suggestedAction: status === "STALE" ? `Review and update ${mapping.helpPage} to reflect changes in: ${triggeringFiles.map((f2) =>
|
|
28171
|
+
suggestedAction: status === "STALE" ? `Review and update ${mapping.helpPage} to reflect changes in: ${triggeringFiles.map((f2) => basename6(f2)).join(", ")}` : "No action needed"
|
|
25953
28172
|
});
|
|
25954
28173
|
for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
|
|
25955
28174
|
if (parentId === mappingId) {
|
|
25956
28175
|
const guidePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
|
|
25957
|
-
if (
|
|
25958
|
-
const guideContent =
|
|
28176
|
+
if (existsSync29(guidePath)) {
|
|
28177
|
+
const guideContent = readFileSync31(guidePath, "utf-8");
|
|
25959
28178
|
const guideFrontmatter = extractFrontmatter(guideContent);
|
|
25960
28179
|
if (!guideFrontmatter?.lastVerified || status === "STALE") {
|
|
25961
28180
|
results.push({
|
|
@@ -25989,13 +28208,13 @@ function handleDocsCoverage(args3) {
|
|
|
25989
28208
|
const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
|
|
25990
28209
|
for (const mapping of mappings) {
|
|
25991
28210
|
const helpPagePath = ensureWithinRoot(resolve25(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
|
|
25992
|
-
const exists =
|
|
28211
|
+
const exists = existsSync29(helpPagePath);
|
|
25993
28212
|
let hasContent = false;
|
|
25994
28213
|
let lineCount = 0;
|
|
25995
28214
|
let lastVerified = null;
|
|
25996
28215
|
let status = null;
|
|
25997
28216
|
if (exists) {
|
|
25998
|
-
const content =
|
|
28217
|
+
const content = readFileSync31(helpPagePath, "utf-8");
|
|
25999
28218
|
lineCount = content.split("\n").length;
|
|
26000
28219
|
hasContent = lineCount > 10;
|
|
26001
28220
|
const frontmatter = extractFrontmatter(content);
|
|
@@ -26336,7 +28555,7 @@ var init_observability_tools = __esm({
|
|
|
26336
28555
|
});
|
|
26337
28556
|
|
|
26338
28557
|
// src/sentinel-db.ts
|
|
26339
|
-
import { existsSync as
|
|
28558
|
+
import { existsSync as existsSync30 } from "fs";
|
|
26340
28559
|
import { resolve as resolve26 } from "path";
|
|
26341
28560
|
function parsePortalScope(raw) {
|
|
26342
28561
|
if (!raw) return [];
|
|
@@ -26574,22 +28793,22 @@ function validateFeatures(db, domainFilter) {
|
|
|
26574
28793
|
const missingPages = [];
|
|
26575
28794
|
for (const comp of components) {
|
|
26576
28795
|
const absPath = resolve26(PROJECT_ROOT, comp.component_file);
|
|
26577
|
-
if (!
|
|
28796
|
+
if (!existsSync30(absPath)) {
|
|
26578
28797
|
missingComponents.push(comp.component_file);
|
|
26579
28798
|
}
|
|
26580
28799
|
}
|
|
26581
28800
|
for (const proc of procedures) {
|
|
26582
28801
|
const routerPath = resolve26(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
|
|
26583
|
-
if (!
|
|
28802
|
+
if (!existsSync30(routerPath)) {
|
|
26584
28803
|
missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
|
|
26585
28804
|
}
|
|
26586
28805
|
}
|
|
26587
28806
|
for (const page of pages) {
|
|
26588
28807
|
const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
|
|
26589
28808
|
const absPath = resolve26(PROJECT_ROOT, routeToPath);
|
|
26590
|
-
if (page.page_route.startsWith("/") && !
|
|
28809
|
+
if (page.page_route.startsWith("/") && !existsSync30(absPath)) {
|
|
26591
28810
|
const altPath = resolve26(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
|
|
26592
|
-
if (!
|
|
28811
|
+
if (!existsSync30(altPath)) {
|
|
26593
28812
|
missingPages.push(page.page_route);
|
|
26594
28813
|
}
|
|
26595
28814
|
}
|
|
@@ -27113,8 +29332,8 @@ var init_sentinel_tools = __esm({
|
|
|
27113
29332
|
});
|
|
27114
29333
|
|
|
27115
29334
|
// src/sentinel-scanner.ts
|
|
27116
|
-
import { readFileSync as
|
|
27117
|
-
import { resolve as resolve27, join as
|
|
29335
|
+
import { readFileSync as readFileSync32, existsSync as existsSync31, readdirSync as readdirSync22, statSync as statSync13 } from "fs";
|
|
29336
|
+
import { resolve as resolve27, join as join20, basename as basename7, dirname as dirname14, relative as relative11 } from "path";
|
|
27118
29337
|
function inferDomain(filePath) {
|
|
27119
29338
|
const domains = getConfig().domains;
|
|
27120
29339
|
const path = filePath.toLowerCase();
|
|
@@ -27244,9 +29463,9 @@ function scanComponentExports(dataDb2) {
|
|
|
27244
29463
|
const componentsBase = config.paths.components ?? config.paths.source + "/components";
|
|
27245
29464
|
const componentDirs = [];
|
|
27246
29465
|
const basePath = resolve27(projectRoot, componentsBase);
|
|
27247
|
-
if (
|
|
29466
|
+
if (existsSync31(basePath)) {
|
|
27248
29467
|
try {
|
|
27249
|
-
const entries =
|
|
29468
|
+
const entries = readdirSync22(basePath, { withFileTypes: true });
|
|
27250
29469
|
for (const entry of entries) {
|
|
27251
29470
|
if (entry.isDirectory()) {
|
|
27252
29471
|
componentDirs.push(componentsBase + "/" + entry.name);
|
|
@@ -27257,11 +29476,11 @@ function scanComponentExports(dataDb2) {
|
|
|
27257
29476
|
}
|
|
27258
29477
|
for (const dir of componentDirs) {
|
|
27259
29478
|
const absDir = resolve27(projectRoot, dir);
|
|
27260
|
-
if (!
|
|
27261
|
-
const files =
|
|
29479
|
+
if (!existsSync31(absDir)) continue;
|
|
29480
|
+
const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
|
|
27262
29481
|
for (const file of files) {
|
|
27263
|
-
const relPath =
|
|
27264
|
-
const source =
|
|
29482
|
+
const relPath = relative11(projectRoot, file);
|
|
29483
|
+
const source = readFileSync32(file, "utf-8");
|
|
27265
29484
|
const annotations = parseFeatureAnnotations(source);
|
|
27266
29485
|
if (annotations.length > 0) {
|
|
27267
29486
|
for (const ann of annotations) {
|
|
@@ -27287,7 +29506,7 @@ function scanComponentExports(dataDb2) {
|
|
|
27287
29506
|
if (hasHandlers && exportMatch) {
|
|
27288
29507
|
const componentName = exportMatch[1];
|
|
27289
29508
|
const domain = inferDomain(relPath);
|
|
27290
|
-
const subdomain =
|
|
29509
|
+
const subdomain = basename7(dirname14(relPath));
|
|
27291
29510
|
const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
|
|
27292
29511
|
if (!annotations.some((a2) => a2.featureKey === featureKey)) {
|
|
27293
29512
|
features.push({
|
|
@@ -27307,16 +29526,16 @@ function scanComponentExports(dataDb2) {
|
|
|
27307
29526
|
}
|
|
27308
29527
|
return features;
|
|
27309
29528
|
}
|
|
27310
|
-
function
|
|
29529
|
+
function walkDir2(dir) {
|
|
27311
29530
|
const results = [];
|
|
27312
29531
|
try {
|
|
27313
|
-
const entries =
|
|
29532
|
+
const entries = readdirSync22(dir);
|
|
27314
29533
|
for (const entry of entries) {
|
|
27315
|
-
const fullPath =
|
|
29534
|
+
const fullPath = join20(dir, entry);
|
|
27316
29535
|
try {
|
|
27317
|
-
const stat =
|
|
29536
|
+
const stat = statSync13(fullPath);
|
|
27318
29537
|
if (stat.isDirectory()) {
|
|
27319
|
-
results.push(...
|
|
29538
|
+
results.push(...walkDir2(fullPath));
|
|
27320
29539
|
} else {
|
|
27321
29540
|
results.push(fullPath);
|
|
27322
29541
|
}
|
|
@@ -28300,7 +30519,7 @@ var init_audit_trail = __esm({
|
|
|
28300
30519
|
});
|
|
28301
30520
|
|
|
28302
30521
|
// src/validation-engine.ts
|
|
28303
|
-
import { existsSync as
|
|
30522
|
+
import { existsSync as existsSync32, readFileSync as readFileSync33 } from "fs";
|
|
28304
30523
|
function p10(baseName) {
|
|
28305
30524
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
28306
30525
|
}
|
|
@@ -28331,7 +30550,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
28331
30550
|
});
|
|
28332
30551
|
return checks;
|
|
28333
30552
|
}
|
|
28334
|
-
if (!
|
|
30553
|
+
if (!existsSync32(absPath)) {
|
|
28335
30554
|
checks.push({
|
|
28336
30555
|
name: "file_exists",
|
|
28337
30556
|
severity: "error",
|
|
@@ -28340,7 +30559,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
28340
30559
|
});
|
|
28341
30560
|
return checks;
|
|
28342
30561
|
}
|
|
28343
|
-
const source =
|
|
30562
|
+
const source = readFileSync33(absPath, "utf-8");
|
|
28344
30563
|
const lines = source.split("\n");
|
|
28345
30564
|
if (activeChecks.rule_compliance !== false) {
|
|
28346
30565
|
for (const ruleSet of config.rules) {
|
|
@@ -28785,7 +31004,7 @@ var init_adr_generator = __esm({
|
|
|
28785
31004
|
});
|
|
28786
31005
|
|
|
28787
31006
|
// src/security-scorer.ts
|
|
28788
|
-
import { existsSync as
|
|
31007
|
+
import { existsSync as existsSync33, readFileSync as readFileSync34 } from "fs";
|
|
28789
31008
|
function p12(baseName) {
|
|
28790
31009
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
28791
31010
|
}
|
|
@@ -28809,12 +31028,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
28809
31028
|
}]
|
|
28810
31029
|
};
|
|
28811
31030
|
}
|
|
28812
|
-
if (!
|
|
31031
|
+
if (!existsSync33(absPath)) {
|
|
28813
31032
|
return { riskScore: 0, findings: [] };
|
|
28814
31033
|
}
|
|
28815
31034
|
let source;
|
|
28816
31035
|
try {
|
|
28817
|
-
source =
|
|
31036
|
+
source = readFileSync34(absPath, "utf-8");
|
|
28818
31037
|
} catch {
|
|
28819
31038
|
return { riskScore: 0, findings: [] };
|
|
28820
31039
|
}
|
|
@@ -29103,7 +31322,7 @@ var init_security_scorer = __esm({
|
|
|
29103
31322
|
});
|
|
29104
31323
|
|
|
29105
31324
|
// src/dependency-scorer.ts
|
|
29106
|
-
import { existsSync as
|
|
31325
|
+
import { existsSync as existsSync34, readFileSync as readFileSync35 } from "fs";
|
|
29107
31326
|
import { resolve as resolve28 } from "path";
|
|
29108
31327
|
function p13(baseName) {
|
|
29109
31328
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
@@ -29137,9 +31356,9 @@ function calculateDepRisk(factors) {
|
|
|
29137
31356
|
}
|
|
29138
31357
|
function getInstalledPackages(projectRoot) {
|
|
29139
31358
|
const pkgPath = resolve28(projectRoot, "package.json");
|
|
29140
|
-
if (!
|
|
31359
|
+
if (!existsSync34(pkgPath)) return /* @__PURE__ */ new Map();
|
|
29141
31360
|
try {
|
|
29142
|
-
const pkg = JSON.parse(
|
|
31361
|
+
const pkg = JSON.parse(readFileSync35(pkgPath, "utf-8"));
|
|
29143
31362
|
const packages = /* @__PURE__ */ new Map();
|
|
29144
31363
|
for (const [name2, version] of Object.entries(pkg.dependencies ?? {})) {
|
|
29145
31364
|
packages.set(name2, version);
|
|
@@ -29741,9 +31960,9 @@ var init_regression_detector = __esm({
|
|
|
29741
31960
|
});
|
|
29742
31961
|
|
|
29743
31962
|
// src/knowledge-indexer.ts
|
|
29744
|
-
import { createHash as
|
|
29745
|
-
import { readFileSync as
|
|
29746
|
-
import { resolve as resolve29, relative as
|
|
31963
|
+
import { createHash as createHash8 } from "crypto";
|
|
31964
|
+
import { readFileSync as readFileSync36, readdirSync as readdirSync23, statSync as statSync14, existsSync as existsSync35 } from "fs";
|
|
31965
|
+
import { resolve as resolve29, relative as relative12, basename as basename8, extname as extname2 } from "path";
|
|
29747
31966
|
function getKnowledgePaths() {
|
|
29748
31967
|
const resolved = getResolvedPaths();
|
|
29749
31968
|
const config = getConfig();
|
|
@@ -29769,7 +31988,7 @@ function discoverMarkdownFiles(baseDir) {
|
|
|
29769
31988
|
const files = [];
|
|
29770
31989
|
function walk(dir) {
|
|
29771
31990
|
try {
|
|
29772
|
-
const entries =
|
|
31991
|
+
const entries = readdirSync23(dir, { withFileTypes: true });
|
|
29773
31992
|
for (const entry of entries) {
|
|
29774
31993
|
const fullPath = resolve29(dir, entry.name);
|
|
29775
31994
|
if (entry.isDirectory()) {
|
|
@@ -29777,7 +31996,7 @@ function discoverMarkdownFiles(baseDir) {
|
|
|
29777
31996
|
if (entry.name === "archive" && dir.includes("status")) continue;
|
|
29778
31997
|
if (entry.name === "node_modules") continue;
|
|
29779
31998
|
walk(fullPath);
|
|
29780
|
-
} else if (entry.isFile() &&
|
|
31999
|
+
} else if (entry.isFile() && extname2(entry.name) === ".md") {
|
|
29781
32000
|
files.push(fullPath);
|
|
29782
32001
|
}
|
|
29783
32002
|
}
|
|
@@ -29791,7 +32010,7 @@ function categorizeFile(filePath) {
|
|
|
29791
32010
|
const paths = getKnowledgePaths();
|
|
29792
32011
|
if (filePath.startsWith(paths.plansDir)) return "plan";
|
|
29793
32012
|
if (filePath.startsWith(paths.docsDir)) {
|
|
29794
|
-
const relFromDocs =
|
|
32013
|
+
const relFromDocs = relative12(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
|
|
29795
32014
|
if (relFromDocs.startsWith("plans/")) return "plan";
|
|
29796
32015
|
if (relFromDocs.includes("architecture")) return "architecture";
|
|
29797
32016
|
if (relFromDocs.includes("security")) return "security";
|
|
@@ -29807,7 +32026,7 @@ function categorizeFile(filePath) {
|
|
|
29807
32026
|
}
|
|
29808
32027
|
const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
|
|
29809
32028
|
if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
|
|
29810
|
-
const rel =
|
|
32029
|
+
const rel = relative12(paths.claudeDir, filePath).replace(/\\/g, "/");
|
|
29811
32030
|
const firstDir = rel.split("/")[0];
|
|
29812
32031
|
const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
|
|
29813
32032
|
"patterns",
|
|
@@ -29829,7 +32048,7 @@ function categorizeFile(filePath) {
|
|
|
29829
32048
|
return "root";
|
|
29830
32049
|
}
|
|
29831
32050
|
function hashContent2(content) {
|
|
29832
|
-
return
|
|
32051
|
+
return createHash8("sha256").update(content).digest("hex");
|
|
29833
32052
|
}
|
|
29834
32053
|
function parseCRTable(content) {
|
|
29835
32054
|
const rules = [];
|
|
@@ -29948,7 +32167,7 @@ function parseCorrections(content) {
|
|
|
29948
32167
|
function extractTitle(content, filePath) {
|
|
29949
32168
|
const h1Match = content.match(/^#\s+(.+)/m);
|
|
29950
32169
|
if (h1Match) return h1Match[1].trim();
|
|
29951
|
-
return
|
|
32170
|
+
return basename8(filePath, ".md");
|
|
29952
32171
|
}
|
|
29953
32172
|
function extractDescription2(content) {
|
|
29954
32173
|
const fmMatch = content.match(/^---\s*\n[\s\S]*?description:\s*"?([^"\n]+)"?\s*\n[\s\S]*?---/);
|
|
@@ -29977,7 +32196,7 @@ function buildCrossReferences(db) {
|
|
|
29977
32196
|
}
|
|
29978
32197
|
}
|
|
29979
32198
|
if (rule.reference_path) {
|
|
29980
|
-
const patternName =
|
|
32199
|
+
const patternName = basename8(rule.reference_path, ".md");
|
|
29981
32200
|
insertEdge.run("cr", rule.rule_id, "pattern", patternName, "references");
|
|
29982
32201
|
edgeCount++;
|
|
29983
32202
|
}
|
|
@@ -30049,11 +32268,11 @@ function indexAllKnowledge(db) {
|
|
|
30049
32268
|
files.push(...memFiles);
|
|
30050
32269
|
} catch {
|
|
30051
32270
|
}
|
|
30052
|
-
if (
|
|
32271
|
+
if (existsSync35(paths.plansDir)) {
|
|
30053
32272
|
const planFiles = discoverMarkdownFiles(paths.plansDir);
|
|
30054
32273
|
files.push(...planFiles);
|
|
30055
32274
|
}
|
|
30056
|
-
if (
|
|
32275
|
+
if (existsSync35(paths.docsDir)) {
|
|
30057
32276
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
30058
32277
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
30059
32278
|
files.push(...docsFiles);
|
|
@@ -30096,10 +32315,10 @@ function indexAllKnowledge(db) {
|
|
|
30096
32315
|
} catch {
|
|
30097
32316
|
}
|
|
30098
32317
|
for (const filePath of files) {
|
|
30099
|
-
if (!
|
|
30100
|
-
const content =
|
|
32318
|
+
if (!existsSync35(filePath)) continue;
|
|
32319
|
+
const content = readFileSync36(filePath, "utf-8");
|
|
30101
32320
|
const hash = hashContent2(content);
|
|
30102
|
-
const relPath = filePath.startsWith(paths.claudeDir) ?
|
|
32321
|
+
const relPath = filePath.startsWith(paths.claudeDir) ? relative12(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative12(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative12(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative12(paths.memoryDir, filePath)}` : basename8(filePath);
|
|
30103
32322
|
const category = categorizeFile(filePath);
|
|
30104
32323
|
const title = extractTitle(content, filePath);
|
|
30105
32324
|
const description = extractDescription2(content);
|
|
@@ -30113,10 +32332,10 @@ function indexAllKnowledge(db) {
|
|
|
30113
32332
|
stats.chunksCreated++;
|
|
30114
32333
|
}
|
|
30115
32334
|
}
|
|
30116
|
-
const fileName =
|
|
32335
|
+
const fileName = basename8(filePath);
|
|
30117
32336
|
const fileNameLower = fileName.toLowerCase();
|
|
30118
32337
|
const relPathLower = relPath.toLowerCase();
|
|
30119
|
-
const claudeMdName =
|
|
32338
|
+
const claudeMdName = basename8(getResolvedPaths().claudeMdPath).toLowerCase();
|
|
30120
32339
|
if (fileNameLower === claudeMdName || relPathLower.includes(claudeMdName)) {
|
|
30121
32340
|
const crRules = parseCRTable(content);
|
|
30122
32341
|
for (const rule of crRules) {
|
|
@@ -30150,7 +32369,7 @@ function indexAllKnowledge(db) {
|
|
|
30150
32369
|
}
|
|
30151
32370
|
}
|
|
30152
32371
|
if (category === "commands" && fileName !== "_shared-preamble.md") {
|
|
30153
|
-
const cmdName =
|
|
32372
|
+
const cmdName = basename8(filePath, ".md");
|
|
30154
32373
|
insertChunk.run(docId, "command", cmdName, content.substring(0, 1e3), 1, null, JSON.stringify({ command_name: cmdName }));
|
|
30155
32374
|
stats.chunksCreated++;
|
|
30156
32375
|
}
|
|
@@ -30217,17 +32436,17 @@ function isKnowledgeStale(db) {
|
|
|
30217
32436
|
files.push(...discoverMarkdownFiles(paths.memoryDir));
|
|
30218
32437
|
} catch {
|
|
30219
32438
|
}
|
|
30220
|
-
if (
|
|
32439
|
+
if (existsSync35(paths.plansDir)) {
|
|
30221
32440
|
files.push(...discoverMarkdownFiles(paths.plansDir));
|
|
30222
32441
|
}
|
|
30223
|
-
if (
|
|
32442
|
+
if (existsSync35(paths.docsDir)) {
|
|
30224
32443
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
30225
32444
|
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
|
|
30226
32445
|
files.push(...docsFiles);
|
|
30227
32446
|
}
|
|
30228
32447
|
for (const filePath of files) {
|
|
30229
32448
|
try {
|
|
30230
|
-
const stat =
|
|
32449
|
+
const stat = statSync14(filePath);
|
|
30231
32450
|
if (stat.mtimeMs > lastIndexTime) return true;
|
|
30232
32451
|
} catch {
|
|
30233
32452
|
continue;
|
|
@@ -30249,8 +32468,8 @@ var init_knowledge_indexer = __esm({
|
|
|
30249
32468
|
});
|
|
30250
32469
|
|
|
30251
32470
|
// src/knowledge-tools.ts
|
|
30252
|
-
import { readFileSync as
|
|
30253
|
-
import { resolve as resolve30, basename as
|
|
32471
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync5, appendFileSync as appendFileSync2, readdirSync as readdirSync24 } from "fs";
|
|
32472
|
+
import { resolve as resolve30, basename as basename9 } from "path";
|
|
30254
32473
|
function p16(baseName) {
|
|
30255
32474
|
return `${getConfig().toolPrefix}_${baseName}`;
|
|
30256
32475
|
}
|
|
@@ -31000,14 +33219,14 @@ ${crRule ? `- **CR**: ${crRule}
|
|
|
31000
33219
|
`;
|
|
31001
33220
|
let existing = "";
|
|
31002
33221
|
try {
|
|
31003
|
-
existing =
|
|
33222
|
+
existing = readFileSync37(correctionsPath, "utf-8");
|
|
31004
33223
|
} catch {
|
|
31005
33224
|
}
|
|
31006
33225
|
const archiveIdx = existing.indexOf("## Archived");
|
|
31007
33226
|
if (archiveIdx > 0) {
|
|
31008
33227
|
const before = existing.slice(0, archiveIdx);
|
|
31009
33228
|
const after = existing.slice(archiveIdx);
|
|
31010
|
-
|
|
33229
|
+
writeFileSync5(correctionsPath, before + entry + after);
|
|
31011
33230
|
} else {
|
|
31012
33231
|
appendFileSync2(correctionsPath, entry);
|
|
31013
33232
|
}
|
|
@@ -31049,7 +33268,7 @@ function handlePlan(db, args3) {
|
|
|
31049
33268
|
AND kc.content LIKE ?
|
|
31050
33269
|
ORDER BY kd.file_path DESC
|
|
31051
33270
|
LIMIT 20
|
|
31052
|
-
`).all(`%${
|
|
33271
|
+
`).all(`%${basename9(file)}%`);
|
|
31053
33272
|
lines.push(`## Plans referencing \`${file}\` (${results.length} found)`);
|
|
31054
33273
|
lines.push("");
|
|
31055
33274
|
for (const r2 of results) {
|
|
@@ -31182,7 +33401,7 @@ function handleGaps(db, args3) {
|
|
|
31182
33401
|
} else if (checkType === "routers") {
|
|
31183
33402
|
try {
|
|
31184
33403
|
const routersDir = getResolvedPaths().routersDir;
|
|
31185
|
-
const routerFiles =
|
|
33404
|
+
const routerFiles = readdirSync24(routersDir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith("_"));
|
|
31186
33405
|
lines.push(`| Router | Knowledge Hits | Status |`);
|
|
31187
33406
|
lines.push(`|--------|----------------|--------|`);
|
|
31188
33407
|
for (const file of routerFiles) {
|
|
@@ -31338,13 +33557,13 @@ var init_knowledge_tools = __esm({
|
|
|
31338
33557
|
|
|
31339
33558
|
// src/knowledge-db.ts
|
|
31340
33559
|
import Database3 from "better-sqlite3";
|
|
31341
|
-
import { dirname as
|
|
31342
|
-
import { existsSync as
|
|
33560
|
+
import { dirname as dirname15 } from "path";
|
|
33561
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync10 } from "fs";
|
|
31343
33562
|
function getKnowledgeDb() {
|
|
31344
33563
|
const dbPath = getResolvedPaths().knowledgeDbPath;
|
|
31345
|
-
const dir =
|
|
31346
|
-
if (!
|
|
31347
|
-
|
|
33564
|
+
const dir = dirname15(dbPath);
|
|
33565
|
+
if (!existsSync37(dir)) {
|
|
33566
|
+
mkdirSync10(dir, { recursive: true });
|
|
31348
33567
|
}
|
|
31349
33568
|
const db = new Database3(dbPath);
|
|
31350
33569
|
db.pragma("journal_mode = WAL");
|
|
@@ -32077,8 +34296,8 @@ var init_python_tools = __esm({
|
|
|
32077
34296
|
});
|
|
32078
34297
|
|
|
32079
34298
|
// src/tools.ts
|
|
32080
|
-
import { readFileSync as
|
|
32081
|
-
import { resolve as resolve31, basename as
|
|
34299
|
+
import { readFileSync as readFileSync38, existsSync as existsSync38 } from "fs";
|
|
34300
|
+
import { resolve as resolve31, basename as basename10 } from "path";
|
|
32082
34301
|
function prefix2() {
|
|
32083
34302
|
return getConfig().toolPrefix;
|
|
32084
34303
|
}
|
|
@@ -32513,8 +34732,8 @@ function handleContext(file, dataDb2, codegraphDb2) {
|
|
|
32513
34732
|
const resolvedPaths = getResolvedPaths();
|
|
32514
34733
|
const root = getProjectRoot();
|
|
32515
34734
|
const absFilePath = ensureWithinRoot(resolve31(resolvedPaths.srcDir, "..", file), root);
|
|
32516
|
-
if (
|
|
32517
|
-
const fileContent =
|
|
34735
|
+
if (existsSync38(absFilePath)) {
|
|
34736
|
+
const fileContent = readFileSync38(absFilePath, "utf-8").slice(0, 3e3);
|
|
32518
34737
|
const keywords = [];
|
|
32519
34738
|
if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
|
|
32520
34739
|
if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
|
|
@@ -32608,7 +34827,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
|
|
|
32608
34827
|
WHERE o.files_involved LIKE ?
|
|
32609
34828
|
ORDER BY o.importance DESC, o.created_at_epoch DESC
|
|
32610
34829
|
LIMIT 5
|
|
32611
|
-
`).all(`%${
|
|
34830
|
+
`).all(`%${basename10(file)}%`);
|
|
32612
34831
|
if (fileObservations.length > 0) {
|
|
32613
34832
|
lines.push("## Past Observations (This File)");
|
|
32614
34833
|
for (const obs of fileObservations) {
|
|
@@ -32624,7 +34843,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
|
|
|
32624
34843
|
WHERE type = 'failed_attempt' AND files_involved LIKE ?
|
|
32625
34844
|
ORDER BY recurrence_count DESC
|
|
32626
34845
|
LIMIT 3
|
|
32627
|
-
`).all(`%${
|
|
34846
|
+
`).all(`%${basename10(file)}%`);
|
|
32628
34847
|
if (failures.length > 0) {
|
|
32629
34848
|
lines.push("## Failed Attempts (DO NOT RETRY)");
|
|
32630
34849
|
for (const f2 of failures) {
|
|
@@ -32939,10 +35158,10 @@ function handleSchema(args3) {
|
|
|
32939
35158
|
lines.push("");
|
|
32940
35159
|
const projectRoot = getProjectRoot();
|
|
32941
35160
|
const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
|
|
32942
|
-
if (!
|
|
35161
|
+
if (!existsSync38(absPath)) {
|
|
32943
35162
|
return text17(`File not found: ${file}`);
|
|
32944
35163
|
}
|
|
32945
|
-
const source =
|
|
35164
|
+
const source = readFileSync38(absPath, "utf-8");
|
|
32946
35165
|
const config = getConfig();
|
|
32947
35166
|
const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
|
|
32948
35167
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
@@ -33020,8 +35239,8 @@ var init_tools = __esm({
|
|
|
33020
35239
|
|
|
33021
35240
|
// src/server.ts
|
|
33022
35241
|
var server_exports = {};
|
|
33023
|
-
import { readFileSync as
|
|
33024
|
-
import { resolve as resolve32, dirname as
|
|
35242
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
35243
|
+
import { resolve as resolve32, dirname as dirname16 } from "path";
|
|
33025
35244
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
33026
35245
|
function getDb() {
|
|
33027
35246
|
if (!codegraphDb) codegraphDb = getCodeGraphDb();
|
|
@@ -33113,10 +35332,10 @@ var init_server = __esm({
|
|
|
33113
35332
|
init_tools();
|
|
33114
35333
|
init_memory_db();
|
|
33115
35334
|
init_license();
|
|
33116
|
-
__dirname4 =
|
|
35335
|
+
__dirname4 = dirname16(fileURLToPath4(import.meta.url));
|
|
33117
35336
|
PKG_VERSION = (() => {
|
|
33118
35337
|
try {
|
|
33119
|
-
const pkg = JSON.parse(
|
|
35338
|
+
const pkg = JSON.parse(readFileSync39(resolve32(__dirname4, "..", "package.json"), "utf-8"));
|
|
33120
35339
|
return pkg.version ?? "0.0.0";
|
|
33121
35340
|
} catch {
|
|
33122
35341
|
return "0.0.0";
|
|
@@ -33419,7 +35638,7 @@ var config_upgrade_exports = {};
|
|
|
33419
35638
|
__export(config_upgrade_exports, {
|
|
33420
35639
|
runConfigUpgrade: () => runConfigUpgrade
|
|
33421
35640
|
});
|
|
33422
|
-
import { existsSync as
|
|
35641
|
+
import { existsSync as existsSync39, readFileSync as readFileSync40, writeFileSync as writeFileSync6, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
|
|
33423
35642
|
import { resolve as resolve33 } from "path";
|
|
33424
35643
|
import { parse as parseYaml6 } from "yaml";
|
|
33425
35644
|
async function runConfigUpgrade(opts = {}) {
|
|
@@ -33431,14 +35650,14 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
33431
35650
|
const err2 = opts.silent ? () => {
|
|
33432
35651
|
} : (s) => process.stderr.write(s);
|
|
33433
35652
|
if (opts.rollback) {
|
|
33434
|
-
if (!
|
|
35653
|
+
if (!existsSync39(bakPath)) {
|
|
33435
35654
|
const message = `No backup found at ${bakPath}`;
|
|
33436
35655
|
err2(message + "\n");
|
|
33437
35656
|
return { exitCode: 1, action: "none", message };
|
|
33438
35657
|
}
|
|
33439
35658
|
try {
|
|
33440
35659
|
copyFileSync(bakPath, configPath);
|
|
33441
|
-
|
|
35660
|
+
unlinkSync2(bakPath);
|
|
33442
35661
|
log("Config restored from backup.\n");
|
|
33443
35662
|
return { exitCode: 0, action: "rolled-back" };
|
|
33444
35663
|
} catch (e2) {
|
|
@@ -33447,14 +35666,14 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
33447
35666
|
return { exitCode: 2, action: "none", message };
|
|
33448
35667
|
}
|
|
33449
35668
|
}
|
|
33450
|
-
if (!
|
|
35669
|
+
if (!existsSync39(configPath)) {
|
|
33451
35670
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
33452
35671
|
err2(message + "\n");
|
|
33453
35672
|
return { exitCode: 1, action: "none", message };
|
|
33454
35673
|
}
|
|
33455
35674
|
let existing;
|
|
33456
35675
|
try {
|
|
33457
|
-
const content =
|
|
35676
|
+
const content = readFileSync40(configPath, "utf-8");
|
|
33458
35677
|
const parsed = parseYaml6(content);
|
|
33459
35678
|
if (!parsed || typeof parsed !== "object") {
|
|
33460
35679
|
throw new Error("config is not a YAML object");
|
|
@@ -33477,8 +35696,8 @@ async function runConfigUpgrade(opts = {}) {
|
|
|
33477
35696
|
fingerprint: computeFingerprint(detection)
|
|
33478
35697
|
};
|
|
33479
35698
|
try {
|
|
33480
|
-
const original =
|
|
33481
|
-
|
|
35699
|
+
const original = readFileSync40(configPath, "utf-8");
|
|
35700
|
+
writeFileSync6(bakPath, original, "utf-8");
|
|
33482
35701
|
} catch (e2) {
|
|
33483
35702
|
const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
33484
35703
|
err2(message + "\n");
|
|
@@ -33511,7 +35730,7 @@ var config_check_drift_exports = {};
|
|
|
33511
35730
|
__export(config_check_drift_exports, {
|
|
33512
35731
|
runConfigCheckDrift: () => runConfigCheckDrift
|
|
33513
35732
|
});
|
|
33514
|
-
import { existsSync as
|
|
35733
|
+
import { existsSync as existsSync40, readFileSync as readFileSync41 } from "fs";
|
|
33515
35734
|
import { resolve as resolve34 } from "path";
|
|
33516
35735
|
import { parse as parseYaml7 } from "yaml";
|
|
33517
35736
|
function renderChanges(changes) {
|
|
@@ -33525,7 +35744,7 @@ async function runConfigCheckDrift(opts = {}) {
|
|
|
33525
35744
|
} : (s) => process.stdout.write(s);
|
|
33526
35745
|
const err2 = opts.silent ? () => {
|
|
33527
35746
|
} : (s) => process.stderr.write(s);
|
|
33528
|
-
if (!
|
|
35747
|
+
if (!existsSync40(configPath)) {
|
|
33529
35748
|
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
33530
35749
|
err2(message + "\n");
|
|
33531
35750
|
return {
|
|
@@ -33539,7 +35758,7 @@ async function runConfigCheckDrift(opts = {}) {
|
|
|
33539
35758
|
}
|
|
33540
35759
|
let config;
|
|
33541
35760
|
try {
|
|
33542
|
-
const content =
|
|
35761
|
+
const content = readFileSync41(configPath, "utf-8");
|
|
33543
35762
|
const parsed = parseYaml7(content);
|
|
33544
35763
|
if (!parsed || typeof parsed !== "object") {
|
|
33545
35764
|
throw new Error("config is not a YAML object");
|
|
@@ -33606,11 +35825,11 @@ var init_config_check_drift = __esm({
|
|
|
33606
35825
|
});
|
|
33607
35826
|
|
|
33608
35827
|
// src/cli.ts
|
|
33609
|
-
import { readFileSync as
|
|
33610
|
-
import { resolve as resolve35, dirname as
|
|
35828
|
+
import { readFileSync as readFileSync42 } from "fs";
|
|
35829
|
+
import { resolve as resolve35, dirname as dirname17 } from "path";
|
|
33611
35830
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
33612
35831
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
33613
|
-
var __dirname5 =
|
|
35832
|
+
var __dirname5 = dirname17(__filename4);
|
|
33614
35833
|
var args2 = process.argv.slice(2);
|
|
33615
35834
|
var subcommand = args2[0];
|
|
33616
35835
|
async function main() {
|
|
@@ -33792,7 +36011,7 @@ Examples:
|
|
|
33792
36011
|
}
|
|
33793
36012
|
function printVersion() {
|
|
33794
36013
|
try {
|
|
33795
|
-
const pkg = JSON.parse(
|
|
36014
|
+
const pkg = JSON.parse(readFileSync42(resolve35(__dirname5, "../package.json"), "utf-8"));
|
|
33796
36015
|
console.log(`massu v${pkg.version}`);
|
|
33797
36016
|
} catch {
|
|
33798
36017
|
console.log("massu v0.1.0");
|