ma-agents 3.13.0 → 3.13.2-skillname.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/lib/bmad.js CHANGED
@@ -278,17 +278,34 @@ function ensurePluginStageGitignoredForProject(projectRoot) {
278
278
  console.warn(chalk.yellow(` Warning: could not load installer for gitignore policy: ${err.message}`));
279
279
  return;
280
280
  }
281
- if (typeof installer.ensurePluginStageIgnored !== 'function') return;
282
- try {
283
- installer.ensurePluginStageIgnored(projectRoot);
284
- } catch (err) {
285
- // EACCES on read-only `.gitignore`, EROFS on read-only filesystem, etc.
286
- // Logged so the operator can fix it manually; never fatal to the install.
287
- console.warn(
288
- chalk.yellow(
289
- ` Warning: could not update ${path.join(projectRoot, '.gitignore')} with plugin-stage entry: ${err.message}`
290
- )
291
- );
281
+ if (typeof installer.ensurePluginStageIgnored === 'function') {
282
+ try {
283
+ installer.ensurePluginStageIgnored(projectRoot);
284
+ } catch (err) {
285
+ // EACCES on read-only `.gitignore`, EROFS on read-only filesystem, etc.
286
+ // Logged so the operator can fix it manually; never fatal to the install.
287
+ console.warn(
288
+ chalk.yellow(
289
+ ` Warning: could not update ${path.join(projectRoot, '.gitignore')} with plugin-stage entry: ${err.message}`
290
+ )
291
+ );
292
+ }
293
+ }
294
+
295
+ // Bug 27.11 — also ensure the regenerated, installer-managed `_bmad/` tree is
296
+ // gitignored. Same project-root contract and same non-fatal failure policy as
297
+ // the plugin-stage entry above. `_bmad-output/` is intentionally left tracked
298
+ // (see ensureBmadOutputTracked) and is not matched by these patterns.
299
+ if (typeof installer.ensureBmadDirIgnored === 'function') {
300
+ try {
301
+ installer.ensureBmadDirIgnored(projectRoot);
302
+ } catch (err) {
303
+ console.warn(
304
+ chalk.yellow(
305
+ ` Warning: could not update ${path.join(projectRoot, '.gitignore')} with _bmad/ entry: ${err.message}`
306
+ )
307
+ );
308
+ }
292
309
  }
293
310
  }
294
311
 
