@massu/core 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +791 -26
- package/dist/hooks/session-start.js +8706 -771
- package/package.json +1 -1
- package/src/cli.ts +79 -1
- package/src/commands/config-check-drift.ts +132 -0
- package/src/commands/config-refresh.ts +224 -0
- package/src/commands/config-upgrade.ts +126 -0
- package/src/commands/init.ts +4 -0
- package/src/hooks/session-start.ts +42 -1
package/dist/cli.js
CHANGED
|
@@ -3277,10 +3277,10 @@ var require_array = __commonJS({
|
|
|
3277
3277
|
"use strict";
|
|
3278
3278
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3279
3279
|
exports.splitWhen = exports.flatten = void 0;
|
|
3280
|
-
function
|
|
3280
|
+
function flatten2(items) {
|
|
3281
3281
|
return items.reduce((collection, item) => [].concat(collection, item), []);
|
|
3282
3282
|
}
|
|
3283
|
-
exports.flatten =
|
|
3283
|
+
exports.flatten = flatten2;
|
|
3284
3284
|
function splitWhen(items, predicate) {
|
|
3285
3285
|
const result = [[]];
|
|
3286
3286
|
let groupIndex = 0;
|
|
@@ -7797,41 +7797,41 @@ var require_queue = __commonJS({
|
|
|
7797
7797
|
queue.drained = drained;
|
|
7798
7798
|
return queue;
|
|
7799
7799
|
function push(value) {
|
|
7800
|
-
var p19 = new Promise(function(
|
|
7800
|
+
var p19 = new Promise(function(resolve25, reject) {
|
|
7801
7801
|
pushCb(value, function(err, result) {
|
|
7802
7802
|
if (err) {
|
|
7803
7803
|
reject(err);
|
|
7804
7804
|
return;
|
|
7805
7805
|
}
|
|
7806
|
-
|
|
7806
|
+
resolve25(result);
|
|
7807
7807
|
});
|
|
7808
7808
|
});
|
|
7809
7809
|
p19.catch(noop);
|
|
7810
7810
|
return p19;
|
|
7811
7811
|
}
|
|
7812
7812
|
function unshift(value) {
|
|
7813
|
-
var p19 = new Promise(function(
|
|
7813
|
+
var p19 = new Promise(function(resolve25, reject) {
|
|
7814
7814
|
unshiftCb(value, function(err, result) {
|
|
7815
7815
|
if (err) {
|
|
7816
7816
|
reject(err);
|
|
7817
7817
|
return;
|
|
7818
7818
|
}
|
|
7819
|
-
|
|
7819
|
+
resolve25(result);
|
|
7820
7820
|
});
|
|
7821
7821
|
});
|
|
7822
7822
|
p19.catch(noop);
|
|
7823
7823
|
return p19;
|
|
7824
7824
|
}
|
|
7825
7825
|
function drained() {
|
|
7826
|
-
var p19 = new Promise(function(
|
|
7826
|
+
var p19 = new Promise(function(resolve25) {
|
|
7827
7827
|
process.nextTick(function() {
|
|
7828
7828
|
if (queue.idle()) {
|
|
7829
|
-
|
|
7829
|
+
resolve25();
|
|
7830
7830
|
} else {
|
|
7831
7831
|
var previousDrain = queue.drain;
|
|
7832
7832
|
queue.drain = function() {
|
|
7833
7833
|
if (typeof previousDrain === "function") previousDrain();
|
|
7834
|
-
|
|
7834
|
+
resolve25();
|
|
7835
7835
|
queue.drain = previousDrain;
|
|
7836
7836
|
};
|
|
7837
7837
|
}
|
|
@@ -8317,9 +8317,9 @@ var require_stream3 = __commonJS({
|
|
|
8317
8317
|
});
|
|
8318
8318
|
}
|
|
8319
8319
|
_getStat(filepath) {
|
|
8320
|
-
return new Promise((
|
|
8320
|
+
return new Promise((resolve25, reject) => {
|
|
8321
8321
|
this._stat(filepath, this._fsStatSettings, (error, stats) => {
|
|
8322
|
-
return error === null ?
|
|
8322
|
+
return error === null ? resolve25(stats) : reject(error);
|
|
8323
8323
|
});
|
|
8324
8324
|
});
|
|
8325
8325
|
}
|
|
@@ -8343,10 +8343,10 @@ var require_async5 = __commonJS({
|
|
|
8343
8343
|
this._readerStream = new stream_1.default(this._settings);
|
|
8344
8344
|
}
|
|
8345
8345
|
dynamic(root, options) {
|
|
8346
|
-
return new Promise((
|
|
8346
|
+
return new Promise((resolve25, reject) => {
|
|
8347
8347
|
this._walkAsync(root, options, (error, entries) => {
|
|
8348
8348
|
if (error === null) {
|
|
8349
|
-
|
|
8349
|
+
resolve25(entries);
|
|
8350
8350
|
} else {
|
|
8351
8351
|
reject(error);
|
|
8352
8352
|
}
|
|
@@ -8356,10 +8356,10 @@ var require_async5 = __commonJS({
|
|
|
8356
8356
|
async static(patterns, options) {
|
|
8357
8357
|
const entries = [];
|
|
8358
8358
|
const stream = this._readerStream.static(patterns, options);
|
|
8359
|
-
return new Promise((
|
|
8359
|
+
return new Promise((resolve25, reject) => {
|
|
8360
8360
|
stream.once("error", reject);
|
|
8361
8361
|
stream.on("data", (entry) => entries.push(entry));
|
|
8362
|
-
stream.once("end", () =>
|
|
8362
|
+
stream.once("end", () => resolve25(entries));
|
|
8363
8363
|
});
|
|
8364
8364
|
}
|
|
8365
8365
|
};
|
|
@@ -9686,6 +9686,132 @@ var init_detect = __esm({
|
|
|
9686
9686
|
}
|
|
9687
9687
|
});
|
|
9688
9688
|
|
|
9689
|
+
// src/detect/drift.ts
|
|
9690
|
+
import { createHash } from "crypto";
|
|
9691
|
+
function summarizeDetection(det) {
|
|
9692
|
+
const languages = Array.from(new Set(det.manifests.map((m3) => m3.language))).sort();
|
|
9693
|
+
const frameworks = {};
|
|
9694
|
+
for (const lang of languages) {
|
|
9695
|
+
const fw = det.frameworks[lang];
|
|
9696
|
+
frameworks[lang] = {
|
|
9697
|
+
framework: fw?.framework ?? null,
|
|
9698
|
+
test_framework: fw?.test_framework ?? null,
|
|
9699
|
+
orm: fw?.orm ?? null
|
|
9700
|
+
};
|
|
9701
|
+
}
|
|
9702
|
+
const sourceDirs = {};
|
|
9703
|
+
for (const lang of languages) {
|
|
9704
|
+
const info = det.sourceDirs[lang];
|
|
9705
|
+
sourceDirs[lang] = [...info?.source_dirs ?? []].sort();
|
|
9706
|
+
}
|
|
9707
|
+
const manifests = [...det.manifests.map((m3) => m3.relativePath)].sort();
|
|
9708
|
+
const workspaces = [...det.monorepo.packages.map((p19) => p19.path)].sort();
|
|
9709
|
+
return {
|
|
9710
|
+
languages,
|
|
9711
|
+
frameworks,
|
|
9712
|
+
source_dirs: sourceDirs,
|
|
9713
|
+
manifests,
|
|
9714
|
+
monorepo: det.monorepo.type,
|
|
9715
|
+
workspaces
|
|
9716
|
+
};
|
|
9717
|
+
}
|
|
9718
|
+
function computeFingerprint(det) {
|
|
9719
|
+
const data = summarizeDetection(det);
|
|
9720
|
+
const stable = JSON.stringify(data, Object.keys(data).sort());
|
|
9721
|
+
return createHash("sha256").update(stable).digest("hex");
|
|
9722
|
+
}
|
|
9723
|
+
function stringOf(v3) {
|
|
9724
|
+
if (typeof v3 === "string") return v3;
|
|
9725
|
+
if (v3 === null || v3 === void 0) return null;
|
|
9726
|
+
return String(v3);
|
|
9727
|
+
}
|
|
9728
|
+
function detectDrift(currentConfig, actualDetection) {
|
|
9729
|
+
const changes = [];
|
|
9730
|
+
const configFw = currentConfig.framework && typeof currentConfig.framework === "object" ? currentConfig.framework : {};
|
|
9731
|
+
const configLanguages = configFw.languages && typeof configFw.languages === "object" ? configFw.languages : {};
|
|
9732
|
+
const detectedLanguages = Array.from(
|
|
9733
|
+
new Set(actualDetection.manifests.map((m3) => m3.language))
|
|
9734
|
+
);
|
|
9735
|
+
const configLangKeys = Object.keys(configLanguages).sort();
|
|
9736
|
+
const detectedLangKeys = [...detectedLanguages].sort();
|
|
9737
|
+
if (JSON.stringify(configLangKeys) !== JSON.stringify(detectedLangKeys)) {
|
|
9738
|
+
changes.push({
|
|
9739
|
+
field: "framework.languages",
|
|
9740
|
+
before: configLangKeys,
|
|
9741
|
+
after: detectedLangKeys
|
|
9742
|
+
});
|
|
9743
|
+
}
|
|
9744
|
+
for (const lang of detectedLanguages) {
|
|
9745
|
+
const detFw = actualDetection.frameworks[lang];
|
|
9746
|
+
const cfgEntry = configLanguages[lang];
|
|
9747
|
+
if (!cfgEntry) continue;
|
|
9748
|
+
const cfgFramework = stringOf(cfgEntry.framework);
|
|
9749
|
+
const detFramework = detFw?.framework ?? null;
|
|
9750
|
+
if (cfgFramework !== detFramework && detFramework !== null) {
|
|
9751
|
+
changes.push({
|
|
9752
|
+
field: `framework.languages.${lang}.framework`,
|
|
9753
|
+
before: cfgFramework,
|
|
9754
|
+
after: detFramework
|
|
9755
|
+
});
|
|
9756
|
+
}
|
|
9757
|
+
const cfgTest = stringOf(cfgEntry.test_framework);
|
|
9758
|
+
const detTest = detFw?.test_framework ?? null;
|
|
9759
|
+
if (cfgTest !== detTest && detTest !== null) {
|
|
9760
|
+
changes.push({
|
|
9761
|
+
field: `framework.languages.${lang}.test_framework`,
|
|
9762
|
+
before: cfgTest,
|
|
9763
|
+
after: detTest
|
|
9764
|
+
});
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
const detectedManifestPaths = new Set(actualDetection.manifests.map((m3) => m3.relativePath));
|
|
9768
|
+
const declaredManifestPaths = /* @__PURE__ */ new Set();
|
|
9769
|
+
const canonical = currentConfig.canonical_paths;
|
|
9770
|
+
if (canonical && typeof canonical.manifest_paths === "string") {
|
|
9771
|
+
for (const p19 of canonical.manifest_paths.split(",").map((s) => s.trim())) {
|
|
9772
|
+
if (p19) declaredManifestPaths.add(p19);
|
|
9773
|
+
}
|
|
9774
|
+
}
|
|
9775
|
+
if (Array.isArray(currentConfig.manifests)) {
|
|
9776
|
+
for (const p19 of currentConfig.manifests) {
|
|
9777
|
+
if (typeof p19 === "string") declaredManifestPaths.add(p19);
|
|
9778
|
+
}
|
|
9779
|
+
}
|
|
9780
|
+
if (declaredManifestPaths.size > 0) {
|
|
9781
|
+
const added = [...detectedManifestPaths].filter((p19) => !declaredManifestPaths.has(p19)).sort();
|
|
9782
|
+
const removed = [...declaredManifestPaths].filter((p19) => !detectedManifestPaths.has(p19)).sort();
|
|
9783
|
+
if (added.length > 0) {
|
|
9784
|
+
changes.push({ field: "manifests.added", before: [], after: added });
|
|
9785
|
+
}
|
|
9786
|
+
if (removed.length > 0) {
|
|
9787
|
+
changes.push({ field: "manifests.removed", before: removed, after: [] });
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
const configWorkspaces = [];
|
|
9791
|
+
if (Array.isArray(currentConfig.monorepo?.workspaces)) {
|
|
9792
|
+
for (const w2 of currentConfig.monorepo.workspaces) {
|
|
9793
|
+
if (typeof w2 === "string") configWorkspaces.push(w2);
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
const detectedWorkspaces = actualDetection.monorepo.packages.map((p19) => p19.path).sort();
|
|
9797
|
+
if (configWorkspaces.length > 0) {
|
|
9798
|
+
const cfgSorted = [...configWorkspaces].sort();
|
|
9799
|
+
if (JSON.stringify(cfgSorted) !== JSON.stringify(detectedWorkspaces)) {
|
|
9800
|
+
changes.push({
|
|
9801
|
+
field: "monorepo.workspaces",
|
|
9802
|
+
before: cfgSorted,
|
|
9803
|
+
after: detectedWorkspaces
|
|
9804
|
+
});
|
|
9805
|
+
}
|
|
9806
|
+
}
|
|
9807
|
+
return { drifted: changes.length > 0, changes };
|
|
9808
|
+
}
|
|
9809
|
+
var init_drift = __esm({
|
|
9810
|
+
"src/detect/drift.ts"() {
|
|
9811
|
+
"use strict";
|
|
9812
|
+
}
|
|
9813
|
+
});
|
|
9814
|
+
|
|
9689
9815
|
// ../../node_modules/sisteransi/src/index.js
|
|
9690
9816
|
var require_src = __commonJS({
|
|
9691
9817
|
"../../node_modules/sisteransi/src/index.js"(exports, module) {
|
|
@@ -11000,6 +11126,7 @@ function buildConfigFromDetection(opts) {
|
|
|
11000
11126
|
if (Object.keys(verification).length > 0) {
|
|
11001
11127
|
config.verification = verification;
|
|
11002
11128
|
}
|
|
11129
|
+
config.detection = { fingerprint: computeFingerprint(detection) };
|
|
11003
11130
|
if (languages.includes("python")) {
|
|
11004
11131
|
const pySourceDirs = detection.sourceDirs.python?.source_dirs ?? [];
|
|
11005
11132
|
const pyRoot = pySourceDirs.length > 0 ? pySourceDirs[0] : ".";
|
|
@@ -11372,7 +11499,7 @@ Examples:
|
|
|
11372
11499
|
Documentation: https://massu.ai/docs/getting-started/configuration
|
|
11373
11500
|
`);
|
|
11374
11501
|
}
|
|
11375
|
-
function
|
|
11502
|
+
function summarizeDetection2(detection) {
|
|
11376
11503
|
const parts = [];
|
|
11377
11504
|
const languages = Array.from(
|
|
11378
11505
|
new Set(detection.manifests.map((m3) => m3.language))
|
|
@@ -11475,7 +11602,7 @@ async function runInit(argv, overrides) {
|
|
|
11475
11602
|
errLog(" primary language chosen by manifest count; review framework.primary in the generated config");
|
|
11476
11603
|
}
|
|
11477
11604
|
}
|
|
11478
|
-
log(` Detected: ${
|
|
11605
|
+
log(` Detected: ${summarizeDetection2(detection)}`);
|
|
11479
11606
|
if (!opts.ci && !opts.force) {
|
|
11480
11607
|
const confirmed = await promptStackConfirm();
|
|
11481
11608
|
if (!confirmed) {
|
|
@@ -11593,6 +11720,7 @@ var init_init = __esm({
|
|
|
11593
11720
|
init_config();
|
|
11594
11721
|
init_install_commands();
|
|
11595
11722
|
init_detect();
|
|
11723
|
+
init_drift();
|
|
11596
11724
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
11597
11725
|
__dirname2 = dirname4(__filename2);
|
|
11598
11726
|
TEMPLATE_NAMES = [
|
|
@@ -11608,7 +11736,7 @@ var init_init = __esm({
|
|
|
11608
11736
|
});
|
|
11609
11737
|
|
|
11610
11738
|
// src/license.ts
|
|
11611
|
-
import { createHash } from "crypto";
|
|
11739
|
+
import { createHash as createHash2 } from "crypto";
|
|
11612
11740
|
function tierLevel(tier) {
|
|
11613
11741
|
return TIER_LEVELS[tier] ?? 0;
|
|
11614
11742
|
}
|
|
@@ -11633,7 +11761,7 @@ function annotateToolDefinitions(defs) {
|
|
|
11633
11761
|
});
|
|
11634
11762
|
}
|
|
11635
11763
|
async function validateLicense(apiKey) {
|
|
11636
|
-
const keyHash =
|
|
11764
|
+
const keyHash = createHash2("sha256").update(apiKey).digest("hex");
|
|
11637
11765
|
const memDb = getMemoryDb();
|
|
11638
11766
|
try {
|
|
11639
11767
|
const cached = memDb.prepare(
|
|
@@ -11694,7 +11822,7 @@ async function validateLicense(apiKey) {
|
|
|
11694
11822
|
}
|
|
11695
11823
|
}
|
|
11696
11824
|
function updateLicenseCache(apiKey, tier, validUntil, features = []) {
|
|
11697
|
-
const keyHash =
|
|
11825
|
+
const keyHash = createHash2("sha256").update(apiKey).digest("hex");
|
|
11698
11826
|
const memDb = getMemoryDb();
|
|
11699
11827
|
try {
|
|
11700
11828
|
memDb.prepare(`
|
|
@@ -19004,7 +19132,7 @@ var init_regression_detector = __esm({
|
|
|
19004
19132
|
});
|
|
19005
19133
|
|
|
19006
19134
|
// src/knowledge-indexer.ts
|
|
19007
|
-
import { createHash as
|
|
19135
|
+
import { createHash as createHash3 } from "crypto";
|
|
19008
19136
|
import { readFileSync as readFileSync22, readdirSync as readdirSync17, statSync as statSync8, existsSync as existsSync22 } from "fs";
|
|
19009
19137
|
import { resolve as resolve17, relative as relative10, basename as basename5, extname } from "path";
|
|
19010
19138
|
function getKnowledgePaths() {
|
|
@@ -19092,7 +19220,7 @@ function categorizeFile(filePath) {
|
|
|
19092
19220
|
return "root";
|
|
19093
19221
|
}
|
|
19094
19222
|
function hashContent(content) {
|
|
19095
|
-
return
|
|
19223
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
19096
19224
|
}
|
|
19097
19225
|
function parseCRTable(content) {
|
|
19098
19226
|
const rules = [];
|
|
@@ -22442,9 +22570,569 @@ var init_server = __esm({
|
|
|
22442
22570
|
}
|
|
22443
22571
|
});
|
|
22444
22572
|
|
|
22573
|
+
// src/commands/config-refresh.ts
|
|
22574
|
+
var config_refresh_exports = {};
|
|
22575
|
+
__export(config_refresh_exports, {
|
|
22576
|
+
computeDiff: () => computeDiff,
|
|
22577
|
+
mergeRefresh: () => mergeRefresh,
|
|
22578
|
+
runConfigRefresh: () => runConfigRefresh
|
|
22579
|
+
});
|
|
22580
|
+
import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
|
|
22581
|
+
import { resolve as resolve21 } from "path";
|
|
22582
|
+
import { parse as parseYaml5 } from "yaml";
|
|
22583
|
+
function flatten(obj, prefix3 = "") {
|
|
22584
|
+
const out = {};
|
|
22585
|
+
if (obj === null || obj === void 0) {
|
|
22586
|
+
out[prefix3 || "<root>"] = obj;
|
|
22587
|
+
return out;
|
|
22588
|
+
}
|
|
22589
|
+
if (typeof obj !== "object" || Array.isArray(obj)) {
|
|
22590
|
+
out[prefix3 || "<root>"] = obj;
|
|
22591
|
+
return out;
|
|
22592
|
+
}
|
|
22593
|
+
const rec = obj;
|
|
22594
|
+
for (const [k3, v3] of Object.entries(rec)) {
|
|
22595
|
+
const p19 = prefix3 ? `${prefix3}.${k3}` : k3;
|
|
22596
|
+
if (v3 !== null && typeof v3 === "object" && !Array.isArray(v3)) {
|
|
22597
|
+
Object.assign(out, flatten(v3, p19));
|
|
22598
|
+
} else {
|
|
22599
|
+
out[p19] = v3;
|
|
22600
|
+
}
|
|
22601
|
+
}
|
|
22602
|
+
return out;
|
|
22603
|
+
}
|
|
22604
|
+
function computeDiff(before, after) {
|
|
22605
|
+
const b2 = flatten(before);
|
|
22606
|
+
const a2 = flatten(after);
|
|
22607
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(b2), ...Object.keys(a2)]);
|
|
22608
|
+
const sorted = [...keys].sort();
|
|
22609
|
+
const lines = [];
|
|
22610
|
+
for (const k3 of sorted) {
|
|
22611
|
+
const bVal = b2[k3];
|
|
22612
|
+
const aVal = a2[k3];
|
|
22613
|
+
const bHas = k3 in b2;
|
|
22614
|
+
const aHas = k3 in a2;
|
|
22615
|
+
if (bHas && !aHas) {
|
|
22616
|
+
lines.push({ kind: "remove", path: k3, before: bVal });
|
|
22617
|
+
} else if (!bHas && aHas) {
|
|
22618
|
+
lines.push({ kind: "add", path: k3, after: aVal });
|
|
22619
|
+
} else if (JSON.stringify(bVal) !== JSON.stringify(aVal)) {
|
|
22620
|
+
lines.push({ kind: "change", path: k3, before: bVal, after: aVal });
|
|
22621
|
+
}
|
|
22622
|
+
}
|
|
22623
|
+
return lines;
|
|
22624
|
+
}
|
|
22625
|
+
function mergeRefresh(existing, refreshed) {
|
|
22626
|
+
const out = { ...refreshed };
|
|
22627
|
+
for (const field of PRESERVED_FIELDS) {
|
|
22628
|
+
if (existing[field] !== void 0) {
|
|
22629
|
+
out[field] = existing[field];
|
|
22630
|
+
}
|
|
22631
|
+
}
|
|
22632
|
+
return out;
|
|
22633
|
+
}
|
|
22634
|
+
function renderDiff(diff) {
|
|
22635
|
+
if (diff.length === 0) return "(no changes)\n";
|
|
22636
|
+
const lines = [];
|
|
22637
|
+
for (const d2 of diff) {
|
|
22638
|
+
if (d2.kind === "add") lines.push(`+ ${d2.path}: ${JSON.stringify(d2.after)}`);
|
|
22639
|
+
else if (d2.kind === "remove") lines.push(`- ${d2.path}: ${JSON.stringify(d2.before)}`);
|
|
22640
|
+
else if (d2.kind === "change") {
|
|
22641
|
+
lines.push(`~ ${d2.path}: ${JSON.stringify(d2.before)} -> ${JSON.stringify(d2.after)}`);
|
|
22642
|
+
}
|
|
22643
|
+
}
|
|
22644
|
+
return lines.join("\n") + "\n";
|
|
22645
|
+
}
|
|
22646
|
+
async function runConfigRefresh(opts = {}) {
|
|
22647
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
22648
|
+
const configPath = resolve21(cwd, "massu.config.yaml");
|
|
22649
|
+
const log = opts.silent ? () => {
|
|
22650
|
+
} : (s) => process.stdout.write(s);
|
|
22651
|
+
if (!existsSync26(configPath)) {
|
|
22652
|
+
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
22653
|
+
if (!opts.silent) process.stderr.write(message + "\n");
|
|
22654
|
+
return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
|
|
22655
|
+
}
|
|
22656
|
+
let existing;
|
|
22657
|
+
try {
|
|
22658
|
+
const content = readFileSync26(configPath, "utf-8");
|
|
22659
|
+
const parsed = parseYaml5(content);
|
|
22660
|
+
if (!parsed || typeof parsed !== "object") {
|
|
22661
|
+
throw new Error("config is not a YAML object");
|
|
22662
|
+
}
|
|
22663
|
+
existing = parsed;
|
|
22664
|
+
} catch (err) {
|
|
22665
|
+
const message = `Failed to parse massu.config.yaml: ${err instanceof Error ? err.message : String(err)}`;
|
|
22666
|
+
if (!opts.silent) process.stderr.write(message + "\n");
|
|
22667
|
+
return { exitCode: 2, applied: false, dryRun: !!opts.dryRun, diff: [], message };
|
|
22668
|
+
}
|
|
22669
|
+
const detection = await runDetection(cwd);
|
|
22670
|
+
const refreshed = buildConfigFromDetection({
|
|
22671
|
+
projectRoot: cwd,
|
|
22672
|
+
detection,
|
|
22673
|
+
projectName: typeof existing.project?.name === "string" ? existing.project.name : void 0
|
|
22674
|
+
});
|
|
22675
|
+
if (!refreshed.detection?.fingerprint) {
|
|
22676
|
+
refreshed.detection = { fingerprint: computeFingerprint(detection) };
|
|
22677
|
+
}
|
|
22678
|
+
const merged = mergeRefresh(existing, refreshed);
|
|
22679
|
+
const diff = computeDiff(existing, merged);
|
|
22680
|
+
if (opts.dryRun) {
|
|
22681
|
+
log("Config diff (dry-run; no changes written):\n");
|
|
22682
|
+
log(renderDiff(diff));
|
|
22683
|
+
return { exitCode: 0, applied: false, dryRun: true, diff };
|
|
22684
|
+
}
|
|
22685
|
+
if (diff.length === 0) {
|
|
22686
|
+
log("No changes needed \u2014 config is already up to date.\n");
|
|
22687
|
+
return { exitCode: 0, applied: false, dryRun: false, diff };
|
|
22688
|
+
}
|
|
22689
|
+
if (!process.stdin.isTTY) {
|
|
22690
|
+
log("Config diff (non-interactive; pass --dry-run to suppress this note or run interactively to apply):\n");
|
|
22691
|
+
log(renderDiff(diff));
|
|
22692
|
+
return {
|
|
22693
|
+
exitCode: 0,
|
|
22694
|
+
applied: false,
|
|
22695
|
+
dryRun: false,
|
|
22696
|
+
diff,
|
|
22697
|
+
message: "non-interactive shell; no changes written"
|
|
22698
|
+
};
|
|
22699
|
+
}
|
|
22700
|
+
log("Config diff:\n");
|
|
22701
|
+
log(renderDiff(diff));
|
|
22702
|
+
const { confirm } = await Promise.resolve().then(() => (init_dist3(), dist_exports));
|
|
22703
|
+
const apply = await confirm({ message: "Apply these changes to massu.config.yaml?" });
|
|
22704
|
+
if (apply !== true) {
|
|
22705
|
+
log("Aborted; no changes written.\n");
|
|
22706
|
+
return { exitCode: 0, applied: false, dryRun: false, diff, message: "aborted by user" };
|
|
22707
|
+
}
|
|
22708
|
+
const yamlContent = renderConfigYaml(merged);
|
|
22709
|
+
const writeRes = writeConfigAtomic(configPath, yamlContent);
|
|
22710
|
+
if (!writeRes.validated) {
|
|
22711
|
+
const message = `Failed to write config: ${writeRes.error}`;
|
|
22712
|
+
if (!opts.silent) process.stderr.write(message + "\n");
|
|
22713
|
+
return { exitCode: 2, applied: false, dryRun: false, diff, message };
|
|
22714
|
+
}
|
|
22715
|
+
log("Config refreshed.\n");
|
|
22716
|
+
return { exitCode: 0, applied: true, dryRun: false, diff };
|
|
22717
|
+
}
|
|
22718
|
+
var PRESERVED_FIELDS;
|
|
22719
|
+
var init_config_refresh = __esm({
|
|
22720
|
+
"src/commands/config-refresh.ts"() {
|
|
22721
|
+
"use strict";
|
|
22722
|
+
init_detect();
|
|
22723
|
+
init_drift();
|
|
22724
|
+
init_init();
|
|
22725
|
+
PRESERVED_FIELDS = [
|
|
22726
|
+
"rules",
|
|
22727
|
+
"domains",
|
|
22728
|
+
"canonical_paths",
|
|
22729
|
+
"verification_types",
|
|
22730
|
+
"accessScopes",
|
|
22731
|
+
"knownMismatches",
|
|
22732
|
+
"dbAccessPattern",
|
|
22733
|
+
"analytics",
|
|
22734
|
+
"governance",
|
|
22735
|
+
"security",
|
|
22736
|
+
"team",
|
|
22737
|
+
"regression",
|
|
22738
|
+
"cloud",
|
|
22739
|
+
"conventions",
|
|
22740
|
+
"autoLearning",
|
|
22741
|
+
"python"
|
|
22742
|
+
];
|
|
22743
|
+
}
|
|
22744
|
+
});
|
|
22745
|
+
|
|
22746
|
+
// src/detect/migrate.ts
|
|
22747
|
+
function getRecord(obj) {
|
|
22748
|
+
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
|
|
22749
|
+
return obj;
|
|
22750
|
+
}
|
|
22751
|
+
return {};
|
|
22752
|
+
}
|
|
22753
|
+
function isNoneOrDefault(v3) {
|
|
22754
|
+
if (v3 === void 0 || v3 === null) return true;
|
|
22755
|
+
if (typeof v3 !== "string") return false;
|
|
22756
|
+
return v3 === "none" || v3 === "";
|
|
22757
|
+
}
|
|
22758
|
+
function chooseString(user, detected) {
|
|
22759
|
+
if (typeof user === "string" && !isNoneOrDefault(user)) return user;
|
|
22760
|
+
if (typeof detected === "string" && detected !== "") return detected;
|
|
22761
|
+
if (typeof user === "string") return user;
|
|
22762
|
+
return "none";
|
|
22763
|
+
}
|
|
22764
|
+
function buildLanguageEntries(detection) {
|
|
22765
|
+
const languages = Array.from(
|
|
22766
|
+
new Set(detection.manifests.map((m3) => m3.language))
|
|
22767
|
+
);
|
|
22768
|
+
const entries = {};
|
|
22769
|
+
for (const lang of languages) {
|
|
22770
|
+
const fw = detection.frameworks[lang];
|
|
22771
|
+
const dirInfo = detection.sourceDirs[lang];
|
|
22772
|
+
const sourceDirs = dirInfo?.source_dirs ?? [];
|
|
22773
|
+
const entry = {};
|
|
22774
|
+
if (fw?.framework) entry.framework = fw.framework;
|
|
22775
|
+
if (fw?.test_framework) entry.test_framework = fw.test_framework;
|
|
22776
|
+
if (fw?.orm) entry.orm = fw.orm;
|
|
22777
|
+
if (fw?.router) entry.router = fw.router;
|
|
22778
|
+
if (fw?.ui_library) entry.ui = fw.ui_library;
|
|
22779
|
+
if (sourceDirs.length > 0) entry.source_dirs = sourceDirs;
|
|
22780
|
+
if (Object.keys(entry).length > 0) {
|
|
22781
|
+
entries[lang] = entry;
|
|
22782
|
+
}
|
|
22783
|
+
}
|
|
22784
|
+
return entries;
|
|
22785
|
+
}
|
|
22786
|
+
function pickPrimary(detection) {
|
|
22787
|
+
const counts = /* @__PURE__ */ new Map();
|
|
22788
|
+
for (const m3 of detection.manifests) {
|
|
22789
|
+
counts.set(m3.language, (counts.get(m3.language) ?? 0) + 1);
|
|
22790
|
+
}
|
|
22791
|
+
const sorted = [...counts.entries()].sort((a2, b2) => {
|
|
22792
|
+
if (b2[1] !== a2[1]) return b2[1] - a2[1];
|
|
22793
|
+
return a2[0].localeCompare(b2[0]);
|
|
22794
|
+
});
|
|
22795
|
+
return sorted.length > 0 ? sorted[0][0] : null;
|
|
22796
|
+
}
|
|
22797
|
+
function buildVerificationBlock(detection, userVerification) {
|
|
22798
|
+
const ver = {};
|
|
22799
|
+
const languages = Array.from(
|
|
22800
|
+
new Set(detection.manifests.map((m3) => m3.language))
|
|
22801
|
+
);
|
|
22802
|
+
for (const lang of languages) {
|
|
22803
|
+
const cmds = detection.verificationCommands[lang];
|
|
22804
|
+
if (!cmds) continue;
|
|
22805
|
+
const entry = {};
|
|
22806
|
+
if (cmds.test) entry.test = cmds.test;
|
|
22807
|
+
if (cmds.type) entry.type = cmds.type;
|
|
22808
|
+
if (cmds.build) entry.build = cmds.build;
|
|
22809
|
+
if (cmds.syntax) entry.syntax = cmds.syntax;
|
|
22810
|
+
if (cmds.lint) entry.lint = cmds.lint;
|
|
22811
|
+
if (Object.keys(entry).length > 0) ver[lang] = entry;
|
|
22812
|
+
}
|
|
22813
|
+
if (userVerification) {
|
|
22814
|
+
for (const [lang, userEntry] of Object.entries(userVerification)) {
|
|
22815
|
+
if (typeof userEntry !== "object" || userEntry === null) continue;
|
|
22816
|
+
ver[lang] = { ...ver[lang] ?? {}, ...userEntry };
|
|
22817
|
+
}
|
|
22818
|
+
}
|
|
22819
|
+
return ver;
|
|
22820
|
+
}
|
|
22821
|
+
function migrateV1ToV2(v1Config, detection) {
|
|
22822
|
+
const v1 = getRecord(v1Config);
|
|
22823
|
+
const v1Framework = getRecord(v1.framework);
|
|
22824
|
+
const v1Paths = getRecord(v1.paths);
|
|
22825
|
+
const v1Project = getRecord(v1.project);
|
|
22826
|
+
const v1Verification = v1.verification;
|
|
22827
|
+
const languages = Array.from(
|
|
22828
|
+
new Set(detection.manifests.map((m3) => m3.language))
|
|
22829
|
+
);
|
|
22830
|
+
const languageEntries = buildLanguageEntries(detection);
|
|
22831
|
+
const primary = pickPrimary(detection);
|
|
22832
|
+
const frameworkType = languages.length > 1 ? "multi" : languages[0] ?? (typeof v1Framework.type === "string" ? v1Framework.type : "typescript");
|
|
22833
|
+
const primaryEntry = primary ? languageEntries[primary] : void 0;
|
|
22834
|
+
const primaryRouter = primaryEntry?.router;
|
|
22835
|
+
const primaryOrm = primaryEntry?.orm;
|
|
22836
|
+
const primaryUi = primaryEntry?.ui;
|
|
22837
|
+
const legacyRouter = chooseString(v1Framework.router, primaryRouter);
|
|
22838
|
+
const legacyOrm = chooseString(v1Framework.orm, primaryOrm);
|
|
22839
|
+
const legacyUi = chooseString(v1Framework.ui, primaryUi);
|
|
22840
|
+
const framework = {
|
|
22841
|
+
type: frameworkType,
|
|
22842
|
+
router: legacyRouter,
|
|
22843
|
+
orm: legacyOrm,
|
|
22844
|
+
ui: legacyUi
|
|
22845
|
+
};
|
|
22846
|
+
if (languages.length > 1 && primary) {
|
|
22847
|
+
framework.primary = primary;
|
|
22848
|
+
}
|
|
22849
|
+
if (Object.keys(languageEntries).length > 0) {
|
|
22850
|
+
framework.languages = languageEntries;
|
|
22851
|
+
}
|
|
22852
|
+
let pathsSource = typeof v1Paths.source === "string" ? v1Paths.source : "src";
|
|
22853
|
+
if (pathsSource === "src" && primary) {
|
|
22854
|
+
const primaryDirs = detection.sourceDirs[primary]?.source_dirs ?? [];
|
|
22855
|
+
if (primaryDirs.length > 0) pathsSource = primaryDirs[0];
|
|
22856
|
+
}
|
|
22857
|
+
const aliases = v1Paths.aliases && typeof v1Paths.aliases === "object" ? v1Paths.aliases : { "@": pathsSource };
|
|
22858
|
+
const paths = {
|
|
22859
|
+
source: pathsSource,
|
|
22860
|
+
aliases
|
|
22861
|
+
};
|
|
22862
|
+
for (const k3 of ["routers", "routerRoot", "pages", "middleware", "schema", "components", "hooks"]) {
|
|
22863
|
+
if (typeof v1Paths[k3] === "string") paths[k3] = v1Paths[k3];
|
|
22864
|
+
}
|
|
22865
|
+
const verification = buildVerificationBlock(detection, v1Verification);
|
|
22866
|
+
const v22 = {
|
|
22867
|
+
schema_version: 2,
|
|
22868
|
+
project: {
|
|
22869
|
+
name: typeof v1Project.name === "string" ? v1Project.name : "my-project",
|
|
22870
|
+
root: typeof v1Project.root === "string" ? v1Project.root : "auto"
|
|
22871
|
+
},
|
|
22872
|
+
framework,
|
|
22873
|
+
paths,
|
|
22874
|
+
toolPrefix: typeof v1.toolPrefix === "string" ? v1.toolPrefix : "massu"
|
|
22875
|
+
};
|
|
22876
|
+
for (const field of PRESERVED_FIELDS2) {
|
|
22877
|
+
if (field in v1 && v1[field] !== void 0) {
|
|
22878
|
+
v22[field] = v1[field];
|
|
22879
|
+
}
|
|
22880
|
+
}
|
|
22881
|
+
if (!Array.isArray(v22.domains)) {
|
|
22882
|
+
v22.domains = [];
|
|
22883
|
+
}
|
|
22884
|
+
if (!Array.isArray(v22.rules)) {
|
|
22885
|
+
v22.rules = [];
|
|
22886
|
+
}
|
|
22887
|
+
if (Object.keys(verification).length > 0) {
|
|
22888
|
+
v22.verification = verification;
|
|
22889
|
+
}
|
|
22890
|
+
if (languages.includes("python")) {
|
|
22891
|
+
const existing = getRecord(v1.python);
|
|
22892
|
+
const pyFw = detection.frameworks.python;
|
|
22893
|
+
const pySourceDirs = detection.sourceDirs.python?.source_dirs ?? [];
|
|
22894
|
+
const pyRoot = typeof existing.root === "string" && existing.root !== "" ? existing.root : pySourceDirs.length > 0 ? pySourceDirs[0] : ".";
|
|
22895
|
+
const pythonBlock = {
|
|
22896
|
+
root: pyRoot,
|
|
22897
|
+
exclude_dirs: Array.isArray(existing.exclude_dirs) ? existing.exclude_dirs : ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]
|
|
22898
|
+
};
|
|
22899
|
+
if (existing.domains !== void 0) pythonBlock.domains = existing.domains;
|
|
22900
|
+
if (existing.alembic_dir !== void 0) pythonBlock.alembic_dir = existing.alembic_dir;
|
|
22901
|
+
if (pyFw?.framework && existing.framework === void 0) {
|
|
22902
|
+
pythonBlock.framework = pyFw.framework;
|
|
22903
|
+
} else if (existing.framework !== void 0) {
|
|
22904
|
+
pythonBlock.framework = existing.framework;
|
|
22905
|
+
}
|
|
22906
|
+
if (pyFw?.orm && existing.orm === void 0) {
|
|
22907
|
+
pythonBlock.orm = pyFw.orm;
|
|
22908
|
+
} else if (existing.orm !== void 0) {
|
|
22909
|
+
pythonBlock.orm = existing.orm;
|
|
22910
|
+
}
|
|
22911
|
+
v22.python = pythonBlock;
|
|
22912
|
+
} else if (v1.python !== void 0) {
|
|
22913
|
+
v22.python = v1.python;
|
|
22914
|
+
}
|
|
22915
|
+
return v22;
|
|
22916
|
+
}
|
|
22917
|
+
var PRESERVED_FIELDS2;
|
|
22918
|
+
var init_migrate = __esm({
|
|
22919
|
+
"src/detect/migrate.ts"() {
|
|
22920
|
+
"use strict";
|
|
22921
|
+
PRESERVED_FIELDS2 = [
|
|
22922
|
+
"rules",
|
|
22923
|
+
"domains",
|
|
22924
|
+
"canonical_paths",
|
|
22925
|
+
"verification_types",
|
|
22926
|
+
"detection",
|
|
22927
|
+
"accessScopes",
|
|
22928
|
+
"knownMismatches",
|
|
22929
|
+
"dbAccessPattern",
|
|
22930
|
+
"analytics",
|
|
22931
|
+
"governance",
|
|
22932
|
+
"security",
|
|
22933
|
+
"team",
|
|
22934
|
+
"regression",
|
|
22935
|
+
"cloud",
|
|
22936
|
+
"conventions",
|
|
22937
|
+
"autoLearning"
|
|
22938
|
+
];
|
|
22939
|
+
}
|
|
22940
|
+
});
|
|
22941
|
+
|
|
22942
|
+
// src/commands/config-upgrade.ts
|
|
22943
|
+
var config_upgrade_exports = {};
|
|
22944
|
+
__export(config_upgrade_exports, {
|
|
22945
|
+
runConfigUpgrade: () => runConfigUpgrade
|
|
22946
|
+
});
|
|
22947
|
+
import { existsSync as existsSync27, readFileSync as readFileSync27, writeFileSync as writeFileSync4, copyFileSync, unlinkSync } from "fs";
|
|
22948
|
+
import { resolve as resolve22 } from "path";
|
|
22949
|
+
import { parse as parseYaml6 } from "yaml";
|
|
22950
|
+
async function runConfigUpgrade(opts = {}) {
|
|
22951
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
22952
|
+
const configPath = resolve22(cwd, "massu.config.yaml");
|
|
22953
|
+
const bakPath = `${configPath}.bak`;
|
|
22954
|
+
const log = opts.silent ? () => {
|
|
22955
|
+
} : (s) => process.stdout.write(s);
|
|
22956
|
+
const err = opts.silent ? () => {
|
|
22957
|
+
} : (s) => process.stderr.write(s);
|
|
22958
|
+
if (opts.rollback) {
|
|
22959
|
+
if (!existsSync27(bakPath)) {
|
|
22960
|
+
const message = `No backup found at ${bakPath}`;
|
|
22961
|
+
err(message + "\n");
|
|
22962
|
+
return { exitCode: 1, action: "none", message };
|
|
22963
|
+
}
|
|
22964
|
+
try {
|
|
22965
|
+
copyFileSync(bakPath, configPath);
|
|
22966
|
+
unlinkSync(bakPath);
|
|
22967
|
+
log("Config restored from backup.\n");
|
|
22968
|
+
return { exitCode: 0, action: "rolled-back" };
|
|
22969
|
+
} catch (e2) {
|
|
22970
|
+
const message = `Rollback failed: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
22971
|
+
err(message + "\n");
|
|
22972
|
+
return { exitCode: 2, action: "none", message };
|
|
22973
|
+
}
|
|
22974
|
+
}
|
|
22975
|
+
if (!existsSync27(configPath)) {
|
|
22976
|
+
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
22977
|
+
err(message + "\n");
|
|
22978
|
+
return { exitCode: 1, action: "none", message };
|
|
22979
|
+
}
|
|
22980
|
+
let existing;
|
|
22981
|
+
try {
|
|
22982
|
+
const content = readFileSync27(configPath, "utf-8");
|
|
22983
|
+
const parsed = parseYaml6(content);
|
|
22984
|
+
if (!parsed || typeof parsed !== "object") {
|
|
22985
|
+
throw new Error("config is not a YAML object");
|
|
22986
|
+
}
|
|
22987
|
+
existing = parsed;
|
|
22988
|
+
} catch (e2) {
|
|
22989
|
+
const message = `Failed to parse massu.config.yaml: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
22990
|
+
err(message + "\n");
|
|
22991
|
+
return { exitCode: 2, action: "none", message };
|
|
22992
|
+
}
|
|
22993
|
+
const schemaVersion = existing.schema_version;
|
|
22994
|
+
if (schemaVersion === 2) {
|
|
22995
|
+
log("Config is already at schema_version=2; nothing to do.\n");
|
|
22996
|
+
return { exitCode: 0, action: "already-current" };
|
|
22997
|
+
}
|
|
22998
|
+
const detection = await runDetection(cwd);
|
|
22999
|
+
const v22 = migrateV1ToV2(existing, detection);
|
|
23000
|
+
v22.detection = {
|
|
23001
|
+
...v22.detection ?? {},
|
|
23002
|
+
fingerprint: computeFingerprint(detection)
|
|
23003
|
+
};
|
|
23004
|
+
try {
|
|
23005
|
+
const original = readFileSync27(configPath, "utf-8");
|
|
23006
|
+
writeFileSync4(bakPath, original, "utf-8");
|
|
23007
|
+
} catch (e2) {
|
|
23008
|
+
const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
23009
|
+
err(message + "\n");
|
|
23010
|
+
return { exitCode: 2, action: "none", message };
|
|
23011
|
+
}
|
|
23012
|
+
const yamlContent = renderConfigYaml(v22);
|
|
23013
|
+
const writeRes = writeConfigAtomic(configPath, yamlContent);
|
|
23014
|
+
if (!writeRes.validated) {
|
|
23015
|
+
const message = `Failed to write upgraded config: ${writeRes.error}`;
|
|
23016
|
+
err(message + "\n");
|
|
23017
|
+
return { exitCode: 2, action: "none", message };
|
|
23018
|
+
}
|
|
23019
|
+
void opts.ci;
|
|
23020
|
+
log(`Config upgraded to schema_version=2. Backup saved at ${bakPath}
|
|
23021
|
+
`);
|
|
23022
|
+
return { exitCode: 0, action: "migrated" };
|
|
23023
|
+
}
|
|
23024
|
+
var init_config_upgrade = __esm({
|
|
23025
|
+
"src/commands/config-upgrade.ts"() {
|
|
23026
|
+
"use strict";
|
|
23027
|
+
init_detect();
|
|
23028
|
+
init_drift();
|
|
23029
|
+
init_migrate();
|
|
23030
|
+
init_init();
|
|
23031
|
+
}
|
|
23032
|
+
});
|
|
23033
|
+
|
|
23034
|
+
// src/commands/config-check-drift.ts
|
|
23035
|
+
var config_check_drift_exports = {};
|
|
23036
|
+
__export(config_check_drift_exports, {
|
|
23037
|
+
runConfigCheckDrift: () => runConfigCheckDrift
|
|
23038
|
+
});
|
|
23039
|
+
import { existsSync as existsSync28, readFileSync as readFileSync28 } from "fs";
|
|
23040
|
+
import { resolve as resolve23 } from "path";
|
|
23041
|
+
import { parse as parseYaml7 } from "yaml";
|
|
23042
|
+
function renderChanges(changes) {
|
|
23043
|
+
if (changes.length === 0) return "(none)\n";
|
|
23044
|
+
return changes.map((c2) => ` ${c2.field}: ${JSON.stringify(c2.before)} -> ${JSON.stringify(c2.after)}`).join("\n") + "\n";
|
|
23045
|
+
}
|
|
23046
|
+
async function runConfigCheckDrift(opts = {}) {
|
|
23047
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
23048
|
+
const configPath = resolve23(cwd, "massu.config.yaml");
|
|
23049
|
+
const log = opts.silent ? () => {
|
|
23050
|
+
} : (s) => process.stdout.write(s);
|
|
23051
|
+
const err = opts.silent ? () => {
|
|
23052
|
+
} : (s) => process.stderr.write(s);
|
|
23053
|
+
if (!existsSync28(configPath)) {
|
|
23054
|
+
const message = "massu.config.yaml not found. Run: npx massu init";
|
|
23055
|
+
err(message + "\n");
|
|
23056
|
+
return {
|
|
23057
|
+
exitCode: 2,
|
|
23058
|
+
drifted: false,
|
|
23059
|
+
changes: [],
|
|
23060
|
+
storedFingerprint: null,
|
|
23061
|
+
currentFingerprint: null,
|
|
23062
|
+
message
|
|
23063
|
+
};
|
|
23064
|
+
}
|
|
23065
|
+
let config;
|
|
23066
|
+
try {
|
|
23067
|
+
const content = readFileSync28(configPath, "utf-8");
|
|
23068
|
+
const parsed = parseYaml7(content);
|
|
23069
|
+
if (!parsed || typeof parsed !== "object") {
|
|
23070
|
+
throw new Error("config is not a YAML object");
|
|
23071
|
+
}
|
|
23072
|
+
config = parsed;
|
|
23073
|
+
} catch (e2) {
|
|
23074
|
+
const message = `Failed to parse massu.config.yaml: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
23075
|
+
err(message + "\n");
|
|
23076
|
+
return {
|
|
23077
|
+
exitCode: 2,
|
|
23078
|
+
drifted: false,
|
|
23079
|
+
changes: [],
|
|
23080
|
+
storedFingerprint: null,
|
|
23081
|
+
currentFingerprint: null,
|
|
23082
|
+
message
|
|
23083
|
+
};
|
|
23084
|
+
}
|
|
23085
|
+
const detection = await runDetection(cwd);
|
|
23086
|
+
const currentFp = computeFingerprint(detection);
|
|
23087
|
+
const storedFp = typeof config.detection?.fingerprint === "string" ? config.detection.fingerprint : null;
|
|
23088
|
+
const report = detectDrift(config, detection);
|
|
23089
|
+
const fingerprintDrift = storedFp !== null && storedFp !== currentFp;
|
|
23090
|
+
const drifted = report.drifted || fingerprintDrift;
|
|
23091
|
+
if (!drifted) {
|
|
23092
|
+
log("No drift detected.\n");
|
|
23093
|
+
if (opts.verbose) {
|
|
23094
|
+
log(`Fingerprint: ${currentFp}
|
|
23095
|
+
`);
|
|
23096
|
+
}
|
|
23097
|
+
return {
|
|
23098
|
+
exitCode: 0,
|
|
23099
|
+
drifted: false,
|
|
23100
|
+
changes: report.changes,
|
|
23101
|
+
storedFingerprint: storedFp,
|
|
23102
|
+
currentFingerprint: currentFp
|
|
23103
|
+
};
|
|
23104
|
+
}
|
|
23105
|
+
err("Config drift detected; run `npx massu config refresh` to update.\n");
|
|
23106
|
+
if (opts.verbose) {
|
|
23107
|
+
if (storedFp !== null) {
|
|
23108
|
+
log(`Fingerprint: ${storedFp.slice(0, 16)} -> ${currentFp.slice(0, 16)}
|
|
23109
|
+
`);
|
|
23110
|
+
} else {
|
|
23111
|
+
log(`Fingerprint (new): ${currentFp.slice(0, 16)}
|
|
23112
|
+
`);
|
|
23113
|
+
}
|
|
23114
|
+
log("Changes:\n");
|
|
23115
|
+
log(renderChanges(report.changes));
|
|
23116
|
+
}
|
|
23117
|
+
return {
|
|
23118
|
+
exitCode: 1,
|
|
23119
|
+
drifted: true,
|
|
23120
|
+
changes: report.changes,
|
|
23121
|
+
storedFingerprint: storedFp,
|
|
23122
|
+
currentFingerprint: currentFp
|
|
23123
|
+
};
|
|
23124
|
+
}
|
|
23125
|
+
var init_config_check_drift = __esm({
|
|
23126
|
+
"src/commands/config-check-drift.ts"() {
|
|
23127
|
+
"use strict";
|
|
23128
|
+
init_detect();
|
|
23129
|
+
init_drift();
|
|
23130
|
+
}
|
|
23131
|
+
});
|
|
23132
|
+
|
|
22445
23133
|
// src/cli.ts
|
|
22446
|
-
import { readFileSync as
|
|
22447
|
-
import { resolve as
|
|
23134
|
+
import { readFileSync as readFileSync29 } from "fs";
|
|
23135
|
+
import { resolve as resolve24, dirname as dirname12 } from "path";
|
|
22448
23136
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
22449
23137
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
22450
23138
|
var __dirname5 = dirname12(__filename4);
|
|
@@ -22477,6 +23165,10 @@ async function main() {
|
|
|
22477
23165
|
await runValidateConfig2();
|
|
22478
23166
|
break;
|
|
22479
23167
|
}
|
|
23168
|
+
case "config": {
|
|
23169
|
+
await handleConfigSubcommand(args.slice(1));
|
|
23170
|
+
break;
|
|
23171
|
+
}
|
|
22480
23172
|
case "--help":
|
|
22481
23173
|
case "-h": {
|
|
22482
23174
|
printHelp();
|
|
@@ -22492,6 +23184,56 @@ async function main() {
|
|
|
22492
23184
|
}
|
|
22493
23185
|
}
|
|
22494
23186
|
}
|
|
23187
|
+
async function handleConfigSubcommand(configArgs) {
|
|
23188
|
+
const sub = configArgs[0];
|
|
23189
|
+
const flags = new Set(configArgs.slice(1));
|
|
23190
|
+
switch (sub) {
|
|
23191
|
+
case "refresh": {
|
|
23192
|
+
const { runConfigRefresh: runConfigRefresh2 } = await Promise.resolve().then(() => (init_config_refresh(), config_refresh_exports));
|
|
23193
|
+
const result = await runConfigRefresh2({ dryRun: flags.has("--dry-run") });
|
|
23194
|
+
process.exit(result.exitCode);
|
|
23195
|
+
return;
|
|
23196
|
+
}
|
|
23197
|
+
case "validate": {
|
|
23198
|
+
const { runValidateConfig: runValidateConfig2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
23199
|
+
await runValidateConfig2();
|
|
23200
|
+
return;
|
|
23201
|
+
}
|
|
23202
|
+
case "upgrade": {
|
|
23203
|
+
const { runConfigUpgrade: runConfigUpgrade2 } = await Promise.resolve().then(() => (init_config_upgrade(), config_upgrade_exports));
|
|
23204
|
+
const result = await runConfigUpgrade2({
|
|
23205
|
+
rollback: flags.has("--rollback"),
|
|
23206
|
+
ci: flags.has("--ci") || flags.has("--yes")
|
|
23207
|
+
});
|
|
23208
|
+
process.exit(result.exitCode);
|
|
23209
|
+
return;
|
|
23210
|
+
}
|
|
23211
|
+
case "doctor": {
|
|
23212
|
+
const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
23213
|
+
await runDoctor2();
|
|
23214
|
+
return;
|
|
23215
|
+
}
|
|
23216
|
+
case "check-drift": {
|
|
23217
|
+
const { runConfigCheckDrift: runConfigCheckDrift2 } = await Promise.resolve().then(() => (init_config_check_drift(), config_check_drift_exports));
|
|
23218
|
+
const result = await runConfigCheckDrift2({ verbose: flags.has("--verbose") });
|
|
23219
|
+
process.exit(result.exitCode);
|
|
23220
|
+
return;
|
|
23221
|
+
}
|
|
23222
|
+
case "--help":
|
|
23223
|
+
case "-h":
|
|
23224
|
+
case void 0: {
|
|
23225
|
+
printConfigHelp();
|
|
23226
|
+
return;
|
|
23227
|
+
}
|
|
23228
|
+
default: {
|
|
23229
|
+
process.stderr.write(`massu: unknown config subcommand: ${sub}
|
|
23230
|
+
`);
|
|
23231
|
+
printConfigHelp();
|
|
23232
|
+
process.exit(1);
|
|
23233
|
+
return;
|
|
23234
|
+
}
|
|
23235
|
+
}
|
|
23236
|
+
}
|
|
22495
23237
|
function printHelp() {
|
|
22496
23238
|
console.log(`
|
|
22497
23239
|
Massu AI - Engineering Governance Platform
|
|
@@ -22504,7 +23246,8 @@ Commands:
|
|
|
22504
23246
|
doctor Check installation health
|
|
22505
23247
|
install-hooks Install/update Claude Code hooks
|
|
22506
23248
|
install-commands Install/update slash commands
|
|
22507
|
-
validate-config Validate massu.config.yaml
|
|
23249
|
+
validate-config Validate massu.config.yaml (alias: config validate)
|
|
23250
|
+
config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
|
|
22508
23251
|
|
|
22509
23252
|
Options:
|
|
22510
23253
|
--help, -h Show this help message
|
|
@@ -22513,13 +23256,35 @@ Options:
|
|
|
22513
23256
|
Getting started:
|
|
22514
23257
|
npx massu init # Full setup in one command
|
|
22515
23258
|
npx massu init --help # Show all init options (--ci, --force, --template)
|
|
23259
|
+
npx massu config --help # Show config subcommands
|
|
22516
23260
|
|
|
22517
23261
|
Documentation: https://massu.ai/docs
|
|
22518
23262
|
`);
|
|
22519
23263
|
}
|
|
23264
|
+
function printConfigHelp() {
|
|
23265
|
+
console.log(`
|
|
23266
|
+
massu config <subcommand>
|
|
23267
|
+
|
|
23268
|
+
Subcommands:
|
|
23269
|
+
refresh Re-run detection and apply changes to massu.config.yaml.
|
|
23270
|
+
--dry-run Print diff and exit without writing.
|
|
23271
|
+
validate Validate massu.config.yaml (alias of \`massu validate-config\`).
|
|
23272
|
+
upgrade Migrate a v1 config to schema_version=2.
|
|
23273
|
+
--rollback Restore from .bak file.
|
|
23274
|
+
--ci, --yes Non-interactive mode (no prompts).
|
|
23275
|
+
doctor Run the full health check (alias of \`massu doctor\`).
|
|
23276
|
+
check-drift CI-safe drift gate; exits 1 on drift.
|
|
23277
|
+
--verbose Print detailed changes to stdout.
|
|
23278
|
+
|
|
23279
|
+
Examples:
|
|
23280
|
+
npx massu config refresh --dry-run
|
|
23281
|
+
npx massu config upgrade --ci
|
|
23282
|
+
npx massu config check-drift --verbose
|
|
23283
|
+
`);
|
|
23284
|
+
}
|
|
22520
23285
|
function printVersion() {
|
|
22521
23286
|
try {
|
|
22522
|
-
const pkg = JSON.parse(
|
|
23287
|
+
const pkg = JSON.parse(readFileSync29(resolve24(__dirname5, "../package.json"), "utf-8"));
|
|
22523
23288
|
console.log(`massu v${pkg.version}`);
|
|
22524
23289
|
} catch {
|
|
22525
23290
|
console.log("massu v0.1.0");
|