@massa-ai/opencode-plugin 1.55.0 → 1.56.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.
@@ -601,15 +601,18 @@ var init_massa_ai_config = __esm(() => {
601
601
  security: {
602
602
  corsOrigins: []
603
603
  },
604
- scheduler: DEFAULT_SCHEDULER_CONFIG
604
+ scheduler: DEFAULT_SCHEDULER_CONFIG,
605
+ bootstrap: { rules: {} }
605
606
  };
606
607
  });
607
608
 
608
609
  // ../../packages/shared/dist/config/config-loader.js
609
610
  var exports_config_loader = {};
610
611
  __export(exports_config_loader, {
612
+ writeRawConfig: () => writeRawConfig,
611
613
  writeFileAtomically: () => writeFileAtomically,
612
614
  saveConfig: () => saveConfig,
615
+ readRawConfigStrict: () => readRawConfigStrict,
613
616
  migrateDataDirOnce: () => migrateDataDirOnce,
614
617
  mergeSchedulerSection: () => mergeSchedulerSection,
615
618
  loadRawUserConfig: () => loadRawUserConfig,
@@ -620,12 +623,23 @@ __export(exports_config_loader, {
620
623
  getConfigForEnv: () => getConfigForEnv,
621
624
  getConfigDir: () => getConfigDir,
622
625
  configExists: () => configExists,
623
- __resetMigrationForTests: () => __resetMigrationForTests
626
+ __resetMigrationForTests: () => __resetMigrationForTests,
627
+ ConfigWriteConflictError: () => ConfigWriteConflictError,
628
+ ConfigParseError: () => ConfigParseError
624
629
  });
625
630
  import fs from "fs";
626
631
  import path3 from "path";
627
632
  import os2 from "os";
628
633
  import crypto from "crypto";
634
+ function readConfigFileOrEmpty() {
635
+ try {
636
+ return fs.readFileSync(CONFIG_FILE, "utf-8");
637
+ } catch (error) {
638
+ if (error?.code === "ENOENT")
639
+ return "";
640
+ throw error;
641
+ }
642
+ }
629
643
  function getConfigDir() {
630
644
  return CONFIG_DIR;
631
645
  }
@@ -692,6 +706,21 @@ function loadRawUserConfig() {
692
706
  return {};
693
707
  }
694
708
  }
709
+ function readRawConfigStrict() {
710
+ const raw = readConfigFileOrEmpty();
711
+ if (raw === "")
712
+ return {};
713
+ let parsed;
714
+ try {
715
+ parsed = JSON.parse(raw);
716
+ } catch (error) {
717
+ throw new ConfigParseError(CONFIG_FILE, error);
718
+ }
719
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
720
+ throw new ConfigParseError(CONFIG_FILE, new Error("parsed value is not a JSON object"));
721
+ }
722
+ return parsed;
723
+ }
695
724
  function loadConfigSafe() {
696
725
  try {
697
726
  return loadConfig();
@@ -746,6 +775,43 @@ function writeFileAtomically(targetPath, content) {
746
775
  function saveConfig(config) {
747
776
  writeFileAtomically(CONFIG_FILE, JSON.stringify(config, null, 2));
748
777
  }
778
+ function writeRawConfig(doc, opts) {
779
+ const onDiskAtStart = readConfigFileOrEmpty();
780
+ if (onDiskAtStart === opts.expectedBytes) {
781
+ writeFileAtomically(CONFIG_FILE, JSON.stringify(doc, null, 2));
782
+ return;
783
+ }
784
+ let original;
785
+ let current;
786
+ try {
787
+ original = opts.expectedBytes === "" ? {} : JSON.parse(opts.expectedBytes);
788
+ } catch (error) {
789
+ throw new ConfigParseError(`${CONFIG_FILE} (caller-supplied expectedBytes)`, error);
790
+ }
791
+ try {
792
+ current = onDiskAtStart === "" ? {} : JSON.parse(onDiskAtStart);
793
+ } catch (error) {
794
+ throw new ConfigParseError(CONFIG_FILE, error);
795
+ }
796
+ const reapplied = { ...current };
797
+ const touchedKeys = new Set([...Object.keys(original), ...Object.keys(doc)]);
798
+ for (const key of touchedKeys) {
799
+ const before = JSON.stringify(original[key]);
800
+ const after = JSON.stringify(doc[key]);
801
+ if (before === after)
802
+ continue;
803
+ if (Object.prototype.hasOwnProperty.call(doc, key)) {
804
+ reapplied[key] = doc[key];
805
+ } else {
806
+ delete reapplied[key];
807
+ }
808
+ }
809
+ const onDiskImmediatelyBeforeWrite = readConfigFileOrEmpty();
810
+ if (onDiskImmediatelyBeforeWrite !== onDiskAtStart) {
811
+ throw new ConfigWriteConflictError(CONFIG_FILE);
812
+ }
813
+ writeFileAtomically(CONFIG_FILE, JSON.stringify(reapplied, null, 2));
814
+ }
749
815
  function initConfig() {
750
816
  if (!fs.existsSync(CONFIG_FILE)) {
751
817
  saveConfig(defaultMassaAiConfig);
@@ -772,12 +838,25 @@ function getConfigForEnv() {
772
838
  env.ENABLE_METRICS = String(config.logging.enableMetrics);
773
839
  return env;
774
840
  }
775
- var CONFIG_DIR, CONFIG_FILE, migrationAttempted = false, tempFileCounter = 0;
841
+ var CONFIG_DIR, CONFIG_FILE, ConfigParseError, ConfigWriteConflictError, migrationAttempted = false, tempFileCounter = 0;
776
842
  var init_config_loader = __esm(() => {
777
843
  init_massa_ai_config();
778
844
  init_xdg();
779
845
  CONFIG_DIR = configDir("massa-ai");
780
846
  CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
847
+ ConfigParseError = class ConfigParseError extends Error {
848
+ constructor(filePath, cause) {
849
+ const reason = cause instanceof Error ? cause.message : String(cause);
850
+ super(`Failed to parse ${filePath}: ${reason}`);
851
+ this.name = "ConfigParseError";
852
+ }
853
+ };
854
+ ConfigWriteConflictError = class ConfigWriteConflictError extends Error {
855
+ constructor(filePath) {
856
+ super(`${filePath} changed on disk twice while writing \u2014 refusing to overwrite a ` + `concurrent update. Re-read the file and retry.`);
857
+ this.name = "ConfigWriteConflictError";
858
+ }
859
+ };
781
860
  });
782
861
 
783
862
  // ../../packages/shared/dist/env.js
@@ -2622,12 +2701,585 @@ function findRepoRootWithMarker(startDir, marker, maxLevels) {
2622
2701
  }
2623
2702
  return null;
2624
2703
  }
2625
- // src/config-cli.ts
2626
- import { promises as fs9 } from "fs";
2704
+ // ../../packages/shared/dist/bootstrap/rules.js
2705
+ var BOOTSTRAP_RULE_IDS = [
2706
+ "caveman",
2707
+ "massa-ai-router",
2708
+ "persona-router",
2709
+ "dedupe-guardrails",
2710
+ "plan-challenge",
2711
+ "conversation-feedback",
2712
+ "indexing-hygiene",
2713
+ "english-code",
2714
+ "code-comments"
2715
+ ];
2716
+ function isBootstrapRuleId(value) {
2717
+ return typeof value === "string" && BOOTSTRAP_RULE_IDS.includes(value);
2718
+ }
2719
+ var BOOTSTRAP_RULES = [
2720
+ {
2721
+ id: "caveman",
2722
+ defaultEnabled: true,
2723
+ description: "Keep communication compressed while preserving technical accuracy."
2724
+ },
2725
+ {
2726
+ id: "massa-ai-router",
2727
+ defaultEnabled: true,
2728
+ description: "Load the massa-ai skill as the workflow router before substantive work."
2729
+ },
2730
+ {
2731
+ id: "persona-router",
2732
+ defaultEnabled: true,
2733
+ description: "Select one cataloged specialist persona after massa-ai context is available."
2734
+ },
2735
+ {
2736
+ id: "dedupe-guardrails",
2737
+ defaultEnabled: true,
2738
+ description: "Reuse already-loaded massa-ai context instead of bulk-loading workflows or references."
2739
+ },
2740
+ {
2741
+ id: "plan-challenge",
2742
+ defaultEnabled: true,
2743
+ description: "Run The Fool as a post-plan challenge gate per the configured policy."
2744
+ },
2745
+ {
2746
+ id: "conversation-feedback",
2747
+ defaultEnabled: true,
2748
+ description: "Emit chat-visible status updates for massa-ai workflow progress."
2749
+ },
2750
+ {
2751
+ id: "indexing-hygiene",
2752
+ defaultEnabled: true,
2753
+ description: "Ignore build output, dependency, and secret paths during indexing and context loading."
2754
+ },
2755
+ {
2756
+ id: "english-code",
2757
+ defaultEnabled: true,
2758
+ description: "Write generated code, identifiers, comments, and commit-facing artifacts in English regardless of conversational language."
2759
+ },
2760
+ {
2761
+ id: "code-comments",
2762
+ defaultEnabled: false,
2763
+ description: "Require API doc blocks and rationale comments on generated code, per code-annotation.md \xA71/\xA72."
2764
+ }
2765
+ ];
2766
+ var RULES_BY_ID = new Map(BOOTSTRAP_RULES.map((rule) => [rule.id, rule]));
2767
+ function bootstrapRuleDefaults() {
2768
+ const defaults = {};
2769
+ for (const rule of BOOTSTRAP_RULES)
2770
+ defaults[rule.id] = rule.defaultEnabled;
2771
+ return defaults;
2772
+ }
2773
+
2774
+ class BootstrapRuleError extends Error {
2775
+ constructor(message) {
2776
+ super(message);
2777
+ this.name = "BootstrapRuleError";
2778
+ }
2779
+ }
2780
+ function namedError4(name, message) {
2781
+ const err = new BootstrapRuleError(message);
2782
+ err.name = name;
2783
+ return err;
2784
+ }
2785
+ var UnknownRuleError = (id, known = BOOTSTRAP_RULE_IDS) => namedError4("UnknownRuleError", `unknown bootstrap rule "${id}" \u2014 valid ids: ${known.join(", ")}`);
2786
+ function assertKnownRuleId(id) {
2787
+ if (!isBootstrapRuleId(id))
2788
+ throw UnknownRuleError(id, BOOTSTRAP_RULE_IDS);
2789
+ }
2790
+ // ../../packages/shared/dist/bootstrap/state.js
2791
+ init_config_loader();
2792
+ import fs9 from "fs";
2793
+ var BOOTSTRAP_STATE_KEY = "bootstrap";
2794
+ var BOOTSTRAP_RULES_KEY = "rules";
2795
+ var BOOTSTRAP_STATE_PATH = `${BOOTSTRAP_STATE_KEY}.${BOOTSTRAP_RULES_KEY}`;
2796
+ function isPlainObject2(value) {
2797
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2798
+ }
2799
+ function resolveBootstrapState(doc) {
2800
+ const document = doc ?? readRawConfigStrict();
2801
+ const state = bootstrapRuleDefaults();
2802
+ const ignored = [];
2803
+ const bootstrap = document[BOOTSTRAP_STATE_KEY];
2804
+ if (bootstrap === undefined)
2805
+ return { state, ignoredStateKeys: [] };
2806
+ if (!isPlainObject2(bootstrap)) {
2807
+ return { state, ignoredStateKeys: [BOOTSTRAP_STATE_KEY] };
2808
+ }
2809
+ const rules = bootstrap[BOOTSTRAP_RULES_KEY];
2810
+ if (rules === undefined)
2811
+ return { state, ignoredStateKeys: [] };
2812
+ if (!isPlainObject2(rules)) {
2813
+ return { state, ignoredStateKeys: [BOOTSTRAP_STATE_PATH] };
2814
+ }
2815
+ for (const [key, value] of Object.entries(rules)) {
2816
+ if (!isBootstrapRuleId(key) || typeof value !== "boolean") {
2817
+ ignored.push(key);
2818
+ continue;
2819
+ }
2820
+ state[key] = value;
2821
+ }
2822
+ return { state, ignoredStateKeys: ignored.sort() };
2823
+ }
2824
+ function readConfigBytes() {
2825
+ try {
2826
+ return fs9.readFileSync(getConfigPath(), "utf-8");
2827
+ } catch (error) {
2828
+ if (error?.code === "ENOENT")
2829
+ return "";
2830
+ throw error;
2831
+ }
2832
+ }
2833
+ function setBootstrapRuleEnabled(id, enabled) {
2834
+ assertKnownRuleId(id);
2835
+ const expectedBytes = readConfigBytes();
2836
+ let document;
2837
+ if (expectedBytes === "") {
2838
+ document = {};
2839
+ } else {
2840
+ let parsed;
2841
+ try {
2842
+ parsed = JSON.parse(expectedBytes);
2843
+ } catch (error) {
2844
+ throw new ConfigParseError(getConfigPath(), error);
2845
+ }
2846
+ if (!isPlainObject2(parsed)) {
2847
+ throw new ConfigParseError(getConfigPath(), new Error("parsed value is not a JSON object"));
2848
+ }
2849
+ document = parsed;
2850
+ }
2851
+ const before = resolveBootstrapState(document);
2852
+ const bootstrap = document[BOOTSTRAP_STATE_KEY];
2853
+ const bootstrapSubtree = isPlainObject2(bootstrap) ? bootstrap : {};
2854
+ const rules = bootstrapSubtree[BOOTSTRAP_RULES_KEY];
2855
+ const rulesSubtree = isPlainObject2(rules) ? rules : {};
2856
+ const next = {
2857
+ ...document,
2858
+ [BOOTSTRAP_STATE_KEY]: {
2859
+ ...bootstrapSubtree,
2860
+ [BOOTSTRAP_RULES_KEY]: { ...rulesSubtree, [id]: enabled }
2861
+ }
2862
+ };
2863
+ writeRawConfig(next, { expectedBytes });
2864
+ const after = resolveBootstrapState(next);
2865
+ return {
2866
+ id,
2867
+ enabled,
2868
+ changed: before.state[id] !== enabled,
2869
+ state: after.state,
2870
+ ignoredStateKeys: after.ignoredStateKeys
2871
+ };
2872
+ }
2873
+ // ../../packages/shared/dist/bootstrap/render.js
2627
2874
  import path13 from "path";
2875
+ var BOOTSTRAP_BLOCK_START = "<!-- massa-ai:bootstrap:start -->";
2876
+ var BOOTSTRAP_BLOCK_END = "<!-- massa-ai:bootstrap:end -->";
2877
+ var CONTRACT_FILENAME = "MASSA-AI.md";
2878
+ function wrapBootstrapBlock(body) {
2879
+ return `${BOOTSTRAP_BLOCK_START}
2880
+ ${body.replace(/\n+$/, "")}
2881
+ ${BOOTSTRAP_BLOCK_END}
2882
+ `;
2883
+ }
2884
+ function ruleMarker(id, suffix) {
2885
+ return `<!-- massa-ai:rule:${id}:${suffix} -->`;
2886
+ }
2887
+ var ANY_MASSA_AI_MARKER = /<!--\s*massa-ai:(?:rule|bootstrap):[^>]*-->/;
2888
+
2889
+ class BootstrapRenderError extends Error {
2890
+ details;
2891
+ constructor(name, message, details = []) {
2892
+ super(message);
2893
+ this.name = name;
2894
+ this.details = details;
2895
+ }
2896
+ }
2897
+ var HOST_CONFIG_DIR = {
2898
+ claude: [".claude"],
2899
+ codex: [".codex"],
2900
+ cursor: [".cursor"],
2901
+ opencode: [".config", "opencode"]
2902
+ };
2903
+ function resolveHostRoot(host, targetHome, hostRoot) {
2904
+ requireAbsoluteTargetHome(targetHome);
2905
+ if (hostRoot === undefined)
2906
+ return path13.join(targetHome, ...HOST_CONFIG_DIR[host]);
2907
+ const relative = path13.relative(targetHome, hostRoot);
2908
+ if (!path13.isAbsolute(hostRoot) || relative === "" || relative.startsWith("..") || path13.isAbsolute(relative)) {
2909
+ throw new BootstrapRenderError("HostRootOutsideTargetHomeError", `hostRoot must be an absolute directory inside targetHome, got "${hostRoot}" for targetHome "${targetHome}"`, [hostRoot, targetHome]);
2910
+ }
2911
+ return hostRoot;
2912
+ }
2913
+ function bootstrapContractPath(host, targetHome, hostRoot) {
2914
+ return path13.join(resolveHostRoot(host, targetHome, hostRoot), CONTRACT_FILENAME);
2915
+ }
2916
+ function bootstrapStateFilePath(targetHome) {
2917
+ requireAbsoluteTargetHome(targetHome);
2918
+ return path13.join(targetHome, ".config", "massa-ai", "config.json");
2919
+ }
2920
+ function renderBootstrap(options) {
2921
+ const { source, state, host, targetHome, hostRoot } = options;
2922
+ requireAbsoluteTargetHome(targetHome);
2923
+ requireTotalState(state);
2924
+ const body = applyRuleState(extractBootstrapBlock(source), state);
2925
+ const contract = `${renderHeader(state, targetHome)}
2926
+
2927
+ ${body}`;
2928
+ const pointer = renderPointer(host, targetHome, hostRoot);
2929
+ const emitted = [
2930
+ ...contract.split(`
2931
+ `).filter((line) => ANY_MASSA_AI_MARKER.test(line)),
2932
+ ...pointer.split(`
2933
+ `).filter((line) => ANY_MASSA_AI_MARKER.test(line))
2934
+ ].map((line) => line.trim());
2935
+ if (emitted.length > 0) {
2936
+ throw new BootstrapRenderError("MarkerInInterpolatedPathError", `rendered output carries a massa-ai marker, which can only have come from an interpolated path: ${emitted.join(", ")}`, emitted);
2937
+ }
2938
+ return { contract, pointer };
2939
+ }
2940
+ function requireAbsoluteTargetHome(targetHome) {
2941
+ if (!path13.isAbsolute(targetHome)) {
2942
+ throw new BootstrapRenderError("TargetHomeNotAbsoluteError", `targetHome must be an absolute path, got "${targetHome}"`, [targetHome]);
2943
+ }
2944
+ }
2945
+ function requireTotalState(state) {
2946
+ const missing = BOOTSTRAP_RULES.filter((rule) => typeof state[rule.id] !== "boolean").map((rule) => rule.id);
2947
+ if (missing.length > 0) {
2948
+ throw new BootstrapRenderError("IncompleteBootstrapStateError", `bootstrap state is missing a boolean for: ${missing.join(", ")} \u2014 pass a state resolved by resolveBootstrapState`, missing);
2949
+ }
2950
+ }
2951
+ function extractBootstrapBlock(source) {
2952
+ const startCount = countOccurrences(source, BOOTSTRAP_BLOCK_START);
2953
+ const endCount = countOccurrences(source, BOOTSTRAP_BLOCK_END);
2954
+ const startIndex = source.indexOf(BOOTSTRAP_BLOCK_START);
2955
+ const endIndex = source.indexOf(BOOTSTRAP_BLOCK_END);
2956
+ if (startCount !== 1 || endCount !== 1 || startIndex > endIndex) {
2957
+ throw new BootstrapRenderError("BootstrapSourceError", `source must contain exactly one well-formed bootstrap block \u2014 found ${startCount} start and ${endCount} end marker(s)`, [`start=${startCount}`, `end=${endCount}`]);
2958
+ }
2959
+ return source.slice(startIndex + BOOTSTRAP_BLOCK_START.length, endIndex);
2960
+ }
2961
+ function findMarker(lines, marker) {
2962
+ let index = -1;
2963
+ let count = 0;
2964
+ for (let i = 0;i < lines.length; i++) {
2965
+ if (lines[i]?.trim() === marker) {
2966
+ if (count === 0)
2967
+ index = i;
2968
+ count++;
2969
+ }
2970
+ }
2971
+ return { index, count };
2972
+ }
2973
+ function countOccurrences(haystack, needle) {
2974
+ let count = 0;
2975
+ let from = 0;
2976
+ for (;; ) {
2977
+ const at = haystack.indexOf(needle, from);
2978
+ if (at === -1)
2979
+ return count;
2980
+ count++;
2981
+ from = at + needle.length;
2982
+ }
2983
+ }
2984
+ function splitOffSpan(inner, id) {
2985
+ const offStart = findMarker(inner, ruleMarker(id, "off"));
2986
+ const offEnd = findMarker(inner, ruleMarker(id, "off-end"));
2987
+ if (offStart.count === 0 && offEnd.count === 0)
2988
+ return { on: inner, off: [] };
2989
+ if (offStart.count !== 1 || offEnd.count !== 1 || offStart.index > offEnd.index) {
2990
+ throw new BootstrapRenderError("MalformedOffSpanError", `rule "${id}" has a malformed off-text span \u2014 found ${offStart.count} ":off" and ${offEnd.count} ":off-end" marker(s)`, [id]);
2991
+ }
2992
+ return {
2993
+ on: [...inner.slice(0, offStart.index), ...inner.slice(offEnd.index + 1)],
2994
+ off: inner.slice(offStart.index + 1, offEnd.index)
2995
+ };
2996
+ }
2997
+ function applyRuleState(block, state) {
2998
+ const lines = block.split(`
2999
+ `);
3000
+ const missingSpans = [];
3001
+ for (const rule of BOOTSTRAP_RULES) {
3002
+ const start = findMarker(lines, ruleMarker(rule.id, "start"));
3003
+ const end = findMarker(lines, ruleMarker(rule.id, "end"));
3004
+ if (start.count !== 1 || end.count !== 1 || start.index > end.index) {
3005
+ missingSpans.push(rule.id);
3006
+ continue;
3007
+ }
3008
+ const span = splitOffSpan(lines.slice(start.index + 1, end.index), rule.id);
3009
+ const replacement = state[rule.id] ? span.on : span.off;
3010
+ lines.splice(start.index, end.index - start.index + 1, ...replacement);
3011
+ }
3012
+ if (missingSpans.length > 0) {
3013
+ throw new BootstrapRenderError("MissingRuleSpanError", `source has no well-formed span for rule(s): ${missingSpans.join(", ")}`, missingSpans);
3014
+ }
3015
+ const leftovers = lines.filter((line) => ANY_MASSA_AI_MARKER.test(line)).map((line) => line.trim());
3016
+ if (leftovers.length > 0) {
3017
+ throw new BootstrapRenderError("UnknownRuleMarkerError", `source carries marker(s) no registry rule consumed: ${leftovers.join(", ")}`, leftovers);
3018
+ }
3019
+ return normalizeBlankLines(lines);
3020
+ }
3021
+ function normalizeBlankLines(lines) {
3022
+ const out = [];
3023
+ let inFence = false;
3024
+ for (const line of lines) {
3025
+ if (line.trimStart().startsWith("```")) {
3026
+ inFence = !inFence;
3027
+ out.push(line);
3028
+ continue;
3029
+ }
3030
+ if (inFence) {
3031
+ out.push(line);
3032
+ continue;
3033
+ }
3034
+ const previous = out[out.length - 1];
3035
+ const isBlank = line.trim() === "";
3036
+ if (isBlank && (previous === undefined || previous.trim() === ""))
3037
+ continue;
3038
+ if (/^#{1,6} /.test(line) && previous !== undefined && previous.trim() !== "")
3039
+ out.push("");
3040
+ out.push(line);
3041
+ }
3042
+ return `${out.join(`
3043
+ `).trimEnd()}
3044
+ `;
3045
+ }
3046
+ function renderHeader(state, targetHome) {
3047
+ const lines = [
3048
+ "> Generated by massa-ai from `skills/AGENTS.md`. Edits made here are lost on",
3049
+ "> the next `scripts/install-skills.sh --apply` or",
3050
+ "> `massa-ai-config bootstrap enable|disable <rule-id>` run.",
3051
+ ">",
3052
+ `> Rule state lives in \`${bootstrapStateFilePath(targetHome)}\` under the`,
3053
+ `> \`${BOOTSTRAP_STATE_PATH}\` key. List every rule and its state with`,
3054
+ "> `massa-ai-config bootstrap list`; switch one back on with",
3055
+ "> `massa-ai-config bootstrap enable <rule-id>`. That command is a binary and",
3056
+ "> not a rule, so it keeps working with every rule below disabled \u2014",
3057
+ "> `massa-ai-config bootstrap enable massa-ai-router` is the way back when the",
3058
+ "> router rule itself is off."
3059
+ ];
3060
+ if (BOOTSTRAP_RULES.every((rule) => !state[rule.id])) {
3061
+ lines.push("", "**Every massa-ai bootstrap rule is disabled.** The contract below carries no", "rule to activate, and nothing in this file changes agent behavior until at", "least one rule is enabled again.");
3062
+ }
3063
+ return lines.join(`
3064
+ `);
3065
+ }
3066
+ function renderPointer(host, targetHome, hostRoot) {
3067
+ return [
3068
+ "## massa-ai Startup Contract",
3069
+ "",
3070
+ "Before substantive work in this session, read",
3071
+ `\`${bootstrapContractPath(host, targetHome, hostRoot)}\``,
3072
+ "with your Read tool and follow it. This block is a pointer only: it states no",
3073
+ "rule of its own, and massa-ai overwrites it on the next install.",
3074
+ ""
3075
+ ].join(`
3076
+ `);
3077
+ }
3078
+ // ../../packages/shared/dist/bootstrap/report.js
3079
+ var CLEAN_STATUSES = new Set(["written", "skipped"]);
3080
+ function bootstrapReportSucceeded(report) {
3081
+ return report.rows.every((row) => CLEAN_STATUSES.has(row.status));
3082
+ }
3083
+ function buildBootstrapReport(input) {
3084
+ const restartRequired = !input.dryRun && input.rows.some((row) => row.status === "written");
3085
+ return {
3086
+ rows: input.rows,
3087
+ restartRequired,
3088
+ dryRun: input.dryRun,
3089
+ ignoredStateKeys: input.ignoredStateKeys
3090
+ };
3091
+ }
3092
+ // ../../packages/shared/dist/bootstrap/engine.js
3093
+ init_config_loader();
3094
+ import fs10 from "fs";
3095
+ import path14 from "path";
3096
+ var INSTALL_STATE_FILENAME = "install-state.json";
3097
+ var WIRING_REMEDY = "scripts/install-skills.sh --apply";
3098
+
3099
+ class BootstrapEngineError extends Error {
3100
+ details;
3101
+ constructor(name, message, details = []) {
3102
+ super(message);
3103
+ this.name = name;
3104
+ this.details = details;
3105
+ }
3106
+ }
3107
+ function applyBootstrapState(options) {
3108
+ const { targetHome } = options;
3109
+ const dryRun = options.dryRun ?? false;
3110
+ const warn = options.onWarning ?? ((message) => console.warn(message));
3111
+ const configPath = bootstrapStateFilePath(targetHome);
3112
+ const installStatePath = path14.join(path14.dirname(configPath), INSTALL_STATE_FILENAME);
3113
+ const { platforms } = readInstallState(installStatePath);
3114
+ const installed = HOSTS.filter((host) => platforms[host] !== undefined);
3115
+ if (installed.length === 0) {
3116
+ return buildBootstrapReport({ rows: [], dryRun, ignoredStateKeys: [] });
3117
+ }
3118
+ const resolved = resolveRuleState(configPath, warn);
3119
+ const source = readSource(options);
3120
+ const rows = installed.map((host) => applyHost({
3121
+ host,
3122
+ source,
3123
+ state: resolved.state,
3124
+ targetHome,
3125
+ hostRoot: recordedHostRoot(platforms[host]),
3126
+ dryRun
3127
+ }));
3128
+ return buildBootstrapReport({
3129
+ rows,
3130
+ dryRun,
3131
+ ignoredStateKeys: resolved.ignoredStateKeys
3132
+ });
3133
+ }
3134
+ function recordedHostRoot(record) {
3135
+ const root = record?.root;
3136
+ return typeof root === "string" && root.length > 0 ? root : undefined;
3137
+ }
3138
+ function resolveRuleState(configPath, warn) {
3139
+ const raw = readFileOrNull(configPath);
3140
+ if (raw === null)
3141
+ return resolveBootstrapState({});
3142
+ let parsed;
3143
+ try {
3144
+ parsed = JSON.parse(raw);
3145
+ } catch (error) {
3146
+ warn(degradeWarning(configPath, error));
3147
+ return resolveBootstrapState({});
3148
+ }
3149
+ if (!isPlainObject3(parsed)) {
3150
+ warn(degradeWarning(configPath, new Error("parsed value is not a JSON object")));
3151
+ return resolveBootstrapState({});
3152
+ }
3153
+ return resolveBootstrapState(parsed);
3154
+ }
3155
+ function degradeWarning(configPath, cause) {
3156
+ const error = new ConfigParseError(configPath, cause);
3157
+ return `${error.name}: ${error.message} \u2014 rendering the registry bootstrap defaults; ${configPath} was not written`;
3158
+ }
3159
+ function readSource(options) {
3160
+ if (options.source !== undefined)
3161
+ return options.source;
3162
+ if (options.sourcePath !== undefined) {
3163
+ const text = readFileOrNull(options.sourcePath);
3164
+ if (text === null) {
3165
+ throw new BootstrapEngineError("BootstrapSourceUnreadableError", `could not read the bootstrap source at ${options.sourcePath}`, [options.sourcePath]);
3166
+ }
3167
+ return text;
3168
+ }
3169
+ throw new BootstrapEngineError("BootstrapSourceUnavailableError", "no bootstrap source given \u2014 pass `source` (the skills/AGENTS.md text) or `sourcePath`", ["source", "sourcePath"]);
3170
+ }
3171
+ function applyHost(input) {
3172
+ const { host, source, state, targetHome, hostRoot, dryRun } = input;
3173
+ let contractPath;
3174
+ let document;
3175
+ try {
3176
+ contractPath = bootstrapContractPath(host, targetHome, hostRoot);
3177
+ document = wrapBootstrapBlock(renderBootstrap({ source, state, host, targetHome, hostRoot }).contract);
3178
+ } catch (error) {
3179
+ return { host, status: "failed", reason: error.message };
3180
+ }
3181
+ const wired = isWired(host, targetHome, hostRoot);
3182
+ const notWired = () => ({
3183
+ host,
3184
+ status: "written-not-wired",
3185
+ reason: notWiredReason(host, targetHome, hostRoot)
3186
+ });
3187
+ if (readFileOrNull(contractPath) === document) {
3188
+ return wired ? { host, status: "skipped", reason: `${contractPath} is already up to date` } : notWired();
3189
+ }
3190
+ if (!dryRun) {
3191
+ try {
3192
+ writeFileAtomically(contractPath, document);
3193
+ } catch (error) {
3194
+ return {
3195
+ host,
3196
+ status: "failed",
3197
+ reason: `could not write ${contractPath}: ${error.message}`
3198
+ };
3199
+ }
3200
+ }
3201
+ return wired ? { host, status: "written" } : notWired();
3202
+ }
3203
+ function wiringArtifact(host, targetHome, hostRoot) {
3204
+ const root = resolveHostRoot(host, targetHome, hostRoot);
3205
+ const contractPath = path14.join(root, CONTRACT_FILENAME);
3206
+ switch (host) {
3207
+ case "claude":
3208
+ return { file: path14.join(root, "CLAUDE.md"), token: `@${CONTRACT_FILENAME}` };
3209
+ case "codex":
3210
+ case "cursor":
3211
+ return { file: path14.join(root, "AGENTS.md"), token: contractPath };
3212
+ case "opencode":
3213
+ return { file: openCodeConfigPath(root), token: `"${contractPath}"` };
3214
+ }
3215
+ }
3216
+ function openCodeConfigPath(root) {
3217
+ const json = path14.join(root, "opencode.json");
3218
+ if (fs10.existsSync(json))
3219
+ return json;
3220
+ return path14.join(root, "opencode.jsonc");
3221
+ }
3222
+ function isWired(host, targetHome, hostRoot) {
3223
+ const artifact = wiringArtifact(host, targetHome, hostRoot);
3224
+ const text = readFileOrNull(artifact.file);
3225
+ return text !== null && text.includes(artifact.token);
3226
+ }
3227
+ function notWiredReason(host, targetHome, hostRoot) {
3228
+ const artifact = wiringArtifact(host, targetHome, hostRoot);
3229
+ return `contract written, but ${host} has no artifact that loads it \u2014 expected ${artifact.token} in ${artifact.file}; run ${WIRING_REMEDY} to add the wiring`;
3230
+ }
3231
+ function readFileOrNull(filePath) {
3232
+ try {
3233
+ return fs10.readFileSync(filePath, "utf-8");
3234
+ } catch {
3235
+ return null;
3236
+ }
3237
+ }
3238
+ function isPlainObject3(value) {
3239
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3240
+ }
3241
+ // ../../packages/shared/dist/bootstrap/format.js
3242
+ function enabledWord(enabled) {
3243
+ return enabled ? "enabled" : "disabled";
3244
+ }
3245
+ function formatBootstrapInventory(state) {
3246
+ return BOOTSTRAP_RULES.map((rule) => {
3247
+ const current = enabledWord(state[rule.id]);
3248
+ const fallback = enabledWord(rule.defaultEnabled);
3249
+ return ` ${rule.id}: ${current} (default: ${fallback}) \u2014 ${rule.description}`;
3250
+ }).join(`
3251
+ `);
3252
+ }
3253
+ function formatBootstrapReport(report) {
3254
+ const dryRunSuffix = report.dryRun ? " (dry run \u2014 no files changed)" : "";
3255
+ const lines = [];
3256
+ if (report.rows.length === 0) {
3257
+ lines.push(`bootstrap: no host installed${dryRunSuffix}`);
3258
+ } else {
3259
+ lines.push(`bootstrap: ${report.rows.length} host(s)${dryRunSuffix}`);
3260
+ for (const row of report.rows) {
3261
+ const detail = row.reason ? `: ${row.reason}` : "";
3262
+ lines.push(` ${row.host}: ${row.status}${detail}`);
3263
+ }
3264
+ }
3265
+ if (report.ignoredStateKeys.length > 0) {
3266
+ lines.push("", `Ignored persisted rule state: ${report.ignoredStateKeys.join(", ")} \u2014 not a known rule id with a boolean value.`);
3267
+ }
3268
+ if (report.restartRequired) {
3269
+ lines.push("", "A host session restart is required for the change to take effect.");
3270
+ }
3271
+ return lines.join(`
3272
+ `);
3273
+ }
3274
+
3275
+ // ../../packages/shared/dist/bootstrap/index.js
3276
+ init_config_loader();
3277
+ // src/config-cli.ts
3278
+ import { promises as fs11 } from "fs";
3279
+ import path15 from "path";
2628
3280
  import os8 from "os";
2629
3281
  import { fileURLToPath } from "url";
2630
- var __dirname2 = path13.dirname(fileURLToPath(import.meta.url));
3282
+ var __dirname2 = path15.dirname(fileURLToPath(import.meta.url));
2631
3283
  var GENERATOR_MARKER = "scripts/generate-subagent-artifacts.ts";
2632
3284
  var GENERATOR_MARKER_MAX_LEVELS = 6;
2633
3285
  function formatVariantSync(results) {
@@ -2667,6 +3319,13 @@ Commands:
2667
3319
  profile set <name> [--host <h>] [--dry-run]
2668
3320
  Switch installed agents to a profile (restart required after)
2669
3321
 
3322
+ bootstrap list List every startup-contract rule: state, default, description
3323
+ bootstrap show Same as 'bootstrap list'
3324
+ bootstrap enable <rule-id> [--target <dir> --yes] [--dry-run]
3325
+ bootstrap disable <rule-id> [--target <dir> --yes] [--dry-run]
3326
+ Toggle one rule and re-render MASSA-AI.md for every
3327
+ recorded host (restart required after)
3328
+
2670
3329
  Examples:
2671
3330
  massa-ai-config init
2672
3331
  massa-ai-config init --mistral your-api-key
@@ -2675,6 +3334,8 @@ Examples:
2675
3334
  massa-ai-config set embedding.dimensions 1024
2676
3335
  massa-ai-config agents install --user
2677
3336
  massa-ai-config profile set work --dry-run
3337
+ massa-ai-config bootstrap list
3338
+ massa-ai-config bootstrap disable caveman
2678
3339
  `);
2679
3340
  }
2680
3341
  function parseOptions(args) {
@@ -2838,18 +3499,18 @@ Using defaults:`);
2838
3499
  return 1;
2839
3500
  }
2840
3501
  const scope = typeof options.project === "boolean" ? "project" : "user";
2841
- const agentsDir = scope === "project" ? path13.join(process.cwd(), ".opencode/agents") : path13.join(process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim() || path13.join(os8.homedir(), ".config"), "opencode", "agents");
2842
- const sourceAgentsDir = path13.resolve(__dirname2, "..", "agents");
3502
+ const agentsDir = scope === "project" ? path15.join(process.cwd(), ".opencode/agents") : path15.join(process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim() || path15.join(os8.homedir(), ".config"), "opencode", "agents");
3503
+ const sourceAgentsDir = path15.resolve(__dirname2, "..", "agents");
2843
3504
  if (subcommand === "install") {
2844
- await fs9.mkdir(agentsDir, { recursive: true });
3505
+ await fs11.mkdir(agentsDir, { recursive: true });
2845
3506
  let count = 0;
2846
- const entries = await fs9.readdir(sourceAgentsDir);
3507
+ const entries = await fs11.readdir(sourceAgentsDir);
2847
3508
  for (const entry of entries) {
2848
3509
  if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
2849
3510
  continue;
2850
- const src = path13.join(sourceAgentsDir, entry);
2851
- const dest = path13.join(agentsDir, entry);
2852
- await fs9.copyFile(src, dest);
3511
+ const src = path15.join(sourceAgentsDir, entry);
3512
+ const dest = path15.join(agentsDir, entry);
3513
+ await fs11.copyFile(src, dest);
2853
3514
  count++;
2854
3515
  }
2855
3516
  console.log(`+ ${count} subagent specialists (generated from skills/agents/*/SKILL.md)`);
@@ -2857,14 +3518,14 @@ Using defaults:`);
2857
3518
  } else {
2858
3519
  let removed = 0;
2859
3520
  try {
2860
- const entries = await fs9.readdir(agentsDir);
3521
+ const entries = await fs11.readdir(agentsDir);
2861
3522
  for (const entry of entries) {
2862
3523
  if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
2863
3524
  continue;
2864
- const filePath = path13.join(agentsDir, entry);
2865
- const content = await fs9.readFile(filePath, "utf8");
3525
+ const filePath = path15.join(agentsDir, entry);
3526
+ const content = await fs11.readFile(filePath, "utf8");
2866
3527
  if (content.includes("massa-ai-owned: true")) {
2867
- await fs9.unlink(filePath);
3528
+ await fs11.unlink(filePath);
2868
3529
  removed++;
2869
3530
  }
2870
3531
  }
@@ -2917,6 +3578,62 @@ Using defaults:`);
2917
3578
  console.error("Usage: massa-ai-config profile <list|show|set> ...");
2918
3579
  return 1;
2919
3580
  }
3581
+ case "bootstrap": {
3582
+ const subcommand = args[1];
3583
+ if (subcommand === "list" || subcommand === "show") {
3584
+ try {
3585
+ console.log(formatBootstrapInventory(resolveBootstrapState().state));
3586
+ } catch (e) {
3587
+ console.error(`Error: ${e.message}`);
3588
+ return 1;
3589
+ }
3590
+ return 0;
3591
+ }
3592
+ if (subcommand === "enable" || subcommand === "disable") {
3593
+ const ruleId = args[2];
3594
+ if (!ruleId) {
3595
+ console.error("Usage: massa-ai-config bootstrap <enable|disable> <rule-id> [--target <dir> --yes] [--dry-run]");
3596
+ return 1;
3597
+ }
3598
+ try {
3599
+ assertKnownRuleId(ruleId);
3600
+ } catch (e) {
3601
+ console.error(`Error: ${e.message}`);
3602
+ return 1;
3603
+ }
3604
+ const targetOpt = typeof options.target === "string" ? options.target : undefined;
3605
+ const targetHome = targetOpt === undefined ? os8.homedir() : path15.resolve(targetOpt);
3606
+ if (targetHome !== os8.homedir() && options.yes !== true) {
3607
+ console.error(`Error: --target ${targetHome} is not your home (${os8.homedir()}) \u2014 pass --yes to confirm writing there`);
3608
+ return 1;
3609
+ }
3610
+ const dryRun = options["dry-run"] === true;
3611
+ try {
3612
+ if (dryRun) {
3613
+ console.log(`bootstrap ${subcommand} ${ruleId}: dry run \u2014 ${getConfigPath()} was not written`);
3614
+ } else {
3615
+ setBootstrapRuleEnabled(ruleId, subcommand === "enable");
3616
+ }
3617
+ const stateFile = bootstrapStateFilePath(targetHome);
3618
+ if (stateFile !== getConfigPath()) {
3619
+ console.error(`Warning: the rule state is persisted to ${getConfigPath()}, but --target renders from ${stateFile} \u2014 set XDG_CONFIG_HOME to move the persisted state`);
3620
+ }
3621
+ const repoRoot = findRepoRootWithMarker(__dirname2, GENERATOR_MARKER, GENERATOR_MARKER_MAX_LEVELS);
3622
+ const report = applyBootstrapState({
3623
+ targetHome,
3624
+ dryRun,
3625
+ sourcePath: repoRoot === null ? undefined : path15.join(repoRoot, "skills", "AGENTS.md")
3626
+ });
3627
+ console.log(formatBootstrapReport(report));
3628
+ return bootstrapReportSucceeded(report) ? 0 : 1;
3629
+ } catch (e) {
3630
+ console.error(`Error: ${e.message}`);
3631
+ return 1;
3632
+ }
3633
+ }
3634
+ console.error("Usage: massa-ai-config bootstrap <list|show|enable|disable> ...");
3635
+ return 1;
3636
+ }
2920
3637
  default:
2921
3638
  console.error(`Unknown command: ${command}`);
2922
3639
  help();
package/dist/index.js CHANGED
@@ -600,15 +600,18 @@ var init_massa_ai_config = __esm(() => {
600
600
  security: {
601
601
  corsOrigins: []
602
602
  },
603
- scheduler: DEFAULT_SCHEDULER_CONFIG
603
+ scheduler: DEFAULT_SCHEDULER_CONFIG,
604
+ bootstrap: { rules: {} }
604
605
  };
605
606
  });
606
607
 
607
608
  // ../../packages/shared/dist/config/config-loader.js
608
609
  var exports_config_loader = {};
609
610
  __export(exports_config_loader, {
611
+ writeRawConfig: () => writeRawConfig,
610
612
  writeFileAtomically: () => writeFileAtomically,
611
613
  saveConfig: () => saveConfig,
614
+ readRawConfigStrict: () => readRawConfigStrict,
612
615
  migrateDataDirOnce: () => migrateDataDirOnce,
613
616
  mergeSchedulerSection: () => mergeSchedulerSection,
614
617
  loadRawUserConfig: () => loadRawUserConfig,
@@ -619,12 +622,23 @@ __export(exports_config_loader, {
619
622
  getConfigForEnv: () => getConfigForEnv,
620
623
  getConfigDir: () => getConfigDir,
621
624
  configExists: () => configExists,
622
- __resetMigrationForTests: () => __resetMigrationForTests
625
+ __resetMigrationForTests: () => __resetMigrationForTests,
626
+ ConfigWriteConflictError: () => ConfigWriteConflictError,
627
+ ConfigParseError: () => ConfigParseError
623
628
  });
624
629
  import fs from "fs";
625
630
  import path3 from "path";
626
631
  import os2 from "os";
627
632
  import crypto from "crypto";
633
+ function readConfigFileOrEmpty() {
634
+ try {
635
+ return fs.readFileSync(CONFIG_FILE, "utf-8");
636
+ } catch (error) {
637
+ if (error?.code === "ENOENT")
638
+ return "";
639
+ throw error;
640
+ }
641
+ }
628
642
  function getConfigDir() {
629
643
  return CONFIG_DIR;
630
644
  }
@@ -691,6 +705,21 @@ function loadRawUserConfig() {
691
705
  return {};
692
706
  }
693
707
  }
708
+ function readRawConfigStrict() {
709
+ const raw = readConfigFileOrEmpty();
710
+ if (raw === "")
711
+ return {};
712
+ let parsed;
713
+ try {
714
+ parsed = JSON.parse(raw);
715
+ } catch (error) {
716
+ throw new ConfigParseError(CONFIG_FILE, error);
717
+ }
718
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
719
+ throw new ConfigParseError(CONFIG_FILE, new Error("parsed value is not a JSON object"));
720
+ }
721
+ return parsed;
722
+ }
694
723
  function loadConfigSafe() {
695
724
  try {
696
725
  return loadConfig();
@@ -745,6 +774,43 @@ function writeFileAtomically(targetPath, content) {
745
774
  function saveConfig(config) {
746
775
  writeFileAtomically(CONFIG_FILE, JSON.stringify(config, null, 2));
747
776
  }
777
+ function writeRawConfig(doc, opts) {
778
+ const onDiskAtStart = readConfigFileOrEmpty();
779
+ if (onDiskAtStart === opts.expectedBytes) {
780
+ writeFileAtomically(CONFIG_FILE, JSON.stringify(doc, null, 2));
781
+ return;
782
+ }
783
+ let original;
784
+ let current;
785
+ try {
786
+ original = opts.expectedBytes === "" ? {} : JSON.parse(opts.expectedBytes);
787
+ } catch (error) {
788
+ throw new ConfigParseError(`${CONFIG_FILE} (caller-supplied expectedBytes)`, error);
789
+ }
790
+ try {
791
+ current = onDiskAtStart === "" ? {} : JSON.parse(onDiskAtStart);
792
+ } catch (error) {
793
+ throw new ConfigParseError(CONFIG_FILE, error);
794
+ }
795
+ const reapplied = { ...current };
796
+ const touchedKeys = new Set([...Object.keys(original), ...Object.keys(doc)]);
797
+ for (const key of touchedKeys) {
798
+ const before = JSON.stringify(original[key]);
799
+ const after = JSON.stringify(doc[key]);
800
+ if (before === after)
801
+ continue;
802
+ if (Object.prototype.hasOwnProperty.call(doc, key)) {
803
+ reapplied[key] = doc[key];
804
+ } else {
805
+ delete reapplied[key];
806
+ }
807
+ }
808
+ const onDiskImmediatelyBeforeWrite = readConfigFileOrEmpty();
809
+ if (onDiskImmediatelyBeforeWrite !== onDiskAtStart) {
810
+ throw new ConfigWriteConflictError(CONFIG_FILE);
811
+ }
812
+ writeFileAtomically(CONFIG_FILE, JSON.stringify(reapplied, null, 2));
813
+ }
748
814
  function initConfig() {
749
815
  if (!fs.existsSync(CONFIG_FILE)) {
750
816
  saveConfig(defaultMassaAiConfig);
@@ -771,12 +837,25 @@ function getConfigForEnv() {
771
837
  env.ENABLE_METRICS = String(config.logging.enableMetrics);
772
838
  return env;
773
839
  }
774
- var CONFIG_DIR, CONFIG_FILE, migrationAttempted = false, tempFileCounter = 0;
840
+ var CONFIG_DIR, CONFIG_FILE, ConfigParseError, ConfigWriteConflictError, migrationAttempted = false, tempFileCounter = 0;
775
841
  var init_config_loader = __esm(() => {
776
842
  init_massa_ai_config();
777
843
  init_xdg();
778
844
  CONFIG_DIR = configDir("massa-ai");
779
845
  CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
846
+ ConfigParseError = class ConfigParseError extends Error {
847
+ constructor(filePath, cause) {
848
+ const reason = cause instanceof Error ? cause.message : String(cause);
849
+ super(`Failed to parse ${filePath}: ${reason}`);
850
+ this.name = "ConfigParseError";
851
+ }
852
+ };
853
+ ConfigWriteConflictError = class ConfigWriteConflictError extends Error {
854
+ constructor(filePath) {
855
+ super(`${filePath} changed on disk twice while writing \u2014 refusing to overwrite a ` + `concurrent update. Re-read the file and retry.`);
856
+ this.name = "ConfigWriteConflictError";
857
+ }
858
+ };
780
859
  });
781
860
 
782
861
  // ../../packages/shared/dist/env.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/opencode-plugin",
3
- "version": "1.55.0",
3
+ "version": "1.56.0",
4
4
  "description": "massa-ai plugin for OpenCode - Semantic code search, memory, and context compression",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,8 +24,8 @@
24
24
  "dependencies": {
25
25
  "@opencode-ai/plugin": "^1.2.15",
26
26
  "@opencode-ai/sdk": "^1.2.15",
27
- "@massa-ai/core": "^1.55.0",
28
- "@massa-ai/shared": "^1.55.0"
27
+ "@massa-ai/core": "^1.56.0",
28
+ "@massa-ai/shared": "^1.56.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.10.5",