maskweaver 0.10.0 → 0.11.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.
@@ -0,0 +1,7 @@
1
+ export interface NpmDistTags {
2
+ latest?: string;
3
+ beta?: string;
4
+ next?: string;
5
+ [tag: string]: string | undefined;
6
+ }
7
+ export declare function fetchNpmDistTags(packageName: string): Promise<NpmDistTags | null>;
@@ -0,0 +1,15 @@
1
+ const NPM_FETCH_TIMEOUT_MS = 5000;
2
+ export async function fetchNpmDistTags(packageName) {
3
+ try {
4
+ const res = await fetch(`https://registry.npmjs.org/-/package/${encodeURIComponent(packageName)}/dist-tags`, {
5
+ signal: AbortSignal.timeout(NPM_FETCH_TIMEOUT_MS),
6
+ });
7
+ if (!res.ok)
8
+ return null;
9
+ const data = (await res.json());
10
+ return data;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export declare function getPluginNameWithVersion(currentVersion: string, packageName: string): Promise<string>;
@@ -0,0 +1,21 @@
1
+ import { fetchNpmDistTags } from "./npm-dist-tags.js";
2
+ const PRIORITIZED_TAGS = ["latest", "beta", "next"];
3
+ function getFallbackEntry(version, packageName) {
4
+ const prereleaseMatch = version.match(/-([a-zA-Z][a-zA-Z0-9-]*)(?:\.|$)/);
5
+ if (prereleaseMatch) {
6
+ return `${packageName}@${prereleaseMatch[1]}`;
7
+ }
8
+ return packageName;
9
+ }
10
+ export async function getPluginNameWithVersion(currentVersion, packageName) {
11
+ const distTags = await fetchNpmDistTags(packageName);
12
+ if (distTags) {
13
+ const allTags = new Set([...PRIORITIZED_TAGS, ...Object.keys(distTags)]);
14
+ for (const tag of allTags) {
15
+ if (distTags[tag] === currentVersion) {
16
+ return `${packageName}@${tag}`;
17
+ }
18
+ }
19
+ }
20
+ return getFallbackEntry(currentVersion, packageName);
21
+ }
@@ -0,0 +1,9 @@
1
+ export interface VersionCompatibility {
2
+ canUpgrade: boolean;
3
+ reason?: string;
4
+ isDowngrade: boolean;
5
+ isMajorBump: boolean;
6
+ requiresMigration: boolean;
7
+ }
8
+ export declare function checkVersionCompatibility(currentVersion: string | null, newVersion: string): VersionCompatibility;
9
+ export declare function extractVersionFromPluginEntry(entry: string): string | null;
@@ -0,0 +1,81 @@
1
+ function parseVersion(version) {
2
+ const clean = version.replace(/^v/, "").split("-")[0];
3
+ return clean.split(".").map(Number);
4
+ }
5
+ function compareVersions(a, b) {
6
+ const partsA = parseVersion(a);
7
+ const partsB = parseVersion(b);
8
+ const maxLen = Math.max(partsA.length, partsB.length);
9
+ for (let i = 0; i < maxLen; i++) {
10
+ const numA = partsA[i] ?? 0;
11
+ const numB = partsB[i] ?? 0;
12
+ if (numA !== numB) {
13
+ return numA - numB;
14
+ }
15
+ }
16
+ return 0;
17
+ }
18
+ export function checkVersionCompatibility(currentVersion, newVersion) {
19
+ if (!currentVersion) {
20
+ return {
21
+ canUpgrade: true,
22
+ isDowngrade: false,
23
+ isMajorBump: false,
24
+ requiresMigration: false,
25
+ };
26
+ }
27
+ const cleanCurrent = currentVersion.replace(/^v/, "");
28
+ const cleanNew = newVersion.replace(/^v/, "");
29
+ try {
30
+ const comparison = compareVersions(cleanNew, cleanCurrent);
31
+ if (comparison < 0) {
32
+ return {
33
+ canUpgrade: false,
34
+ reason: `Downgrade from ${currentVersion} to ${newVersion} is not allowed`,
35
+ isDowngrade: true,
36
+ isMajorBump: false,
37
+ requiresMigration: false,
38
+ };
39
+ }
40
+ if (comparison === 0) {
41
+ return {
42
+ canUpgrade: true,
43
+ reason: `Version ${newVersion} is already installed`,
44
+ isDowngrade: false,
45
+ isMajorBump: false,
46
+ requiresMigration: false,
47
+ };
48
+ }
49
+ const currentMajor = cleanCurrent.split(".")[0];
50
+ const newMajor = cleanNew.split(".")[0];
51
+ const isMajorBump = currentMajor !== newMajor;
52
+ if (isMajorBump) {
53
+ return {
54
+ canUpgrade: true,
55
+ reason: `Major version upgrade from ${currentVersion} to ${newVersion} - configuration migration may be required`,
56
+ isDowngrade: false,
57
+ isMajorBump: true,
58
+ requiresMigration: true,
59
+ };
60
+ }
61
+ return {
62
+ canUpgrade: true,
63
+ isDowngrade: false,
64
+ isMajorBump: false,
65
+ requiresMigration: false,
66
+ };
67
+ }
68
+ catch {
69
+ return {
70
+ canUpgrade: true,
71
+ reason: `Unable to compare versions ${currentVersion} and ${newVersion} - proceeding with caution`,
72
+ isDowngrade: false,
73
+ isMajorBump: false,
74
+ requiresMigration: false,
75
+ };
76
+ }
77
+ }
78
+ export function extractVersionFromPluginEntry(entry) {
79
+ const match = entry.match(/@(.+)$/);
80
+ return match ? match[1] : null;
81
+ }
@@ -7,8 +7,10 @@ import { VERSION } from "../version.js";
7
7
  import { runDoctor, printDoctorReport } from "./doctor.js";
8
8
  import { writeDefaultRuntimeConfig, writeDefaultPluginConfig } from "../shared/generate-agents.js";
9
9
  const PLUGIN_NAME = "maskweaver";
10
+ const PLUGIN_LATEST = "maskweaver@latest";
10
11
  const LEGACY_PLUGIN_NAMES = ["maskweaver/plugin", "@maskweaver/plugin"];
11
- const ALL_PLUGIN_NAMES = [PLUGIN_NAME, ...LEGACY_PLUGIN_NAMES];
12
+ const ALL_PLUGIN_NAMES = [PLUGIN_NAME, PLUGIN_LATEST, ...LEGACY_PLUGIN_NAMES];
13
+ const ALL_BARE_OR_VERSIONED = [PLUGIN_NAME, PLUGIN_LATEST];
12
14
  const CONFIG_NAME = "opencode.json";
13
15
  const colors = {
14
16
  reset: "\x1b[0m",
@@ -83,19 +85,31 @@ function getInstalledPluginEntries(config) {
83
85
  return config.plugin.filter((plugin) => ALL_PLUGIN_NAMES.includes(plugin));
84
86
  }
85
87
  function isPluginInstalled(config) {
86
- return getInstalledPluginEntries(config).length > 0;
88
+ if (!config.plugin || !Array.isArray(config.plugin))
89
+ return false;
90
+ return config.plugin.some((p) => ALL_BARE_OR_VERSIONED.some(name => p === name || p.startsWith(`${name}@`)));
87
91
  }
88
92
  function normalizePluginEntries(config) {
89
93
  if (!config.plugin || !Array.isArray(config.plugin)) {
90
94
  return { migratedFrom: [], changed: false };
91
95
  }
92
96
  const migratedFrom = config.plugin.filter((plugin) => LEGACY_PLUGIN_NAMES.includes(plugin));
93
- if (migratedFrom.length === 0) {
97
+ const hasBare = config.plugin.some((p) => p === PLUGIN_NAME);
98
+ const hasVersioned = config.plugin.some((p) => p.startsWith(`${PLUGIN_NAME}@`));
99
+ if (migratedFrom.length === 0 && !hasBare && !hasVersioned) {
100
+ if (config.plugin.some((p) => p === PLUGIN_LATEST)) {
101
+ return { migratedFrom: [], changed: false };
102
+ }
94
103
  return { migratedFrom: [], changed: false };
95
104
  }
96
- const preserved = config.plugin.filter((plugin) => !ALL_PLUGIN_NAMES.includes(plugin));
97
- config.plugin = [...preserved, PLUGIN_NAME];
98
- return { migratedFrom, changed: true };
105
+ const preserved = config.plugin.filter((plugin) => !ALL_PLUGIN_NAMES.includes(plugin) &&
106
+ !plugin.startsWith(`${PLUGIN_NAME}@`));
107
+ const existingVersioned = config.plugin.find((p) => p.startsWith(`${PLUGIN_NAME}@`));
108
+ config.plugin = [...preserved, existingVersioned || PLUGIN_LATEST];
109
+ if (migratedFrom.length > 0 || hasBare) {
110
+ return { migratedFrom: [...migratedFrom, hasBare ? PLUGIN_NAME : ""].filter(Boolean), changed: true };
111
+ }
112
+ return { migratedFrom: [], changed: false };
99
113
  }
100
114
  async function installPlugin(options) {
101
115
  const isLocal = options.local || false;
@@ -120,13 +134,14 @@ async function installPlugin(options) {
120
134
  log("");
121
135
  return;
122
136
  }
123
- if (config.plugin.includes(PLUGIN_NAME)) {
137
+ const alreadyInstalled = config.plugin.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`));
138
+ if (alreadyInstalled) {
124
139
  warning(`플러그인이 이미 설치되어 있습니다.`);
125
- info(`현재 ${scope} 설정에 등록되어 있습니다: ${PLUGIN_NAME}`);
140
+ info(`현재 ${scope} 설정에 등록되어 있습니다: ${PLUGIN_LATEST}`);
126
141
  log("");
127
142
  return;
128
143
  }
129
- config.plugin.push(PLUGIN_NAME);
144
+ config.plugin.push(PLUGIN_LATEST);
130
145
  writeConfig(configPath, config);
131
146
  let createdRuntimeConfig = false;
132
147
  let createdPluginConfig = false;
@@ -150,7 +165,7 @@ async function installPlugin(options) {
150
165
  }
151
166
  success(`플러그인이 성공적으로 설치되었습니다!`);
152
167
  log("");
153
- log(`📦 설치된 플러그인: ${PLUGIN_NAME}`, "bright");
168
+ log(`📦 설치된 플러그인: ${PLUGIN_LATEST}`, "bright");
154
169
  log(`📍 위치: ${configPath}`, "dim");
155
170
  log("");
156
171
  log(`다음 단계:`, "bright");
@@ -190,7 +205,7 @@ async function uninstallPlugin(options) {
190
205
  log("");
191
206
  return;
192
207
  }
193
- config.plugin = config.plugin.filter((p) => !ALL_PLUGIN_NAMES.includes(p));
208
+ config.plugin = config.plugin.filter((p) => !ALL_PLUGIN_NAMES.includes(p) && !p.startsWith(`${PLUGIN_NAME}@`));
194
209
  writeConfig(configPath, config);
195
210
  success(`플러그인이 성공적으로 제거되었습니다.`);
196
211
  log("");
@@ -5,6 +5,8 @@ import * as os from 'node:os';
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { VERSION } from '../version.js';
8
+ import { fetchNpmDistTags } from '../cli/config-manager/npm-dist-tags.js';
9
+ import { checkVersionCompatibility } from '../cli/config-manager/version-compatibility.js';
8
10
  import { loadPluginConfig, isMaskEnabled, isToolEnabled, getDefaultMask, isAutoActivateEnabled, isVerboseLoggingEnabled, isCompletionSoundEnabled, validateConfig, } from './config/index.js';
9
11
  import { createMemorySearchTool } from './tools/memorySearch.js';
10
12
  import { createMemoryWriteTool } from './tools/memoryWrite.js';
@@ -595,10 +597,10 @@ function playCompletionSound(config) {
595
597
  }
596
598
  let state = null;
597
599
  const OPENCODE_PACKAGES_DIR = path.join(os.homedir(), '.cache', 'opencode', 'packages');
598
- const MASKWEAVER_PACKAGE_GLOB = 'maskweaver@*';
599
- function getCacheVersion() {
600
- if (!fs.existsSync(OPENCODE_PACKAGES_DIR))
601
- return null;
600
+ function getCachedPackageVersion() {
601
+ if (!fs.existsSync(OPENCODE_PACKAGES_DIR)) {
602
+ return { version: null, pkgDir: null };
603
+ }
602
604
  const entries = fs.readdirSync(OPENCODE_PACKAGES_DIR);
603
605
  for (const entry of entries) {
604
606
  if (!entry.startsWith('maskweaver@'))
@@ -615,51 +617,88 @@ function getCacheVersion() {
615
617
  continue;
616
618
  }
617
619
  }
618
- return null;
620
+ return { version: null, pkgDir: null };
619
621
  }
620
- function getLatestNpmVersion() {
621
- try {
622
- const result = spawnSync('npm', ['view', 'maskweaver', 'version'], {
623
- encoding: 'utf-8',
624
- stdio: ['pipe', 'pipe', 'pipe'],
625
- timeout: 10000,
626
- windowsHide: true,
627
- });
628
- if (result.status === 0 && result.stdout) {
629
- return result.stdout.trim();
630
- }
631
- }
632
- catch { }
633
- return null;
622
+ async function fetchLatestFromRegistry() {
623
+ return fetchNpmDistTags('maskweaver');
634
624
  }
635
- function checkAndInvalidateCache() {
636
- const cached = getCacheVersion();
637
- if (!cached) {
638
- return { invalidated: false, cachedVersion: null, latestVersion: null };
625
+ async function checkForUpdates() {
626
+ const cached = getCachedPackageVersion();
627
+ const installedVersion = cached.version;
628
+ const latestTags = await fetchLatestFromRegistry();
629
+ const latestVersion = latestTags?.latest ?? null;
630
+ if (!installedVersion) {
631
+ return {
632
+ updateAvailable: false,
633
+ installedVersion: null,
634
+ latestVersion,
635
+ isDowngrade: false,
636
+ isMajorBump: false,
637
+ requiresMigration: false,
638
+ message: null,
639
+ };
639
640
  }
640
- if (cached.version === VERSION) {
641
- return { invalidated: false, cachedVersion: cached.version, latestVersion: null };
641
+ if (installedVersion === VERSION) {
642
+ return {
643
+ updateAvailable: false,
644
+ installedVersion,
645
+ latestVersion,
646
+ isDowngrade: false,
647
+ isMajorBump: false,
648
+ requiresMigration: false,
649
+ message: null,
650
+ };
642
651
  }
643
- const latest = getLatestNpmVersion();
644
- if (latest && latest !== cached.version) {
645
- try {
646
- fs.rmSync(cached.pkgDir, { recursive: true, force: true });
647
- return { invalidated: true, cachedVersion: cached.version, latestVersion: latest };
648
- }
649
- catch {
650
- return { invalidated: false, cachedVersion: cached.version, latestVersion: latest };
651
- }
652
+ if (!latestVersion || latestVersion === installedVersion) {
653
+ return {
654
+ updateAvailable: false,
655
+ installedVersion,
656
+ latestVersion,
657
+ isDowngrade: false,
658
+ isMajorBump: false,
659
+ requiresMigration: false,
660
+ message: `Plugin cache v${installedVersion} — current is v${VERSION}.`,
661
+ };
662
+ }
663
+ const compatibility = checkVersionCompatibility(installedVersion, latestVersion);
664
+ const currentIsLatest = installedVersion === latestVersion;
665
+ if (currentIsLatest) {
666
+ return {
667
+ updateAvailable: false,
668
+ installedVersion,
669
+ latestVersion,
670
+ isDowngrade: false,
671
+ isMajorBump: false,
672
+ requiresMigration: false,
673
+ message: `Plugin cache v${installedVersion} — current is v${VERSION}.`,
674
+ };
652
675
  }
653
- return { invalidated: false, cachedVersion: cached.version, latestVersion: latest };
676
+ return {
677
+ updateAvailable: true,
678
+ installedVersion,
679
+ latestVersion,
680
+ isDowngrade: compatibility.isDowngrade,
681
+ isMajorBump: compatibility.isMajorBump,
682
+ requiresMigration: compatibility.requiresMigration,
683
+ message: `Update available: v${installedVersion} → v${latestVersion}${compatibility.requiresMigration ? ' (major upgrade — migration may be required)' : ''}.`,
684
+ };
654
685
  }
655
686
  export const MaskweaverPlugin = async ({ client, directory, project, worktree, $, serverUrl }) => {
656
- const cacheCheck = checkAndInvalidateCache();
657
- if (cacheCheck.invalidated) {
658
- pluginLog(client, 'warn', `Stale plugin cache detected (v${cacheCheck.cachedVersion}). Cleared — v${cacheCheck.latestVersion} will install on next restart.`);
659
- pluginLog(client, 'warn', `Please restart OpenCode to activate maskweaver v${VERSION}.`);
687
+ const updateCheck = await checkForUpdates();
688
+ if (updateCheck.updateAvailable) {
689
+ if (updateCheck.isDowngrade) {
690
+ pluginLog(client, 'warn', `Downgrade detected (v${updateCheck.installedVersion} v${updateCheck.latestVersion}) — not allowed. Please use opencode --upgrade.`);
691
+ }
692
+ else {
693
+ pluginLog(client, 'warn', `Update available: maskweaver v${updateCheck.installedVersion} → v${updateCheck.latestVersion}`);
694
+ if (updateCheck.requiresMigration) {
695
+ pluginLog(client, 'warn', `Major version upgrade — configuration migration may be required.`);
696
+ }
697
+ pluginLog(client, 'info', `Run \`opencode --upgrade\` or \`npm update -g maskweaver\` to update.`);
698
+ }
660
699
  }
661
- else if (cacheCheck.cachedVersion && cacheCheck.cachedVersion !== VERSION) {
662
- pluginLog(client, 'info', `Plugin cache v${cacheCheck.cachedVersion} — current is v${VERSION}. Restart recommended.`);
700
+ else if (updateCheck.message) {
701
+ pluginLog(client, 'info', updateCheck.message);
663
702
  }
664
703
  const pluginConfig = loadPluginConfig(directory, { client, verbose: false });
665
704
  const configErrors = validateConfig(pluginConfig);
@@ -91,7 +91,7 @@ const INLINE_DEFAULT = {
91
91
  name: 'craft',
92
92
  aliases: [],
93
93
  deprecatedAliases: [],
94
- description: 'Prepare execution context for a phase (phase auto-select if omitted)',
94
+ description: '[DEPRECATED] Use build instead it now auto-approves and runs craft+build+verify',
95
95
  category: 'execution',
96
96
  args: [
97
97
  { name: 'phaseId', type: 'string', required: false, description: 'Phase ID (auto-select if omitted)' },
@@ -99,7 +99,7 @@ const INLINE_DEFAULT = {
99
99
  ],
100
100
  mdFile: 'weave-craft.md',
101
101
  handler: 'handleCraft',
102
- examples: ['weave command=craft', 'weave command=craft phaseId="P1"'],
102
+ examples: ['weave command=build (use this instead)'],
103
103
  },
104
104
  {
105
105
  name: 'build',
@@ -112,6 +112,7 @@ const INLINE_DEFAULT = {
112
112
  { name: 'phaseIds', type: 'string', required: false, description: 'Comma-separated phase IDs for run action' },
113
113
  { name: 'buildId', type: 'string', required: false, description: 'Build ID for status/stop/resume/sync actions' },
114
114
  { name: 'maxRetries', type: 'number', default: 3, min: 1, max: 10, description: 'Maximum retries per task (for run action)' },
115
+ { name: 'taskResults', type: 'string', required: false, description: 'JSON array of TaskResult from previous wave (for resume)' },
115
116
  { name: 'maxIterations', type: 'number', default: 1, min: 1, max: 20, description: 'Maximum loop iterations before blocking' },
116
117
  { name: 'maxNoProgress', type: 'number', default: 1, min: 0, max: 10, description: 'Maximum repeated no-progress failures before blocking' },
117
118
  ],
@@ -216,12 +217,14 @@ const INLINE_DEFAULT = {
216
217
  args: [
217
218
  { name: 'sync', type: 'boolean', default: false, description: 'Force regenerate agent .md files from config pool' },
218
219
  { name: 'init', type: 'boolean', default: false, description: 'Create default maskweaver.config.json with pool template' },
220
+ { name: 'force', type: 'boolean', default: false, description: 'Force re-detect subscription and overwrite maskweaver.config.json' },
219
221
  ],
220
222
  mdFile: 'weave-agents.md',
221
223
  handler: 'handleAgents',
222
224
  examples: [
223
225
  'weave command=agents sync=true',
224
226
  'weave command=agents init=true',
227
+ 'weave command=agents force=true',
225
228
  ],
226
229
  },
227
230
  {
@@ -13,6 +13,7 @@ export declare function createWeaveTool(): {
13
13
  }>>;
14
14
  sync: z.ZodOptional<z.ZodBoolean>;
15
15
  init: z.ZodOptional<z.ZodBoolean>;
16
+ force: z.ZodOptional<z.ZodBoolean>;
16
17
  docsPath: z.ZodOptional<z.ZodString>;
17
18
  phaseId: z.ZodOptional<z.ZodString>;
18
19
  projectName: z.ZodOptional<z.ZodString>;
@@ -56,6 +57,7 @@ export declare function createWeaveTool(): {
56
57
  deep: z.ZodOptional<z.ZodBoolean>;
57
58
  maxRetries: z.ZodOptional<z.ZodNumber>;
58
59
  buildId: z.ZodOptional<z.ZodString>;
60
+ taskResults: z.ZodOptional<z.ZodString>;
59
61
  phaseIds: z.ZodOptional<z.ZodString>;
60
62
  };
61
63
  execute: (args: {
@@ -97,6 +99,7 @@ export declare function createWeaveTool(): {
97
99
  deep?: boolean;
98
100
  maxRetries?: number;
99
101
  buildId?: string;
102
+ taskResults?: string;
100
103
  phaseIds?: string;
101
104
  }, context: {
102
105
  worktree: string;