substrate-ai 0.1.13 → 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 +21 -0
- package/dist/cli/index.js +201 -0
- package/package.json +2 -1
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) {
|
|
@@ -12130,6 +12234,34 @@ function findPackageRoot(startDir) {
|
|
|
12130
12234
|
return startDir;
|
|
12131
12235
|
}
|
|
12132
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
|
+
}
|
|
12133
12265
|
const logger$3 = createLogger("auto-cmd");
|
|
12134
12266
|
/** BMAD baseline token total for full pipeline comparison (analysis+planning+solutioning+implementation) */
|
|
12135
12267
|
const BMAD_BASELINE_TOKENS_FULL = 56800;
|
|
@@ -12328,6 +12460,70 @@ function formatPipelineSummary(run, tokenSummary, decisionsCount, storiesCount,
|
|
|
12328
12460
|
];
|
|
12329
12461
|
return lines.join("\n");
|
|
12330
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
|
+
}
|
|
12331
12527
|
async function runAutoInit(options) {
|
|
12332
12528
|
const { pack: packName, projectRoot, outputFormat, force = false } = options;
|
|
12333
12529
|
const packPath = join(projectRoot, "packs", packName);
|
|
@@ -12335,6 +12531,7 @@ async function runAutoInit(options) {
|
|
|
12335
12531
|
const dbDir = join(dbRoot, ".substrate");
|
|
12336
12532
|
const dbPath = join(dbDir, "substrate.db");
|
|
12337
12533
|
try {
|
|
12534
|
+
await scaffoldBmadFramework(projectRoot, force, outputFormat);
|
|
12338
12535
|
const localManifest = join(packPath, "manifest.yaml");
|
|
12339
12536
|
let scaffolded = false;
|
|
12340
12537
|
if (!existsSync(localManifest) || force) {
|
|
@@ -12501,6 +12698,10 @@ async function runAutoRun(options) {
|
|
|
12501
12698
|
} catch {}
|
|
12502
12699
|
storyKeys = storyKeys.filter((k) => !completedStoryKeys.has(k));
|
|
12503
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
|
+
}
|
|
12504
12705
|
if (storyKeys.length === 0) {
|
|
12505
12706
|
if (outputFormat === "human") process.stdout.write("No pending stories found in decision store.\n");
|
|
12506
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.
|
|
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",
|