@@ -847,6 +864,11 @@ async function installBmad(modules = ['bmm', 'bmb'], tools = [], projectRoot = p
847
864
  try {
848
865
  runCommand(command, { cwd: projectRoot, env: buildChildSpawnEnv() });
849
866
  bmadInvokeSucceeded = true;
867
+ // Bug 27.14 — repair any module artifact path that upstream's
868
+ // suffix-match output-folder resolution contaminated (e.g. gds paths
869
+ // pinned to bmb's "skills" instead of the chosen output folder). Runs
870
+ // after bmad-method writes the config and before downstream consumers.
871
+ normalizeModuleOutputFolders(projectRoot);
850
872
  // F1a — on-prem persona phase-prefix pass. Runs BEFORE
851
873
  // deployMethodology so a methodology-deploy failure doesn't prevent
852
874
  // the prefix from being applied (they are independent concerns).
@@ -953,6 +975,10 @@ async function runMigration(modules, tools, projectRoot, force, { userName, comm
953
975
  return false;
954
976
  }
955
977
 
978
+ // Bug 27.14 — repair contaminated module artifact paths on the migration
979
+ // path too (same upstream suffix-match resolution applies to updates).
980
+ normalizeModuleOutputFolders(projectRoot);
981
+
956
982
  // Step 3: Merge user customizations
957
983
  if (backedUpFiles.length > 0) {
958
984
  console.log(chalk.cyan(' Step 3/4: Merging user customizations...'));
@@ -1808,6 +1834,237 @@ function readCanonicalBmadConfig(projectRoot) {
1808
1834
  }
1809
1835
  }
1810
1836
 
1837
+ // ── Bug 27.14 — output-folder propagation normalization ──────────────────────
1838
+ //
1839
+ // Upstream bmad-method's resolveConfigValue (official-modules.js) resolves the
1840
+ // `{output_folder}` token via a `endsWith('_output_folder')` suffix match over
1841
+ // all collected answers. When bmb (answer key `bmb_..._output_folder` = "skills")
1842
+ // and gds are co-installed, gds's `{output_folder}` resolves alphabetically to
1843
+ // bmb's "skills" value, so gds artifact paths become `{project-root}/skills/...`
1844
+ // instead of `{project-root}/_bmad-output/...`.
1845
+ //
1846
+ // ma-agents owns no part of that resolution (it lives in vendored node_modules),
1847
+ // so this is a post-install WORK-AROUND: after bmad-method writes the config,
1848
+ // re-read `[core] output_folder` and rewrite any contaminated module artifact
1849
+ // path back onto the canonical output folder. We deliberately scope the rewrite
1850
+ // to the well-known core artifact keys so we never touch a module's INTENTIONAL
1851
+ // own-folder values (e.g. bmb's `bmad_builder_output_folder = ".../skills"` or
1852
+ // wds's `design_artifacts = ".../design-artifacts"`).
1853
+ //
1854
+ // Upstream follow-up (do NOT edit node_modules here): patch resolveConfigValue
1855
+ // to require an exact `output_folder` / `core_output_folder` key instead of the
1856
+ // `endsWith` suffix match. Tracked as an upstream follow-up.
1857
+
1858
+ // Artifact keys that must always live under `[core] output_folder`. These are
1859
+ // the canonical bmm/gds planning+implementation+architecture artifact keys plus
1860
+ // tea's test_artifacts. Module-specific own-folder keys (bmb_*_output_folder,
1861
+ // design_artifacts, project_knowledge, *_output sub-paths) are intentionally
1862
+ // excluded.
1863
+ const OUTPUT_FOLDER_ARTIFACT_KEYS = [
1864
+ 'planning_artifacts',
1865
+ 'architecture_artifacts',
1866
+ 'implementation_artifacts',
1867
+ 'test_artifacts',
1868
+ ];
1869
+
1870
+ /**
1871
+ * Given an artifact path value and the canonical output folder, return a
1872
+ * corrected value if the path's output-folder segment differs from the
1873
+ * canonical one, or `null` if no change is needed.
1874
+ *
1875
+ * Handles both conventions:
1876
+ * - `{project-root}/<seg>/<suffix...>` (config.toml style)
1877
+ * - `<seg>/<suffix...>` (config.yaml stripped style)
1878
+ *
1879
+ * The leading `<seg>` (the first path segment after an optional `{project-root}/`
1880
+ * prefix) is the resolved output folder; if it !== `outputFolder` it is rewritten.
1881
+ * Only single-segment output folders are handled (the bmad default), which covers
1882
+ * the observed `skills` vs `_bmad-output` contamination.
1883
+ */
1884
+ function correctArtifactPath(value, outputFolder) {
1885
+ if (typeof value !== 'string' || !value) return null;
1886
+ const ROOT = '{project-root}/';
1887
+ let prefix = '';
1888
+ let rest = value;
1889
+ if (rest.startsWith(ROOT)) {
1890
+ prefix = ROOT;
1891
+ rest = rest.slice(ROOT.length);
1892
+ }
1893
+ const slash = rest.indexOf('/');
1894
+ if (slash === -1) return null; // no segment/suffix split — leave as-is
1895
+ const seg = rest.slice(0, slash);
1896
+ const suffix = rest.slice(slash); // includes leading '/'
1897
+ if (seg === outputFolder) return null; // already canonical
1898
+ return `${prefix}${outputFolder}${suffix}`;
1899
+ }
1900
+
1901
+ /**
1902
+ * Rewrite contaminated artifact paths inside `_bmad/config.toml`. Returns the
1903
+ * number of lines changed. Uses line-based editing (no TOML dependency) so we
1904
+ * preserve comments, ordering, and formatting exactly.
1905
+ */
1906
+ function normalizeTomlOutputFolders(tomlPath, outputFolder) {
1907
+ if (!fs.existsSync(tomlPath)) return 0;
1908
+ let raw;
1909
+ try {
1910
+ raw = fs.readFileSync(tomlPath, 'utf-8');
1911
+ } catch {
1912
+ return 0;
1913
+ }
1914
+ const usesCrlf = /\r\n/.test(raw);
1915
+ const eol = usesCrlf ? '\r\n' : '\n';
1916
+ const lines = raw.split(/\r?\n/);
1917
+ let changed = 0;
1918
+ let inModuleSection = false;
1919
+
1920
+ for (let i = 0; i < lines.length; i++) {
1921
+ const line = lines[i];
1922
+ const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
1923
+ if (sectionMatch) {
1924
+ const section = sectionMatch[1].trim();
1925
+ inModuleSection = section.startsWith('modules.');
1926
+ continue;
1927
+ }
1928
+ if (!inModuleSection) continue;
1929
+
1930
+ // key = "value" (only quoted string values are artifact paths)
1931
+ const kvMatch = line.match(/^(\s*)([A-Za-z0-9_]+)(\s*=\s*)"([^"]*)"(\s*(?:#.*)?)$/);
1932
+ if (!kvMatch) continue;
1933
+ const [, indent, key, eq, val, trailer] = kvMatch;
1934
+ if (!OUTPUT_FOLDER_ARTIFACT_KEYS.includes(key)) continue;
1935
+ const corrected = correctArtifactPath(val, outputFolder);
1936
+ if (corrected === null) continue;
1937
+ lines[i] = `${indent}${key}${eq}"${corrected}"${trailer}`;
1938
+ changed++;
1939
+ }
1940
+
1941
+ if (changed > 0) {
1942
+ fs.writeFileSync(tomlPath, lines.join(eol), 'utf-8');
1943
+ }
1944
+ return changed;
1945
+ }
1946
+
1947
+ /**
1948
+ * Rewrite contaminated artifact paths inside a per-module `_bmad/<mod>/config.yaml`.
1949
+ * Returns the number of keys changed. Uses js-yaml so we keep schema fidelity.
1950
+ */
1951
+ function normalizeYamlOutputFolders(yamlPath, outputFolder) {
1952
+ if (!fs.existsSync(yamlPath)) return 0;
1953
+ let raw;
1954
+ try {
1955
+ raw = fs.readFileSync(yamlPath, 'utf-8');
1956
+ } catch {
1957
+ return 0;
1958
+ }
1959
+ let doc;
1960
+ try {
1961
+ doc = yaml.parse(raw);
1962
+ } catch {
1963
+ return 0;
1964
+ }
1965
+ if (!doc || typeof doc !== 'object') return 0;
1966
+
1967
+ let changed = 0;
1968
+ for (const key of OUTPUT_FOLDER_ARTIFACT_KEYS) {
1969
+ if (typeof doc[key] !== 'string') continue;
1970
+ const corrected = correctArtifactPath(doc[key], outputFolder);
1971
+ if (corrected === null) continue;
1972
+ doc[key] = corrected;
1973
+ changed++;
1974
+ }
1975
+
1976
+ if (changed > 0) {
1977
+ // Preserve the leading comment header (everything up to the first
1978
+ // non-comment, non-blank line) so the "Generated by BMAD installer"
1979
+ // banner survives the rewrite.
1980
+ const headerLines = [];
1981
+ for (const l of raw.split(/\r?\n/)) {
1982
+ if (l.trim() === '' || l.trimStart().startsWith('#')) {
1983
+ headerLines.push(l);
1984
+ } else {
1985
+ break;
1986
+ }
1987
+ }
1988
+ const header = headerLines.length > 0 ? headerLines.join('\n') + '\n' : '';
1989
+ fs.writeFileSync(yamlPath, header + yaml.stringify(doc), 'utf-8');
1990
+ }
1991
+ return changed;
1992
+ }
1993
+
1994
+ /**
1995
+ * Bug 27.14 — post-install pass that re-points any contaminated module artifact
1996
+ * path at the canonical `[core] output_folder`. Reads the output folder from
1997
+ * `_bmad/config.toml` (the install-owned source of truth), then normalizes both
1998
+ * `_bmad/config.toml` and every `_bmad/<mod>/config.yaml`.
1999
+ *
2000
+ * Idempotent and non-fatal: a second run finds nothing to change, and any I/O
2001
+ * error is logged and swallowed (config normalization must never abort an
2002
+ * otherwise-green install).
2003
+ *
2004
+ * @param {string} projectRoot - Absolute path to the project root.
2005
+ * @returns {number} Total number of artifact paths rewritten (for tests/logging).
2006
+ */
2007
+ function normalizeModuleOutputFolders(projectRoot) {
2008
+ if (typeof projectRoot !== 'string' || !projectRoot) return 0;
2009
+ const tomlPath = path.join(projectRoot, BMAD_DIR, 'config.toml');
2010
+ if (!fs.existsSync(tomlPath)) return 0;
2011
+
2012
+ let tomlRaw;
2013
+ try {
2014
+ tomlRaw = fs.readFileSync(tomlPath, 'utf-8');
2015
+ } catch {
2016
+ return 0;
2017
+ }
2018
+
2019
+ // Extract `[core] output_folder = "..."`. Only the value inside the [core]
2020
+ // section counts; module sections may carry their own output_folder-like keys.
2021
+ let outputFolder = null;
2022
+ let inCore = false;
2023
+ for (const line of tomlRaw.split(/\r?\n/)) {
2024
+ const section = line.match(/^\s*\[([^\]]+)\]\s*$/);
2025
+ if (section) {
2026
+ inCore = section[1].trim() === 'core';
2027
+ continue;
2028
+ }
2029
+ if (!inCore) continue;
2030
+ const kv = line.match(/^\s*output_folder\s*=\s*"([^"]*)"/);
2031
+ if (kv) {
2032
+ outputFolder = kv[1].trim();
2033
+ break;
2034
+ }
2035
+ }
2036
+ if (!outputFolder) return 0; // nothing canonical to normalize against
2037
+
2038
+ let total = 0;
2039
+ try {
2040
+ total += normalizeTomlOutputFolders(tomlPath, outputFolder);
2041
+
2042
+ // Walk each `_bmad/<mod>/config.yaml`.
2043
+ const bmadDir = path.join(projectRoot, BMAD_DIR);
2044
+ let entries = [];
2045
+ try {
2046
+ entries = fs.readdirSync(bmadDir, { withFileTypes: true });
2047
+ } catch {
2048
+ entries = [];
2049
+ }
2050
+ for (const entry of entries) {
2051
+ if (!entry.isDirectory()) continue;
2052
+ const cfg = path.join(bmadDir, entry.name, 'config.yaml');
2053
+ total += normalizeYamlOutputFolders(cfg, outputFolder);
2054
+ }
2055
+ } catch (err) {
2056
+ console.warn(chalk.yellow(` Warning: output-folder normalization skipped: ${err.message}`));
2057
+ return total;
2058
+ }
2059
+
2060
+ if (total > 0) {
2061
+ console.log(chalk.green(
2062
+ ` Normalized ${total} module artifact path(s) to output folder "${outputFolder}/" (bug 27.14 work-around)`
2063
+ ));
2064
+ }
2065
+ return total;
2066
+ }
2067
+
1811
2068
  /**
1812
2069
  * Migration shim: normalize the project layout so that canonical config lives
1813
2070
  * at `_bmad/bmm/config.yaml` (v6.3.0) instead of `_bmad/_config/manifest.yaml`
@@ -2840,6 +3097,8 @@ module.exports = {
2840
3097
  isGeneratedClineWrapper,
2841
3098
  // Story 22.7 — canonical config (_bmad/bmm/config.yaml) helpers
2842
3099
  readCanonicalBmadConfig,
3100
+ normalizeModuleOutputFolders,
3101
+ correctArtifactPath,
2843
3102
  ensureCanonicalConfigLocation,
2844
3103
  CANONICAL_CONFIG_REL,
2845
3104
  LEGACY_MANIFEST_REL,