substrate-ai 0.1.12 → 0.1.14

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AI Dev Toolkit Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli/index.js CHANGED
@@ -23,6 +23,7 @@ import * as readline$1 from "readline";
23
23
  import * as readline from "readline";
24
24
  import { createInterface as createInterface$1 } from "readline";
25
25
  import { randomUUID as randomUUID$1 } from "crypto";
26
+ import { createRequire } from "node:module";
26
27
 
27
28
  //#region src/cli/utils/formatting.ts
28
29
  /**
@@ -10179,6 +10180,109 @@ function createImplementationOrchestrator(deps) {
10179
10180
  };
10180
10181
  }
10181
10182
 
10183
+ //#endregion
10184
+ //#region src/modules/implementation-orchestrator/story-discovery.ts
10185
+ /**
10186
+ * Extract all story keys (N-M format) from epics.md content.
10187
+ *
10188
+ * Supports three extraction patterns found in real epics.md files:
10189
+ * 1. Explicit key lines: **Story key:** `7-2-human-turn-loop` → extracts "7-2"
10190
+ * 2. Story headings: ### Story 7.2: Human Turn Loop → extracts "7-2"
10191
+ * 3. File path refs: _bmad-output/implementation-artifacts/7-2-human-turn-loop.md → extracts "7-2"
10192
+ *
10193
+ * Keys are deduplicated and sorted numerically (epic number primary, story number secondary).
10194
+ *
10195
+ * @param content - Raw string content of epics.md
10196
+ * @returns Sorted, deduplicated array of story key strings in "N-M" format
10197
+ */
10198
+ function parseStoryKeysFromEpics(content) {
10199
+ if (content.length === 0) return [];
10200
+ const keys = new Set();
10201
+ const explicitKeyPattern = /\*\*Story key:\*\*\s*`?(\d+-\d+)(?:-[^`\s]*)?`?/g;
10202
+ let match;
10203
+ while ((match = explicitKeyPattern.exec(content)) !== null) if (match[1] !== void 0) keys.add(match[1]);
10204
+ const headingPattern = /^###\s+Story\s+(\d+)\.(\d+)/gm;
10205
+ while ((match = headingPattern.exec(content)) !== null) if (match[1] !== void 0 && match[2] !== void 0) keys.add(`${match[1]}-${match[2]}`);
10206
+ const filePathPattern = /_bmad-output\/implementation-artifacts\/(\d+-\d+)-/g;
10207
+ while ((match = filePathPattern.exec(content)) !== null) if (match[1] !== void 0) keys.add(match[1]);
10208
+ return sortStoryKeys(Array.from(keys));
10209
+ }
10210
+ /**
10211
+ * Discover pending story keys by diffing epics.md against existing story files.
10212
+ *
10213
+ * Algorithm:
10214
+ * 1. Read _bmad-output/planning-artifacts/epics.md (falls back to _bmad-output/epics.md)
10215
+ * 2. Extract all story keys from epics.md
10216
+ * 3. Glob _bmad-output/implementation-artifacts/ for N-M-*.md files
10217
+ * 4. Return keys from step 2 that are NOT in step 3 (pending work)
10218
+ *
10219
+ * Returns an empty array (without error) if epics.md does not exist.
10220
+ *
10221
+ * @param projectRoot - Absolute path to the project root directory
10222
+ * @returns Sorted array of pending story keys in "N-M" format
10223
+ */
10224
+ function discoverPendingStoryKeys(projectRoot) {
10225
+ const epicsPath = findEpicsFile(projectRoot);
10226
+ if (epicsPath === void 0) return [];
10227
+ let content;
10228
+ try {
10229
+ content = readFileSync$1(epicsPath, "utf-8");
10230
+ } catch {
10231
+ return [];
10232
+ }
10233
+ const allKeys = parseStoryKeysFromEpics(content);
10234
+ if (allKeys.length === 0) return [];
10235
+ const existingKeys = collectExistingStoryKeys(projectRoot);
10236
+ return allKeys.filter((k) => !existingKeys.has(k));
10237
+ }
10238
+ /**
10239
+ * Find epics.md from known candidate paths relative to projectRoot.
10240
+ */
10241
+ function findEpicsFile(projectRoot) {
10242
+ const candidates = ["_bmad-output/planning-artifacts/epics.md", "_bmad-output/epics.md"];
10243
+ for (const candidate of candidates) {
10244
+ const fullPath = join$1(projectRoot, candidate);
10245
+ if (existsSync$1(fullPath)) return fullPath;
10246
+ }
10247
+ return void 0;
10248
+ }
10249
+ /**
10250
+ * Collect story keys that already have implementation artifact files.
10251
+ * Scans _bmad-output/implementation-artifacts/ for files matching N-M-*.md.
10252
+ */
10253
+ function collectExistingStoryKeys(projectRoot) {
10254
+ const existing = new Set();
10255
+ const artifactsDir = join$1(projectRoot, "_bmad-output", "implementation-artifacts");
10256
+ if (!existsSync$1(artifactsDir)) return existing;
10257
+ let entries;
10258
+ try {
10259
+ entries = readdirSync$1(artifactsDir);
10260
+ } catch {
10261
+ return existing;
10262
+ }
10263
+ const filePattern = /^(\d+-\d+)-/;
10264
+ for (const entry of entries) {
10265
+ const name = typeof entry === "string" ? entry : entry.name;
10266
+ if (!name.endsWith(".md")) continue;
10267
+ const m = filePattern.exec(name);
10268
+ if (m !== null && m[1] !== void 0) existing.add(m[1]);
10269
+ }
10270
+ return existing;
10271
+ }
10272
+ /**
10273
+ * Sort story keys numerically by epic number (primary) then story number (secondary).
10274
+ * E.g. ["10-1", "1-2", "2-1"] → ["1-2", "2-1", "10-1"]
10275
+ */
10276
+ function sortStoryKeys(keys) {
10277
+ return keys.slice().sort((a, b) => {
10278
+ const [ae, as_] = a.split("-").map(Number);
10279
+ const [be, bs] = b.split("-").map(Number);
10280
+ const epicDiff = (ae ?? 0) - (be ?? 0);
10281
+ if (epicDiff !== 0) return epicDiff;
10282
+ return (as_ ?? 0) - (bs ?? 0);
10283
+ });
10284
+ }
10285
+
10182
10286
  //#endregion
10183
10287
  //#region src/modules/phase-orchestrator/built-in-phases.ts
10184
10288
  function logPhase(message) {
@@ -12117,12 +12221,47 @@ Analyze thoroughly and return ONLY the JSON array with no additional text.`;
12117
12221
  const __filename = fileURLToPath$1(import.meta.url);
