@massu/core 1.0.0 → 1.2.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 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 flatten(items) {
3280
+ function flatten2(items) {
3281
3281
  return items.reduce((collection, item) => [].concat(collection, item), []);
3282
3282
  }
3283
- exports.flatten = 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(resolve22, reject) {
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
- resolve22(result);
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(resolve22, reject) {
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
- resolve22(result);
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(resolve22) {
7826
+ var p19 = new Promise(function(resolve25) {
7827
7827
  process.nextTick(function() {
7828
7828
  if (queue.idle()) {
7829
- resolve22();
7829
+ resolve25();
7830
7830
  } else {
7831
7831
  var previousDrain = queue.drain;
7832
7832
  queue.drain = function() {
7833
7833
  if (typeof previousDrain === "function") previousDrain();
7834
- resolve22();
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((resolve22, reject) => {
8320
+ return new Promise((resolve25, reject) => {
8321
8321
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
8322
- return error === null ? resolve22(stats) : reject(error);
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((resolve22, reject) => {
8346
+ return new Promise((resolve25, reject) => {
8347
8347
  this._walkAsync(root, options, (error, entries) => {
8348
8348
  if (error === null) {
8349
- resolve22(entries);
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((resolve22, reject) => {
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", () => resolve22(entries));
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 summarizeDetection(detection) {
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: ${summarizeDetection(detection)}`);
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 = createHash("sha256").update(apiKey).digest("hex");
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 = createHash("sha256").update(apiKey).digest("hex");
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 createHash2 } from "crypto";
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 createHash2("sha256").update(content).digest("hex");
19223
+ return createHash3("sha256").update(content).digest("hex");
19096
19224
  }
19097
19225
  function parseCRTable(content) {
19098
19226
  const rules = [];
@@ -22442,9 +22570,691 @@ var init_server = __esm({
22442
22570
  }
22443
22571
  });
22444
22572
 
22573
+ // src/detect/passthrough.ts
22574
+ function copyUnknownKeys(source, target, handledKeys) {
22575
+ if (source === null || typeof source !== "object" || Array.isArray(source)) {
22576
+ return;
22577
+ }
22578
+ for (const k3 of Object.keys(source)) {
22579
+ if (UNSAFE_KEYS.has(k3)) continue;
22580
+ if (source[k3] === void 0) continue;
22581
+ if (handledKeys.has(k3)) continue;
22582
+ if (Object.prototype.hasOwnProperty.call(target, k3)) continue;
22583
+ target[k3] = safeClone(source[k3]);
22584
+ }
22585
+ }
22586
+ function preserveNestedSubkeys(sourceBlock, targetBlock) {
22587
+ if (sourceBlock === null || sourceBlock === void 0 || typeof sourceBlock !== "object" || Array.isArray(sourceBlock)) {
22588
+ return;
22589
+ }
22590
+ const src = sourceBlock;
22591
+ for (const k3 of Object.keys(src)) {
22592
+ if (UNSAFE_KEYS.has(k3)) continue;
22593
+ if (src[k3] === void 0) continue;
22594
+ if (Object.prototype.hasOwnProperty.call(targetBlock, k3)) continue;
22595
+ targetBlock[k3] = safeClone(src[k3]);
22596
+ }
22597
+ }
22598
+ function safeClone(v3) {
22599
+ if (typeof structuredClone === "function") {
22600
+ try {
22601
+ return structuredClone(v3);
22602
+ } catch {
22603
+ }
22604
+ }
22605
+ return v3;
22606
+ }
22607
+ var UNSAFE_KEYS;
22608
+ var init_passthrough = __esm({
22609
+ "src/detect/passthrough.ts"() {
22610
+ "use strict";
22611
+ UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
22612
+ }
22613
+ });
22614
+
22615
+ // src/commands/config-refresh.ts
22616
+ var config_refresh_exports = {};
22617
+ __export(config_refresh_exports, {
22618
+ computeDiff: () => computeDiff,
22619
+ mergeRefresh: () => mergeRefresh,
22620
+ runConfigRefresh: () => runConfigRefresh
22621
+ });
22622
+ import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
22623
+ import { resolve as resolve21 } from "path";
22624
+ import { parse as parseYaml5 } from "yaml";
22625
+ function flatten(obj, prefix3 = "") {
22626
+ const out = {};
22627
+ if (obj === null || obj === void 0) {
22628
+ out[prefix3 || "<root>"] = obj;
22629
+ return out;
22630
+ }
22631
+ if (typeof obj !== "object" || Array.isArray(obj)) {
22632
+ out[prefix3 || "<root>"] = obj;
22633
+ return out;
22634
+ }
22635
+ const rec = obj;
22636
+ for (const [k3, v3] of Object.entries(rec)) {
22637
+ const p19 = prefix3 ? `${prefix3}.${k3}` : k3;
22638
+ if (v3 !== null && typeof v3 === "object" && !Array.isArray(v3)) {
22639
+ Object.assign(out, flatten(v3, p19));
22640
+ } else {
22641
+ out[p19] = v3;
22642
+ }
22643
+ }
22644
+ return out;
22645
+ }
22646
+ function computeDiff(before, after) {
22647
+ const b2 = flatten(before);
22648
+ const a2 = flatten(after);
22649
+ const keys = /* @__PURE__ */ new Set([...Object.keys(b2), ...Object.keys(a2)]);
22650
+ const sorted = [...keys].sort();
22651
+ const lines = [];
22652
+ for (const k3 of sorted) {
22653
+ const bVal = b2[k3];
22654
+ const aVal = a2[k3];
22655
+ const bHas = k3 in b2;
22656
+ const aHas = k3 in a2;
22657
+ if (bHas && !aHas) {
22658
+ lines.push({ kind: "remove", path: k3, before: bVal });
22659
+ } else if (!bHas && aHas) {
22660
+ lines.push({ kind: "add", path: k3, after: aVal });
22661
+ } else if (JSON.stringify(bVal) !== JSON.stringify(aVal)) {
22662
+ lines.push({ kind: "change", path: k3, before: bVal, after: aVal });
22663
+ }
22664
+ }
22665
+ return lines;
22666
+ }
22667
+ function mergeRefresh(existing, refreshed) {
22668
+ const out = { ...refreshed };
22669
+ for (const field of PRESERVED_FIELDS) {
22670
+ if (existing[field] !== void 0) {
22671
+ out[field] = existing[field];
22672
+ }
22673
+ }
22674
+ if (typeof existing.toolPrefix === "string" && existing.toolPrefix !== "") {
22675
+ out.toolPrefix = existing.toolPrefix;
22676
+ }
22677
+ for (const block of ["framework", "paths", "project"]) {
22678
+ const existingBlock = existing[block];
22679
+ const outBlock = out[block];
22680
+ if (existingBlock && typeof existingBlock === "object" && !Array.isArray(existingBlock) && outBlock && typeof outBlock === "object" && !Array.isArray(outBlock)) {
22681
+ preserveNestedSubkeys(existingBlock, outBlock);
22682
+ }
22683
+ }
22684
+ const existingProject = existing.project;
22685
+ const outProject = out.project;
22686
+ if (existingProject && typeof existingProject === "object" && !Array.isArray(existingProject) && outProject && typeof outProject === "object" && !Array.isArray(outProject)) {
22687
+ const userRoot = existingProject.root;
22688
+ if (typeof userRoot === "string" && userRoot !== "") {
22689
+ outProject.root = userRoot;
22690
+ }
22691
+ }
22692
+ const existingPaths = existing.paths;
22693
+ const outPaths = out.paths;
22694
+ if (existingPaths && typeof existingPaths === "object" && !Array.isArray(existingPaths) && outPaths && typeof outPaths === "object" && !Array.isArray(outPaths)) {
22695
+ const existingAliases = existingPaths.aliases;
22696
+ const outAliases = outPaths.aliases;
22697
+ if (existingAliases && typeof existingAliases === "object" && !Array.isArray(existingAliases) && outAliases && typeof outAliases === "object" && !Array.isArray(outAliases)) {
22698
+ outPaths.aliases = {
22699
+ ...outAliases,
22700
+ ...existingAliases
22701
+ };
22702
+ } else if (existingAliases && typeof existingAliases === "object" && !Array.isArray(existingAliases)) {
22703
+ outPaths.aliases = existingAliases;
22704
+ }
22705
+ }
22706
+ const existingVer = existing.verification;
22707
+ const outVer = out.verification;
22708
+ if (existingVer && typeof existingVer === "object" && !Array.isArray(existingVer) && outVer && typeof outVer === "object" && !Array.isArray(outVer)) {
22709
+ const eVer = existingVer;
22710
+ const oVer = outVer;
22711
+ for (const lang of Object.keys(eVer)) {
22712
+ const userLang = eVer[lang];
22713
+ if (userLang === void 0) continue;
22714
+ if (!(lang in oVer)) {
22715
+ oVer[lang] = userLang;
22716
+ } else if (userLang && typeof userLang === "object" && !Array.isArray(userLang) && oVer[lang] && typeof oVer[lang] === "object" && !Array.isArray(oVer[lang])) {
22717
+ oVer[lang] = {
22718
+ ...oVer[lang],
22719
+ ...userLang
22720
+ };
22721
+ }
22722
+ }
22723
+ }
22724
+ const handledTopLevel = /* @__PURE__ */ new Set([
22725
+ "schema_version",
22726
+ "project",
22727
+ "framework",
22728
+ "paths",
22729
+ "toolPrefix",
22730
+ "verification",
22731
+ "detection",
22732
+ ...PRESERVED_FIELDS
22733
+ ]);
22734
+ copyUnknownKeys(existing, out, handledTopLevel);
22735
+ return out;
22736
+ }
22737
+ function renderDiff(diff) {
22738
+ if (diff.length === 0) return "(no changes)\n";
22739
+ const lines = [];
22740
+ for (const d2 of diff) {
22741
+ if (d2.kind === "add") lines.push(`+ ${d2.path}: ${JSON.stringify(d2.after)}`);
22742
+ else if (d2.kind === "remove") lines.push(`- ${d2.path}: ${JSON.stringify(d2.before)}`);
22743
+ else if (d2.kind === "change") {
22744
+ lines.push(`~ ${d2.path}: ${JSON.stringify(d2.before)} -> ${JSON.stringify(d2.after)}`);
22745
+ }
22746
+ }
22747
+ return lines.join("\n") + "\n";
22748
+ }
22749
+ async function runConfigRefresh(opts = {}) {
22750
+ const cwd = opts.cwd ?? process.cwd();
22751
+ const configPath = resolve21(cwd, "massu.config.yaml");
22752
+ const log = opts.silent ? () => {
22753
+ } : (s) => process.stdout.write(s);
22754
+ if (!existsSync26(configPath)) {
22755
+ const message = "massu.config.yaml not found. Run: npx massu init";
22756
+ if (!opts.silent) process.stderr.write(message + "\n");
22757
+ return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
22758
+ }
22759
+ let existing;
22760
+ try {
22761
+ const content = readFileSync26(configPath, "utf-8");
22762
+ const parsed = parseYaml5(content);
22763
+ if (!parsed || typeof parsed !== "object") {
22764
+ throw new Error("config is not a YAML object");
22765
+ }
22766
+ existing = parsed;
22767
+ } catch (err) {
22768
+ const message = `Failed to parse massu.config.yaml: ${err instanceof Error ? err.message : String(err)}`;
22769
+ if (!opts.silent) process.stderr.write(message + "\n");
22770
+ return { exitCode: 2, applied: false, dryRun: !!opts.dryRun, diff: [], message };
22771
+ }
22772
+ const detection = await runDetection(cwd);
22773
+ const refreshed = buildConfigFromDetection({
22774
+ projectRoot: cwd,
22775
+ detection,
22776
+ projectName: typeof existing.project?.name === "string" ? existing.project.name : void 0
22777
+ });
22778
+ if (!refreshed.detection?.fingerprint) {
22779
+ refreshed.detection = { fingerprint: computeFingerprint(detection) };
22780
+ }
22781
+ const merged = mergeRefresh(existing, refreshed);
22782
+ const diff = computeDiff(existing, merged);
22783
+ if (opts.dryRun) {
22784
+ log("Config diff (dry-run; no changes written):\n");
22785
+ log(renderDiff(diff));
22786
+ return { exitCode: 0, applied: false, dryRun: true, diff };
22787
+ }
22788
+ if (diff.length === 0) {
22789
+ log("No changes needed \u2014 config is already up to date.\n");
22790
+ return { exitCode: 0, applied: false, dryRun: false, diff };
22791
+ }
22792
+ if (!process.stdin.isTTY) {
22793
+ log("Config diff (non-interactive; pass --dry-run to suppress this note or run interactively to apply):\n");
22794
+ log(renderDiff(diff));
22795
+ return {
22796
+ exitCode: 0,
22797
+ applied: false,
22798
+ dryRun: false,
22799
+ diff,
22800
+ message: "non-interactive shell; no changes written"
22801
+ };
22802
+ }
22803
+ log("Config diff:\n");
22804
+ log(renderDiff(diff));
22805
+ const { confirm } = await Promise.resolve().then(() => (init_dist3(), dist_exports));
22806
+ const apply = await confirm({ message: "Apply these changes to massu.config.yaml?" });
22807
+ if (apply !== true) {
22808
+ log("Aborted; no changes written.\n");
22809
+ return { exitCode: 0, applied: false, dryRun: false, diff, message: "aborted by user" };
22810
+ }
22811
+ const yamlContent = renderConfigYaml(merged);
22812
+ const writeRes = writeConfigAtomic(configPath, yamlContent);
22813
+ if (!writeRes.validated) {
22814
+ const message = `Failed to write config: ${writeRes.error}`;
22815
+ if (!opts.silent) process.stderr.write(message + "\n");
22816
+ return { exitCode: 2, applied: false, dryRun: false, diff, message };
22817
+ }
22818
+ log("Config refreshed.\n");
22819
+ return { exitCode: 0, applied: true, dryRun: false, diff };
22820
+ }
22821
+ var PRESERVED_FIELDS;
22822
+ var init_config_refresh = __esm({
22823
+ "src/commands/config-refresh.ts"() {
22824
+ "use strict";
22825
+ init_detect();
22826
+ init_drift();
22827
+ init_passthrough();
22828
+ init_init();
22829
+ PRESERVED_FIELDS = [
22830
+ "rules",
22831
+ "domains",
22832
+ "canonical_paths",
22833
+ "verification_types",
22834
+ "accessScopes",
22835
+ "knownMismatches",
22836
+ "dbAccessPattern",
22837
+ "analytics",
22838
+ "governance",
22839
+ "security",
22840
+ "team",
22841
+ "regression",
22842
+ "cloud",
22843
+ "conventions",
22844
+ "autoLearning",
22845
+ "python",
22846
+ "toolPrefix"
22847
+ ];
22848
+ }
22849
+ });
22850
+
22851
+ // src/detect/migrate.ts
22852
+ function getRecord(obj) {
22853
+ if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
22854
+ return obj;
22855
+ }
22856
+ return {};
22857
+ }
22858
+ function isNoneOrDefault(v3) {
22859
+ if (v3 === void 0 || v3 === null) return true;
22860
+ if (typeof v3 !== "string") return false;
22861
+ return v3 === "none" || v3 === "";
22862
+ }
22863
+ function chooseString(user, detected) {
22864
+ if (typeof user === "string" && !isNoneOrDefault(user)) return user;
22865
+ if (typeof detected === "string" && detected !== "") return detected;
22866
+ if (typeof user === "string") return user;
22867
+ return "none";
22868
+ }
22869
+ function buildLanguageEntries(detection) {
22870
+ const languages = Array.from(
22871
+ new Set(detection.manifests.map((m3) => m3.language))
22872
+ );
22873
+ const entries = {};
22874
+ for (const lang of languages) {
22875
+ const fw = detection.frameworks[lang];
22876
+ const dirInfo = detection.sourceDirs[lang];
22877
+ const sourceDirs = dirInfo?.source_dirs ?? [];
22878
+ const entry = {};
22879
+ if (fw?.framework) entry.framework = fw.framework;
22880
+ if (fw?.test_framework) entry.test_framework = fw.test_framework;
22881
+ if (fw?.orm) entry.orm = fw.orm;
22882
+ if (fw?.router) entry.router = fw.router;
22883
+ if (fw?.ui_library) entry.ui = fw.ui_library;
22884
+ if (sourceDirs.length > 0) entry.source_dirs = sourceDirs;
22885
+ if (Object.keys(entry).length > 0) {
22886
+ entries[lang] = entry;
22887
+ }
22888
+ }
22889
+ return entries;
22890
+ }
22891
+ function pickPrimary(detection) {
22892
+ const counts = /* @__PURE__ */ new Map();
22893
+ for (const m3 of detection.manifests) {
22894
+ counts.set(m3.language, (counts.get(m3.language) ?? 0) + 1);
22895
+ }
22896
+ const sorted = [...counts.entries()].sort((a2, b2) => {
22897
+ if (b2[1] !== a2[1]) return b2[1] - a2[1];
22898
+ return a2[0].localeCompare(b2[0]);
22899
+ });
22900
+ return sorted.length > 0 ? sorted[0][0] : null;
22901
+ }
22902
+ function buildVerificationBlock(detection, userVerification) {
22903
+ const ver = {};
22904
+ const languages = Array.from(
22905
+ new Set(detection.manifests.map((m3) => m3.language))
22906
+ );
22907
+ for (const lang of languages) {
22908
+ const cmds = detection.verificationCommands[lang];
22909
+ if (!cmds) continue;
22910
+ const entry = {};
22911
+ if (cmds.test) entry.test = cmds.test;
22912
+ if (cmds.type) entry.type = cmds.type;
22913
+ if (cmds.build) entry.build = cmds.build;
22914
+ if (cmds.syntax) entry.syntax = cmds.syntax;
22915
+ if (cmds.lint) entry.lint = cmds.lint;
22916
+ if (Object.keys(entry).length > 0) ver[lang] = entry;
22917
+ }
22918
+ if (userVerification) {
22919
+ for (const [lang, userEntry] of Object.entries(userVerification)) {
22920
+ if (typeof userEntry !== "object" || userEntry === null) continue;
22921
+ ver[lang] = { ...ver[lang] ?? {}, ...userEntry };
22922
+ }
22923
+ }
22924
+ return ver;
22925
+ }
22926
+ function migrateV1ToV2(v1Config, detection) {
22927
+ const v1 = getRecord(v1Config);
22928
+ const v1Framework = getRecord(v1.framework);
22929
+ const v1Paths = getRecord(v1.paths);
22930
+ const v1Project = getRecord(v1.project);
22931
+ const v1Verification = v1.verification;
22932
+ const languages = Array.from(
22933
+ new Set(detection.manifests.map((m3) => m3.language))
22934
+ );
22935
+ const languageEntries = buildLanguageEntries(detection);
22936
+ const primary = pickPrimary(detection);
22937
+ const frameworkType = languages.length > 1 ? "multi" : languages[0] ?? (typeof v1Framework.type === "string" ? v1Framework.type : "typescript");
22938
+ const primaryEntry = primary ? languageEntries[primary] : void 0;
22939
+ const primaryRouter = primaryEntry?.router;
22940
+ const primaryOrm = primaryEntry?.orm;
22941
+ const primaryUi = primaryEntry?.ui;
22942
+ const legacyRouter = chooseString(v1Framework.router, primaryRouter);
22943
+ const legacyOrm = chooseString(v1Framework.orm, primaryOrm);
22944
+ const legacyUi = chooseString(v1Framework.ui, primaryUi);
22945
+ const framework = {
22946
+ type: frameworkType,
22947
+ router: legacyRouter,
22948
+ orm: legacyOrm,
22949
+ ui: legacyUi
22950
+ };
22951
+ if (languages.length > 1 && primary) {
22952
+ framework.primary = primary;
22953
+ }
22954
+ if (Object.keys(languageEntries).length > 0) {
22955
+ framework.languages = languageEntries;
22956
+ }
22957
+ preserveNestedSubkeys(v1Framework, framework);
22958
+ let pathsSource = typeof v1Paths.source === "string" ? v1Paths.source : "src";
22959
+ if (pathsSource === "src" && primary) {
22960
+ const primaryDirs = detection.sourceDirs[primary]?.source_dirs ?? [];
22961
+ if (primaryDirs.length > 0) pathsSource = primaryDirs[0];
22962
+ }
22963
+ const aliases = v1Paths.aliases && typeof v1Paths.aliases === "object" ? v1Paths.aliases : { "@": pathsSource };
22964
+ const paths = {
22965
+ source: pathsSource,
22966
+ aliases
22967
+ };
22968
+ for (const k3 of ["routers", "routerRoot", "pages", "middleware", "schema", "components", "hooks"]) {
22969
+ if (typeof v1Paths[k3] === "string") paths[k3] = v1Paths[k3];
22970
+ }
22971
+ preserveNestedSubkeys(v1Paths, paths);
22972
+ const verification = buildVerificationBlock(detection, v1Verification);
22973
+ const project = {
22974
+ name: typeof v1Project.name === "string" ? v1Project.name : "my-project",
22975
+ root: typeof v1Project.root === "string" ? v1Project.root : "auto"
22976
+ };
22977
+ preserveNestedSubkeys(v1Project, project);
22978
+ const v22 = {
22979
+ schema_version: 2,
22980
+ project,
22981
+ framework,
22982
+ paths,
22983
+ toolPrefix: typeof v1.toolPrefix === "string" ? v1.toolPrefix : "massu"
22984
+ };
22985
+ for (const field of PRESERVED_FIELDS2) {
22986
+ if (field in v1 && v1[field] !== void 0) {
22987
+ v22[field] = v1[field];
22988
+ }
22989
+ }
22990
+ const handledTopLevel = /* @__PURE__ */ new Set([
22991
+ "schema_version",
22992
+ "project",
22993
+ "framework",
22994
+ "paths",
22995
+ "toolPrefix",
22996
+ "verification",
22997
+ "python",
22998
+ ...PRESERVED_FIELDS2
22999
+ ]);
23000
+ copyUnknownKeys(v1, v22, handledTopLevel);
23001
+ if (!Array.isArray(v22.domains)) {
23002
+ v22.domains = [];
23003
+ }
23004
+ if (!Array.isArray(v22.rules)) {
23005
+ v22.rules = [];
23006
+ }
23007
+ if (Object.keys(verification).length > 0) {
23008
+ v22.verification = verification;
23009
+ }
23010
+ if (languages.includes("python")) {
23011
+ const existing = getRecord(v1.python);
23012
+ const pyFw = detection.frameworks.python;
23013
+ const pySourceDirs = detection.sourceDirs.python?.source_dirs ?? [];
23014
+ const pyRoot = typeof existing.root === "string" && existing.root !== "" ? existing.root : pySourceDirs.length > 0 ? pySourceDirs[0] : ".";
23015
+ const pythonBlock = {
23016
+ root: pyRoot,
23017
+ exclude_dirs: Array.isArray(existing.exclude_dirs) ? existing.exclude_dirs : ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]
23018
+ };
23019
+ if (existing.domains !== void 0) pythonBlock.domains = existing.domains;
23020
+ if (existing.alembic_dir !== void 0) pythonBlock.alembic_dir = existing.alembic_dir;
23021
+ if (pyFw?.framework && existing.framework === void 0) {
23022
+ pythonBlock.framework = pyFw.framework;
23023
+ } else if (existing.framework !== void 0) {
23024
+ pythonBlock.framework = existing.framework;
23025
+ }
23026
+ if (pyFw?.orm && existing.orm === void 0) {
23027
+ pythonBlock.orm = pyFw.orm;
23028
+ } else if (existing.orm !== void 0) {
23029
+ pythonBlock.orm = existing.orm;
23030
+ }
23031
+ preserveNestedSubkeys(v1.python, pythonBlock);
23032
+ v22.python = pythonBlock;
23033
+ } else if (v1.python !== void 0) {
23034
+ v22.python = v1.python;
23035
+ }
23036
+ return v22;
23037
+ }
23038
+ var PRESERVED_FIELDS2;
23039
+ var init_migrate = __esm({
23040
+ "src/detect/migrate.ts"() {
23041
+ "use strict";
23042
+ init_passthrough();
23043
+ PRESERVED_FIELDS2 = [
23044
+ "rules",
23045
+ "domains",
23046
+ "canonical_paths",
23047
+ "verification_types",
23048
+ "detection",
23049
+ "accessScopes",
23050
+ "knownMismatches",
23051
+ "dbAccessPattern",
23052
+ "analytics",
23053
+ "governance",
23054
+ "security",
23055
+ "team",
23056
+ "regression",
23057
+ "cloud",
23058
+ "conventions",
23059
+ "autoLearning"
23060
+ ];
23061
+ }
23062
+ });
23063
+
23064
+ // src/commands/config-upgrade.ts
23065
+ var config_upgrade_exports = {};
23066
+ __export(config_upgrade_exports, {
23067
+ runConfigUpgrade: () => runConfigUpgrade
23068
+ });
23069
+ import { existsSync as existsSync27, readFileSync as readFileSync27, writeFileSync as writeFileSync4, copyFileSync, unlinkSync } from "fs";
23070
+ import { resolve as resolve22 } from "path";
23071
+ import { parse as parseYaml6 } from "yaml";
23072
+ async function runConfigUpgrade(opts = {}) {
23073
+ const cwd = opts.cwd ?? process.cwd();
23074
+ const configPath = resolve22(cwd, "massu.config.yaml");
23075
+ const bakPath = `${configPath}.bak`;
23076
+ const log = opts.silent ? () => {
23077
+ } : (s) => process.stdout.write(s);
23078
+ const err = opts.silent ? () => {
23079
+ } : (s) => process.stderr.write(s);
23080
+ if (opts.rollback) {
23081
+ if (!existsSync27(bakPath)) {
23082
+ const message = `No backup found at ${bakPath}`;
23083
+ err(message + "\n");
23084
+ return { exitCode: 1, action: "none", message };
23085
+ }
23086
+ try {
23087
+ copyFileSync(bakPath, configPath);
23088
+ unlinkSync(bakPath);
23089
+ log("Config restored from backup.\n");
23090
+ return { exitCode: 0, action: "rolled-back" };
23091
+ } catch (e2) {
23092
+ const message = `Rollback failed: ${e2 instanceof Error ? e2.message : String(e2)}`;
23093
+ err(message + "\n");
23094
+ return { exitCode: 2, action: "none", message };
23095
+ }
23096
+ }
23097
+ if (!existsSync27(configPath)) {
23098
+ const message = "massu.config.yaml not found. Run: npx massu init";
23099
+ err(message + "\n");
23100
+ return { exitCode: 1, action: "none", message };
23101
+ }
23102
+ let existing;
23103
+ try {
23104
+ const content = readFileSync27(configPath, "utf-8");
23105
+ const parsed = parseYaml6(content);
23106
+ if (!parsed || typeof parsed !== "object") {
23107
+ throw new Error("config is not a YAML object");
23108
+ }
23109
+ existing = parsed;
23110
+ } catch (e2) {
23111
+ const message = `Failed to parse massu.config.yaml: ${e2 instanceof Error ? e2.message : String(e2)}`;
23112
+ err(message + "\n");
23113
+ return { exitCode: 2, action: "none", message };
23114
+ }
23115
+ const schemaVersion = existing.schema_version;
23116
+ if (schemaVersion === 2) {
23117
+ log("Config is already at schema_version=2; nothing to do.\n");
23118
+ return { exitCode: 0, action: "already-current" };
23119
+ }
23120
+ const detection = await runDetection(cwd);
23121
+ const v22 = migrateV1ToV2(existing, detection);
23122
+ v22.detection = {
23123
+ ...v22.detection ?? {},
23124
+ fingerprint: computeFingerprint(detection)
23125
+ };
23126
+ try {
23127
+ const original = readFileSync27(configPath, "utf-8");
23128
+ writeFileSync4(bakPath, original, "utf-8");
23129
+ } catch (e2) {
23130
+ const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
23131
+ err(message + "\n");
23132
+ return { exitCode: 2, action: "none", message };
23133
+ }
23134
+ const yamlContent = renderConfigYaml(v22);
23135
+ const writeRes = writeConfigAtomic(configPath, yamlContent);
23136
+ if (!writeRes.validated) {
23137
+ const message = `Failed to write upgraded config: ${writeRes.error}`;
23138
+ err(message + "\n");
23139
+ return { exitCode: 2, action: "none", message };
23140
+ }
23141
+ void opts.ci;
23142
+ log(`Config upgraded to schema_version=2. Backup saved at ${bakPath}
23143
+ `);
23144
+ return { exitCode: 0, action: "migrated" };
23145
+ }
23146
+ var init_config_upgrade = __esm({
23147
+ "src/commands/config-upgrade.ts"() {
23148
+ "use strict";
23149
+ init_detect();
23150
+ init_drift();
23151
+ init_migrate();
23152
+ init_init();
23153
+ }
23154
+ });
23155
+
23156
+ // src/commands/config-check-drift.ts
23157
+ var config_check_drift_exports = {};
23158
+ __export(config_check_drift_exports, {
23159
+ runConfigCheckDrift: () => runConfigCheckDrift
23160
+ });
23161
+ import { existsSync as existsSync28, readFileSync as readFileSync28 } from "fs";
23162
+ import { resolve as resolve23 } from "path";
23163
+ import { parse as parseYaml7 } from "yaml";
23164
+ function renderChanges(changes) {
23165
+ if (changes.length === 0) return "(none)\n";
23166
+ return changes.map((c2) => ` ${c2.field}: ${JSON.stringify(c2.before)} -> ${JSON.stringify(c2.after)}`).join("\n") + "\n";
23167
+ }
23168
+ async function runConfigCheckDrift(opts = {}) {
23169
+ const cwd = opts.cwd ?? process.cwd();
23170
+ const configPath = resolve23(cwd, "massu.config.yaml");
23171
+ const log = opts.silent ? () => {
23172
+ } : (s) => process.stdout.write(s);
23173
+ const err = opts.silent ? () => {
23174
+ } : (s) => process.stderr.write(s);
23175
+ if (!existsSync28(configPath)) {
23176
+ const message = "massu.config.yaml not found. Run: npx massu init";
23177
+ err(message + "\n");
23178
+ return {
23179
+ exitCode: 2,
23180
+ drifted: false,
23181
+ changes: [],
23182
+ storedFingerprint: null,
23183
+ currentFingerprint: null,
23184
+ message
23185
+ };
23186
+ }
23187
+ let config;
23188
+ try {
23189
+ const content = readFileSync28(configPath, "utf-8");
23190
+ const parsed = parseYaml7(content);
23191
+ if (!parsed || typeof parsed !== "object") {
23192
+ throw new Error("config is not a YAML object");
23193
+ }
23194
+ config = parsed;
23195
+ } catch (e2) {
23196
+ const message = `Failed to parse massu.config.yaml: ${e2 instanceof Error ? e2.message : String(e2)}`;
23197
+ err(message + "\n");
23198
+ return {
23199
+ exitCode: 2,
23200
+ drifted: false,
23201
+ changes: [],
23202
+ storedFingerprint: null,
23203
+ currentFingerprint: null,
23204
+ message
23205
+ };
23206
+ }
23207
+ const detection = await runDetection(cwd);
23208
+ const currentFp = computeFingerprint(detection);
23209
+ const storedFp = typeof config.detection?.fingerprint === "string" ? config.detection.fingerprint : null;
23210
+ const report = detectDrift(config, detection);
23211
+ const fingerprintDrift = storedFp !== null && storedFp !== currentFp;
23212
+ const drifted = report.drifted || fingerprintDrift;
23213
+ if (!drifted) {
23214
+ log("No drift detected.\n");
23215
+ if (opts.verbose) {
23216
+ log(`Fingerprint: ${currentFp}
23217
+ `);
23218
+ }
23219
+ return {
23220
+ exitCode: 0,
23221
+ drifted: false,
23222
+ changes: report.changes,
23223
+ storedFingerprint: storedFp,
23224
+ currentFingerprint: currentFp
23225
+ };
23226
+ }
23227
+ err("Config drift detected; run `npx massu config refresh` to update.\n");
23228
+ if (opts.verbose) {
23229
+ if (storedFp !== null) {
23230
+ log(`Fingerprint: ${storedFp.slice(0, 16)} -> ${currentFp.slice(0, 16)}
23231
+ `);
23232
+ } else {
23233
+ log(`Fingerprint (new): ${currentFp.slice(0, 16)}
23234
+ `);
23235
+ }
23236
+ log("Changes:\n");
23237
+ log(renderChanges(report.changes));
23238
+ }
23239
+ return {
23240
+ exitCode: 1,
23241
+ drifted: true,
23242
+ changes: report.changes,
23243
+ storedFingerprint: storedFp,
23244
+ currentFingerprint: currentFp
23245
+ };
23246
+ }
23247
+ var init_config_check_drift = __esm({
23248
+ "src/commands/config-check-drift.ts"() {
23249
+ "use strict";
23250
+ init_detect();
23251
+ init_drift();
23252
+ }
23253
+ });
23254
+
22445
23255
  // src/cli.ts
22446
- import { readFileSync as readFileSync26 } from "fs";
22447
- import { resolve as resolve21, dirname as dirname12 } from "path";
23256
+ import { readFileSync as readFileSync29 } from "fs";
23257
+ import { resolve as resolve24, dirname as dirname12 } from "path";
22448
23258
  import { fileURLToPath as fileURLToPath5 } from "url";
22449
23259
  var __filename4 = fileURLToPath5(import.meta.url);
22450
23260
  var __dirname5 = dirname12(__filename4);
@@ -22477,6 +23287,10 @@ async function main() {
22477
23287
  await runValidateConfig2();
22478
23288
  break;
22479
23289
  }
23290
+ case "config": {
23291
+ await handleConfigSubcommand(args.slice(1));
23292
+ break;
23293
+ }
22480
23294
  case "--help":
22481
23295
  case "-h": {
22482
23296
  printHelp();
@@ -22492,6 +23306,56 @@ async function main() {
22492
23306
  }
22493
23307
  }
22494
23308
  }
23309
+ async function handleConfigSubcommand(configArgs) {
23310
+ const sub = configArgs[0];
23311
+ const flags = new Set(configArgs.slice(1));
23312
+ switch (sub) {
23313
+ case "refresh": {
23314
+ const { runConfigRefresh: runConfigRefresh2 } = await Promise.resolve().then(() => (init_config_refresh(), config_refresh_exports));
23315
+ const result = await runConfigRefresh2({ dryRun: flags.has("--dry-run") });
23316
+ process.exit(result.exitCode);
23317
+ return;
23318
+ }
23319
+ case "validate": {
23320
+ const { runValidateConfig: runValidateConfig2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
23321
+ await runValidateConfig2();
23322
+ return;
23323
+ }
23324
+ case "upgrade": {
23325
+ const { runConfigUpgrade: runConfigUpgrade2 } = await Promise.resolve().then(() => (init_config_upgrade(), config_upgrade_exports));
23326
+ const result = await runConfigUpgrade2({
23327
+ rollback: flags.has("--rollback"),
23328
+ ci: flags.has("--ci") || flags.has("--yes")
23329
+ });
23330
+ process.exit(result.exitCode);
23331
+ return;
23332
+ }
23333
+ case "doctor": {
23334
+ const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
23335
+ await runDoctor2();
23336
+ return;
23337
+ }
23338
+ case "check-drift": {
23339
+ const { runConfigCheckDrift: runConfigCheckDrift2 } = await Promise.resolve().then(() => (init_config_check_drift(), config_check_drift_exports));
23340
+ const result = await runConfigCheckDrift2({ verbose: flags.has("--verbose") });
23341
+ process.exit(result.exitCode);
23342
+ return;
23343
+ }
23344
+ case "--help":
23345
+ case "-h":
23346
+ case void 0: {
23347
+ printConfigHelp();
23348
+ return;
23349
+ }
23350
+ default: {
23351
+ process.stderr.write(`massu: unknown config subcommand: ${sub}
23352
+ `);
23353
+ printConfigHelp();
23354
+ process.exit(1);
23355
+ return;
23356
+ }
23357
+ }
23358
+ }
22495
23359
  function printHelp() {
22496
23360
  console.log(`
22497
23361
  Massu AI - Engineering Governance Platform
@@ -22504,7 +23368,8 @@ Commands:
22504
23368
  doctor Check installation health
22505
23369
  install-hooks Install/update Claude Code hooks
22506
23370
  install-commands Install/update slash commands
22507
- validate-config Validate massu.config.yaml
23371
+ validate-config Validate massu.config.yaml (alias: config validate)
23372
+ config <sub> Config lifecycle: refresh | validate | upgrade | doctor | check-drift
22508
23373
 
22509
23374
  Options:
22510
23375
  --help, -h Show this help message
@@ -22513,13 +23378,35 @@ Options:
22513
23378
  Getting started:
22514
23379
  npx massu init # Full setup in one command
22515
23380
  npx massu init --help # Show all init options (--ci, --force, --template)
23381
+ npx massu config --help # Show config subcommands
22516
23382
 
22517
23383
  Documentation: https://massu.ai/docs
22518
23384
  `);
22519
23385
  }
23386
+ function printConfigHelp() {
23387
+ console.log(`
23388
+ massu config <subcommand>
23389
+
23390
+ Subcommands:
23391
+ refresh Re-run detection and apply changes to massu.config.yaml.
23392
+ --dry-run Print diff and exit without writing.
23393
+ validate Validate massu.config.yaml (alias of \`massu validate-config\`).
23394
+ upgrade Migrate a v1 config to schema_version=2.
23395
+ --rollback Restore from .bak file.
23396
+ --ci, --yes Non-interactive mode (no prompts).
23397
+ doctor Run the full health check (alias of \`massu doctor\`).
23398
+ check-drift CI-safe drift gate; exits 1 on drift.
23399
+ --verbose Print detailed changes to stdout.
23400
+
23401
+ Examples:
23402
+ npx massu config refresh --dry-run
23403
+ npx massu config upgrade --ci
23404
+ npx massu config check-drift --verbose
23405
+ `);
23406
+ }
22520
23407
  function printVersion() {
22521
23408
  try {
22522
- const pkg = JSON.parse(readFileSync26(resolve21(__dirname5, "../package.json"), "utf-8"));
23409
+ const pkg = JSON.parse(readFileSync29(resolve24(__dirname5, "../package.json"), "utf-8"));
22523
23410
  console.log(`massu v${pkg.version}`);
22524
23411
  } catch {
22525
23412
  console.log("massu v0.1.0");