substrate-ai 0.19.46 → 0.19.48

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +141 -13
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -16,7 +16,7 @@ import { dirname, join, resolve } from "path";
16
16
  import { access, mkdir, readFile, writeFile } from "fs/promises";
17
17
  import yaml from "js-yaml";
18
18
  import { existsSync, readFileSync } from "node:fs";
19
- import { spawn } from "node:child_process";
19
+ import { execFile, spawn } from "node:child_process";
20
20
  import * as path$3 from "node:path";
21
21
  import * as path$2 from "node:path";
22
22
  import * as path$1 from "node:path";
@@ -1182,6 +1182,17 @@ const PartialSubstrateConfigSchema = z.object({
1182
1182
 
1183
1183
  //#endregion
1184
1184
  //#region src/modules/project-profile/detect.ts
1185
+ function execFileAsync(cmd, args, opts) {
1186
+ return new Promise((resolve$2, reject) => {
1187
+ execFile(cmd, args, {
1188
+ ...opts,
1189
+ timeout: 5e3
1190
+ }, (err, stdout) => {
1191
+ if (err) reject(err);
1192
+ else resolve$2(stdout);
1193
+ });
1194
+ });
1195
+ }
1185
1196
  /**
1186
1197
  * Ordered array of build system markers. Detection checks them in priority
1187
1198
  * order — the first matching marker wins at the single-project level.
@@ -1282,6 +1293,101 @@ async function detectNodeBuildTool(dir) {
1282
1293
  installCommand: "npm install <package>"
1283
1294
  };
1284
1295
  }
1296
+ const TASK_RUNNER_MARKERS = [
1297
+ {
1298
+ file: "justfile",
1299
+ runner: "just",
1300
+ listCommand: ["just", "--list"]
1301
+ },
1302
+ {
1303
+ file: "Justfile",
1304
+ runner: "just",
1305
+ listCommand: ["just", "--list"]
1306
+ },
1307
+ {
1308
+ file: "Makefile",
1309
+ runner: "make",
1310
+ listCommand: []
1311
+ },
1312
+ {
1313
+ file: "Taskfile.yml",
1314
+ runner: "task",
1315
+ listCommand: ["task", "--list"]
1316
+ }
1317
+ ];
1318
+ /** Known build-related target names, in preference order. */
1319
+ const BUILD_TARGETS = [
1320
+ "build-skip-tests",
1321
+ "build-no-tests",
1322
+ "compile",
1323
+ "build"
1324
+ ];
1325
+ /** Known unit-test target names, in preference order. */
1326
+ const TEST_TARGETS = [
1327
+ "test-unit",
1328
+ "test-fast",
1329
+ "test"
1330
+ ];
1331
+ /**
1332
+ * Detect a task runner (justfile, Makefile, Taskfile.yml) in the given directory
1333
+ * and extract build/test command overrides from its available targets.
1334
+ *
1335
+ * Returns overrides for buildCommand and testCommand, or null if no task runner found.
1336
+ */
1337
+ async function detectTaskRunner(dir) {
1338
+ for (const marker of TASK_RUNNER_MARKERS) {
1339
+ if (!await fileExists(path$1.join(dir, marker.file))) continue;
1340
+ if (marker.runner === "just") return detectJustTargets(dir, marker.file);
1341
+ if (marker.runner === "make") return detectMakeTargets(dir);
1342
+ return { runner: "task" };
1343
+ }
1344
+ return null;
1345
+ }
1346
+ async function detectJustTargets(dir, _filename) {
1347
+ const result = { runner: "just" };
1348
+ try {
1349
+ const stdout = await execFileAsync("just", ["--summary"], { cwd: dir });
1350
+ const recipes = stdout.trim().split(/\s+/);
1351
+ for (const target of BUILD_TARGETS) if (recipes.includes(target)) {
1352
+ result.buildCommand = `just ${target}`;
1353
+ break;
1354
+ }
1355
+ for (const target of TEST_TARGETS) if (recipes.includes(target)) {
1356
+ result.testCommand = `just ${target}`;
1357
+ break;
1358
+ }
1359
+ } catch {
1360
+ try {
1361
+ const content = await fs.readFile(path$1.join(dir, _filename), "utf-8");
1362
+ const recipes = content.split("\n").map((line) => line.match(/^([a-zA-Z_][\w-]*)(?:\s+[^:]*)?:/)).filter((m) => m !== null).map((m) => m[1]);
1363
+ for (const target of BUILD_TARGETS) if (recipes.includes(target)) {
1364
+ result.buildCommand = `just ${target}`;
1365
+ break;
1366
+ }
1367
+ for (const target of TEST_TARGETS) if (recipes.includes(target)) {
1368
+ result.testCommand = `just ${target}`;
1369
+ break;
1370
+ }
1371
+ } catch {}
1372
+ }
1373
+ return result;
1374
+ }
1375
+ async function detectMakeTargets(dir) {
1376
+ const result = { runner: "make" };
1377
+ try {
1378
+ const content = await fs.readFile(path$1.join(dir, "Makefile"), "utf-8");
1379
+ const targets = content.split("\n").map((line) => line.match(/^([a-zA-Z_][\w-]*):/)).filter((m) => m !== null).map((m) => m[1]);
1380
+ for (const target of BUILD_TARGETS) if (targets.includes(target)) {
1381
+ result.buildCommand = `make ${target}`;
1382
+ break;
1383
+ }
1384
+ for (const target of TEST_TARGETS) if (targets.includes(target)) {
1385
+ result.testCommand = `make ${target}`;
1386
+ break;
1387
+ }
1388
+ } catch {}
1389
+ return result;
1390
+ }
1285
1391
  /**
1286
1392
  * Detects the language and build tool for a single project directory.
1287
1393
  *
@@ -1293,12 +1399,13 @@ async function detectNodeBuildTool(dir) {
1293
1399
  * @returns A `PackageEntry` describing the detected stack.
1294
1400
  */
1295
1401
  async function detectSingleProjectStack(dir) {
1402
+ let baseEntry;
1296
1403
  for (const marker of STACK_MARKERS) {
1297
1404
  const markerPath = path$1.join(dir, marker.file);
1298
1405
  if (!await fileExists(markerPath)) continue;
1299
1406
  if (marker.file === "package.json") {
1300
1407
  const nodeInfo = await detectNodeBuildTool(dir);
1301
- return {
1408
+ baseEntry = {
1302
1409
  path: dir,
1303
1410
  language: "typescript",
1304
1411
  buildTool: nodeInfo.buildTool,
@@ -1306,20 +1413,24 @@ async function detectSingleProjectStack(dir) {
1306
1413
  testCommand: nodeInfo.testCommand,
1307
1414
  installCommand: nodeInfo.installCommand
1308
1415
  };
1416
+ break;
1309
1417
  }
1310
1418
  if (marker.file === "pyproject.toml") {
1311
1419
  const hasPoetry = await fileExists(path$1.join(dir, "poetry.lock"));
1312
- if (hasPoetry) return {
1313
- path: dir,
1314
- language: "python",
1315
- buildTool: "poetry",
1316
- buildCommand: "poetry build",
1317
- testCommand: "poetry run pytest",
1318
- installCommand: "poetry add <package>"
1319
- };
1420
+ if (hasPoetry) {
1421
+ baseEntry = {
1422
+ path: dir,
1423
+ language: "python",
1424
+ buildTool: "poetry",
1425
+ buildCommand: "poetry build",
1426
+ testCommand: "poetry run pytest",
1427
+ installCommand: "poetry add <package>"
1428
+ };
1429
+ break;
1430
+ }
1320
1431
  const hasVenv = await fileExists(path$1.join(dir, ".venv", "bin", "activate"));
1321
1432
  const venvPrefix = hasVenv ? "source .venv/bin/activate && " : "";
1322
- return {
1433
+ baseEntry = {
1323
1434
  path: dir,
1324
1435
  language: "python",
1325
1436
  buildTool: "pip",
@@ -1327,8 +1438,9 @@ async function detectSingleProjectStack(dir) {
1327
1438
  testCommand: `${venvPrefix}pytest`,
1328
1439
  installCommand: `${venvPrefix}pip install <package>`
1329
1440
  };
1441
+ break;
1330
1442
  }
1331
- return {
1443
+ baseEntry = {
1332
1444
  path: dir,
1333
1445
  language: marker.language,
1334
1446
  buildTool: marker.buildTool,
@@ -1336,8 +1448,9 @@ async function detectSingleProjectStack(dir) {
1336
1448
  testCommand: marker.testCommand,
1337
1449
  installCommand: marker.installCommand
1338
1450
  };
1451
+ break;
1339
1452
  }
1340
- return {
1453
+ if (!baseEntry) baseEntry = {
1341
1454
  path: dir,
1342
1455
  language: "typescript",
1343
1456
  buildTool: "npm",
@@ -1345,6 +1458,21 @@ async function detectSingleProjectStack(dir) {
1345
1458
  testCommand: "npm test",
1346
1459
  installCommand: "npm install <package>"
1347
1460
  };
1461
+ return applyTaskRunnerOverlay(dir, baseEntry);
1462
+ }
1463
+ /**
1464
+ * Apply task runner overlay to a detected PackageEntry.
1465
+ * If a justfile/Makefile/Taskfile.yml exists with matching targets,
1466
+ * override the buildCommand and testCommand with task runner commands.
1467
+ */
1468
+ async function applyTaskRunnerOverlay(dir, entry) {
1469
+ const runner = await detectTaskRunner(dir);
1470
+ if (!runner) return entry;
1471
+ return {
1472
+ ...entry,
1473
+ ...runner.buildCommand && { buildCommand: runner.buildCommand },
1474
+ ...runner.testCommand && { testCommand: runner.testCommand }
1475
+ };
1348
1476
  }
1349
1477
  /**
1350
1478
  * Detects if the project root is a Turborepo monorepo.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.19.46",
3
+ "version": "0.19.48",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",