12118
12222
  const __dirname = dirname(__filename);
12119
12223
  /**
12120
- * Absolute path to the Substrate package root.
12121
- *
12122
- * In source: src/cli/commands/ → 3 levels up = repo root
12123
- * Post-build: dist/cli/commands/ → 3 levels up = dist parent = package root
12224
+ * Find the package root by walking up until we find package.json.
12225
+ * Works regardless of build output structure (tsdown bundles into
12226
+ * dist/cli/index.js, not dist/cli/commands/auto.js).
12124
12227
  */
12228
+ function findPackageRoot(startDir) {
12229
+ let dir = startDir;
12230
+ while (dir !== dirname(dir)) {
12231
+ if (existsSync(join(dir, "package.json"))) return dir;
12232
+ dir = dirname(dir);
12233
+ }
12234
+ return startDir;
12235
+ }
12125
12236
  const PACKAGE_ROOT = join(__dirname, "..", "..", "..");
12237
+ /**
12238
+ * Resolve the absolute path to the bmad-method package's src/ directory.
12239
+ * Uses createRequire so it works in ESM without import.meta.resolve polyfills.
12240
+ * Returns null if bmad-method is not installed.
12241
+ */
12242
+ function resolveBmadMethodSrcPath(fromDir = __dirname) {
12243
+ try {
12244
+ const require = createRequire(join(fromDir, "synthetic.js"));
12245
+ const pkgJsonPath = require.resolve("bmad-method/package.json");
12246
+ return join(dirname(pkgJsonPath), "src");
12247
+ } catch {
12248
+ return null;
12249
+ }
12250
+ }
12251
+ /**
12252
+ * Read the version field from bmad-method's package.json.
12253
+ * Returns 'unknown' if not resolvable.
12254
+ */
12255
+ function resolveBmadMethodVersion(fromDir = __dirname) {
12256
+ try {
12257
+ const require = createRequire(join(fromDir, "synthetic.js"));
12258
+ const pkgJsonPath = require.resolve("bmad-method/package.json");
12259
+ const pkg = require(pkgJsonPath);
12260
+ return pkg.version ?? "unknown";
12261
+ } catch {
12262
+ return "unknown";
12263
+ }
12264
+ }
12126
12265
  const logger$3 = createLogger("auto-cmd");
12127
12266
  /** BMAD baseline token total for full pipeline comparison (analysis+planning+solutioning+implementation) */
12128
12267
  const BMAD_BASELINE_TOKENS_FULL = 56800;
@@ -12321,6 +12460,70 @@ function formatPipelineSummary(run, tokenSummary, decisionsCount, storiesCount,
12321
12460
  ];
12322
12461
  return lines.join("\n");
12323
12462
  }
12463
+ /**
12464
+ * Subdirectories of bmad-method/src/ that constitute the framework.
12465
+ * tea/ is an optional add-on that may or may not be present in the package.
12466
+ */
12467
+ const BMAD_FRAMEWORK_DIRS = [
12468
+ "core",
12469
+ "bmm",
12470
+ "tea"
12471
+ ];
12472
+ /**
12473
+ * Scaffold the BMAD framework from the bmad-method npm dependency into
12474
+ * <projectRoot>/_bmad/.
12475
+ *
12476
+ * Behaviour:
12477
+ * - If _bmad/ already exists and --force is NOT set → skip silently.
12478
+ * - If _bmad/ already exists and --force IS set → warn and replace.
12479
+ * - Copies each of BMAD_FRAMEWORK_DIRS from bmad-method/src/ that exist.
12480
+ * - Generates a minimal _config/config.yaml stub if it doesn't already exist.
12481
+ * - Logs "Scaffolding BMAD framework from bmad-method@<version>".
12482
+ */
12483
+ async function scaffoldBmadFramework(projectRoot, force, outputFormat) {
12484
+ const bmadDest = join(projectRoot, "_bmad");
12485
+ const bmadExists = existsSync(bmadDest);
12486
+ if (bmadExists && !force) return;
12487
+ const bmadSrc = resolveBmadMethodSrcPath();
12488
+ if (!bmadSrc) {
12489
+ if (outputFormat !== "json") process.stderr.write("Warning: bmad-method is not installed. BMAD framework not scaffolded. Run: npm install bmad-method\n");
12490
+ return;
12491
+ }
12492
+ const version = resolveBmadMethodVersion();
12493
+ if (force && bmadExists) process.stderr.write(`Warning: Replacing existing _bmad/ framework with bmad-method@${version}\n`);
12494
+ process.stdout.write(`Scaffolding BMAD framework from bmad-method@${version}\n`);
12495
+ logger$3.info({
12496
+ version,
12497
+ dest: bmadDest
12498
+ }, "Scaffolding BMAD framework");
12499
+ for (const dir of BMAD_FRAMEWORK_DIRS) {
12500
+ const srcDir = join(bmadSrc, dir);
12501
+ if (existsSync(srcDir)) {
12502
+ const destDir = join(bmadDest, dir);
12503
+ mkdirSync(destDir, { recursive: true });
12504
+ cpSync(srcDir, destDir, { recursive: true });
12505
+ logger$3.info({
12506
+ dir,
12507
+ dest: destDir
12508
+ }, "Scaffolded BMAD framework directory");
12509
+ }
12510
+ }
12511
+ const configDir = join(bmadDest, "_config");
12512
+ const configFile = join(configDir, "config.yaml");
12513
+ if (!existsSync(configFile)) {
12514
+ mkdirSync(configDir, { recursive: true });
12515
+ const configStub = [
12516
+ "# BMAD framework configuration",
12517
+ `# Scaffolded from bmad-method@${version} by substrate auto init`,
12518
+ "# This file is project-specific — customize as needed.",
12519
+ "user_name: Human",
12520
+ "communication_language: English",
12521
+ "document_output_language: English"
12522
+ ].join("\n") + "\n";
12523
+ await writeFile(configFile, configStub, "utf8");
12524
+ logger$3.info({ configFile }, "Generated _bmad/_config/config.yaml stub");
12525
+ }
12526
+ }
12324
12527
  async function runAutoInit(options) {
12325
12528
  const { pack: packName, projectRoot, outputFormat, force = false } = options;
12326
12529
  const packPath = join(projectRoot, "packs", packName);
@@ -12328,10 +12531,12 @@ async function runAutoInit(options) {
12328
12531
  const dbDir = join(dbRoot, ".substrate");
12329
12532
  const dbPath = join(dbDir, "substrate.db");
12330
12533
  try {
12534
+ await scaffoldBmadFramework(projectRoot, force, outputFormat);
12331
12535
  const localManifest = join(packPath, "manifest.yaml");
12332
12536
  let scaffolded = false;
12333
12537
  if (!existsSync(localManifest) || force) {
12334
- const bundledPackPath = join(PACKAGE_ROOT, "packs", packName);
12538
+ const packageRoot = findPackageRoot(__dirname);
12539
+ const bundledPackPath = join(packageRoot, "packs", packName);
12335
12540
  if (!existsSync(join(bundledPackPath, "manifest.yaml"))) {
12336
12541
  const errorMsg = `Pack '${packName}' not found locally or in bundled packs. Try reinstalling Substrate.`;
12337
12542
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -12493,6 +12698,10 @@ async function runAutoRun(options) {
12493
12698
  } catch {}
12494
12699
  storyKeys = storyKeys.filter((k) => !completedStoryKeys.has(k));
12495
12700
  }
12701
+ if (storyKeys.length === 0) {
12702
+ storyKeys = discoverPendingStoryKeys(projectRoot);
12703
+ if (storyKeys.length > 0) process.stdout.write(`Discovered ${storyKeys.length} pending stories from epics.md: ${storyKeys.join(", ")}\n`);
12704
+ }
12496
12705
  if (storyKeys.length === 0) {
12497
12706
  if (outputFormat === "human") process.stdout.write("No pending stories found in decision store.\n");
12498
12707
  else process.stdout.write(formatOutput({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -59,6 +59,7 @@
59
59
  "dependencies": {
60
60
  "@types/better-sqlite3": "^7.6.13",
61
61
  "better-sqlite3": "^12.6.2",
62
+ "bmad-method": "^6.0.3",
62
63
  "commander": "^12.1.0",
63
64
  "js-yaml": "^4.1.1",
64
65
  "pino": "^9.6.0",