@gobing-ai/spur 0.3.26 → 0.3.27
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/config/rules/recommended-pre-check.yaml +1 -0
- package/config/rules/strict/runtime-boundaries.yaml +23 -16
- package/config/rules/ui/ui-import-boundary.yaml +7 -4
- package/config/tasks/section-matrix.yaml +7 -7
- package/config/workflows/task-pipeline.yaml +9 -0
- package/package.json +1 -1
- package/spur.js +2007 -469
- package/web/_astro/BoardApp.Bk3T0o6s.js +1 -0
- package/web/_astro/BoardApp.BwAdAgK9.js +179 -0
- package/web/_astro/{TaskDetail.Dsk9DnGY.js → TaskDetail.L36slEqE.js} +1 -1
- package/web/_astro/{arc.Biv8k-OR.js → arc.CO-I_PZe.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.BrtW5Sn7.js → architectureDiagram-3BPJPVTR.Si-A1Ncl.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.CTpFZJhW.js → blockDiagram-GPEHLZMM.CeNy_ta6.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU._ZJj9H0G.js → c4Diagram-AAUBKEIU.BgN_scEU.js} +1 -1
- package/web/_astro/channel.CYdGnFHM.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.DD9mDiEM.js → chunk-2J33WTMH.C3oDtmdi.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.ENDQu3TH.js → chunk-4BX2VUAB.Bv8Fxh4H.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DLpJf8Xp.js → chunk-55IACEB6.BT-TqtV6.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BR6vsvIV.js → chunk-727SXJPM.DkGMsI0B.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DGOBzXM6.js → chunk-AQP2D5EJ.B_-SqGnF.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DrghnRtZ.js → chunk-FMBD7UC4.DwR3skMv.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BqIqI3oO.js → chunk-ND2GUHAM.BaTQutFl.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.CJ2RJt0K.js → chunk-QZHKN3VN.BJMSqqJp.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.Qu1p6crX.js → classDiagram-4FO5ZUOK.SaqCNUPF.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.Qu1p6crX.js → classDiagram-v2-Q7XG4LA2.SaqCNUPF.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.7VrRetJ0.js → cose-bilkent-S5V4N54A.BkmjA_E2.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.r4lTyJXQ.js → dagre-BM42HDAG.CNUvWhrX.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DahExuaR.js → diagram-2AECGRRQ.C68Cg9uE.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.B-dSm3op.js → diagram-5GNKFQAL.DbHkPLEn.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CQGlKa6a.js → diagram-KO2AKTUF.Dt0EdErs.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.CJ6nomD-.js → diagram-LMA3HP47.Bixs6mnm.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.ijGl68sM.js → diagram-OG6HWLK6.B4KBI6KT.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DBAe2Iin.js → erDiagram-TEJ5UH35.C_4i9Xvx.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.z3iXF6Ku.js → flowDiagram-I6XJVG4X.CfGFczAY.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.DP2ouiRt.js → ganttDiagram-6RSMTGT7.C2AIvtmg.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.DizVfgVG.js → gitGraphDiagram-PVQCEYII.HlXuGqQg.js} +1 -1
- package/web/_astro/index.D5Wqk2Tw.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.BPTTgL0f.js → infoDiagram-5YYISTIA.BWQJsJ4Z.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.CXdMa4-b.js → ishikawaDiagram-YF4QCWOH.CCHgIHik.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.ChSKZ-1j.js → journeyDiagram-JHISSGLW.D9M6YwOJ.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DUUlW-km.js → kanban-definition-UN3LZRKU.K20D-a1_.js} +1 -1
- package/web/_astro/{linear.AzHrSiEQ.js → linear.DeP5Qsol.js} +1 -1
- package/web/_astro/{mermaid.core.BajX78G7.js → mermaid.core.ClzxNMPU.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BmdUHcac.js → mindmap-definition-RKZ34NQL.CGn9RUNj.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.Dh59Ur2v.js → pieDiagram-4H26LBE5.CoA92Csb.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.D2qYQmFj.js → quadrantDiagram-W4KKPZXB.CJ_UPjM1.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DW5nnrHl.js → requirementDiagram-4Y6WPE33.KkEPNNUU.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.CMl1PJrk.js → sankeyDiagram-5OEKKPKP.CoCWbQgh.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.U5dq1PA0.js → sequenceDiagram-3UESZ5HK.DUFpV3EP.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.zrt55DVx.js → stateDiagram-AJRCARHV.CgWX0_9m.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CKBGQ0dG.js → stateDiagram-v2-BHNVJYJU.BKUXeGSf.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.CKAlx9rU.js → timeline-definition-PNZ67QCA.CXayAguY.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.Bgt1eF06.js → vennDiagram-CIIHVFJN.DPak_Elh.js} +1 -1
- package/web/_astro/{wardley-L42UT6IY.BF02aioC.js → wardley-L42UT6IY.t3ZlDKd0.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.C94wxcnJ.js → wardleyDiagram-YWT4CUSO.CwPSVASQ.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.BwK7SRoW.js → xychartDiagram-2RQKCTM6.CJfrQgpF.js} +1 -1
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.DH9wI8y7.js +0 -179
- package/web/_astro/BoardApp.DZiwQCP7.js +0 -1
- package/web/_astro/channel.Ih1_8SAH.js +0 -1
- package/web/_astro/index.D3Y4Q0mx.css +0 -1
package/spur.js
CHANGED
|
@@ -37197,6 +37197,29 @@ var init_finding_codes = __esm(() => {
|
|
|
37197
37197
|
};
|
|
37198
37198
|
});
|
|
37199
37199
|
|
|
37200
|
+
// ../../packages/config/src/projects.ts
|
|
37201
|
+
import { homedir } from "os";
|
|
37202
|
+
import { join } from "path";
|
|
37203
|
+
function getProjectsFilePath() {
|
|
37204
|
+
if (process.env.SPUR_PROJECTS_FILE) {
|
|
37205
|
+
return process.env.SPUR_PROJECTS_FILE;
|
|
37206
|
+
}
|
|
37207
|
+
return join(homedir(), ".config", "spur", "projects.json");
|
|
37208
|
+
}
|
|
37209
|
+
var projectEntrySchema, projectsFileSchema;
|
|
37210
|
+
var init_projects = __esm(() => {
|
|
37211
|
+
init_zod();
|
|
37212
|
+
projectEntrySchema = exports_external.object({
|
|
37213
|
+
name: exports_external.string().min(1),
|
|
37214
|
+
path: exports_external.string().min(1),
|
|
37215
|
+
port: exports_external.number().int().nonnegative().default(0)
|
|
37216
|
+
});
|
|
37217
|
+
projectsFileSchema = exports_external.object({
|
|
37218
|
+
schema_version: exports_external.literal(1).default(1),
|
|
37219
|
+
projects: exports_external.array(projectEntrySchema).default([])
|
|
37220
|
+
});
|
|
37221
|
+
});
|
|
37222
|
+
|
|
37200
37223
|
// ../../packages/config/src/index.ts
|
|
37201
37224
|
function folderPathWithDefault(defaultPath) {
|
|
37202
37225
|
return exports_external.preprocess((v) => v == null ? undefined : v, exports_external.string().default(defaultPath));
|
|
@@ -37248,6 +37271,7 @@ var SPUR_ENV_VARS, SPUR_LOG_LEVELS, DEFAULT_TASKS_DIR = "docs/tasks", DEFAULT_FE
|
|
|
37248
37271
|
var init_src = __esm(() => {
|
|
37249
37272
|
init_zod();
|
|
37250
37273
|
init_finding_codes();
|
|
37274
|
+
init_projects();
|
|
37251
37275
|
SPUR_ENV_VARS = {
|
|
37252
37276
|
nodeEnv: "NODE_ENV",
|
|
37253
37277
|
port: "PORT",
|
|
@@ -38938,14 +38962,14 @@ var init_dist5 = __esm(() => {
|
|
|
38938
38962
|
|
|
38939
38963
|
// ../../packages/config/src/bundled-config.ts
|
|
38940
38964
|
import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
38941
|
-
import { dirname as dirname3, join } from "path";
|
|
38965
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
38942
38966
|
function bundledConfigRoot() {
|
|
38943
38967
|
if (cachedRoot !== undefined)
|
|
38944
38968
|
return cachedRoot;
|
|
38945
38969
|
let dir = import.meta.dirname;
|
|
38946
38970
|
while (true) {
|
|
38947
38971
|
for (const name of BUNDLED_CONFIG_DIRS) {
|
|
38948
|
-
const candidate =
|
|
38972
|
+
const candidate = join2(dir, name);
|
|
38949
38973
|
if (isBundledConfigDir(candidate)) {
|
|
38950
38974
|
cachedRoot = candidate;
|
|
38951
38975
|
return cachedRoot;
|
|
@@ -38960,7 +38984,7 @@ function bundledConfigRoot() {
|
|
|
38960
38984
|
return cachedRoot;
|
|
38961
38985
|
}
|
|
38962
38986
|
function isBundledConfigDir(candidate) {
|
|
38963
|
-
return existsSync3(candidate) && statSync4(candidate).isDirectory() && existsSync3(
|
|
38987
|
+
return existsSync3(candidate) && statSync4(candidate).isDirectory() && existsSync3(join2(candidate, "rules")) && existsSync3(join2(candidate, "workflows"));
|
|
38964
38988
|
}
|
|
38965
38989
|
function listBundledConfigFiles() {
|
|
38966
38990
|
const root = bundledConfigRoot();
|
|
@@ -38971,7 +38995,7 @@ function listBundledConfigFiles() {
|
|
|
38971
38995
|
function walk(dir, relPrefix, filter = /\.(ya?ml|json)$/i) {
|
|
38972
38996
|
const acc = [];
|
|
38973
38997
|
for (const entry of readdirSync3(dir)) {
|
|
38974
|
-
const abs =
|
|
38998
|
+
const abs = join2(dir, entry);
|
|
38975
38999
|
const rel = relPrefix.length > 0 ? `${relPrefix}/${entry}` : entry;
|
|
38976
39000
|
if (statSync4(abs).isDirectory()) {
|
|
38977
39001
|
acc.push(...walk(abs, rel, filter));
|
|
@@ -38998,8 +39022,8 @@ var init_template_renderer = () => {};
|
|
|
38998
39022
|
|
|
38999
39023
|
// ../../packages/config/src/loader.ts
|
|
39000
39024
|
import { existsSync as existsSync4, statSync as statSync5 } from "fs";
|
|
39001
|
-
import { homedir } from "os";
|
|
39002
|
-
import { dirname as dirname4, join as
|
|
39025
|
+
import { homedir as homedir2 } from "os";
|
|
39026
|
+
import { dirname as dirname4, join as join3 } from "path";
|
|
39003
39027
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
39004
39028
|
function embeddedSchemasId(embeddedSchemas) {
|
|
39005
39029
|
if (embeddedSchemas === undefined)
|
|
@@ -39022,7 +39046,7 @@ function cacheKey(configPath, opts, validateJsonSchema2) {
|
|
|
39022
39046
|
}
|
|
39023
39047
|
function resolveSchemaSpecifier(specifier, manifestSpecifier) {
|
|
39024
39048
|
if (specifier === manifestSpecifier) {
|
|
39025
|
-
const workspaceManifest =
|
|
39049
|
+
const workspaceManifest = join3(LOADER_DIR, "..", "..", "..", "apps", "cli", "package.json");
|
|
39026
39050
|
if (existsSync4(workspaceManifest))
|
|
39027
39051
|
return workspaceManifest;
|
|
39028
39052
|
}
|
|
@@ -39034,7 +39058,7 @@ function resolveSchemaSpecifier(specifier, manifestSpecifier) {
|
|
|
39034
39058
|
}
|
|
39035
39059
|
}
|
|
39036
39060
|
function resolveConfigFile(cwd) {
|
|
39037
|
-
const projectConfig =
|
|
39061
|
+
const projectConfig = join3(cwd ?? process.cwd(), SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
|
|
39038
39062
|
if (existsSync4(projectConfig))
|
|
39039
39063
|
return projectConfig;
|
|
39040
39064
|
if (process.env.SPUR_SKIP_GLOBAL_CONFIG === "true")
|
|
@@ -39074,9 +39098,9 @@ async function loadSpurConfig(cwd = process.cwd(), opts) {
|
|
|
39074
39098
|
}
|
|
39075
39099
|
function expandTilde(path8) {
|
|
39076
39100
|
if (path8 === "~")
|
|
39077
|
-
return
|
|
39101
|
+
return homedir2();
|
|
39078
39102
|
if (path8.startsWith("~/"))
|
|
39079
|
-
return
|
|
39103
|
+
return join3(homedir2(), path8.slice(2));
|
|
39080
39104
|
return path8;
|
|
39081
39105
|
}
|
|
39082
39106
|
function expandTeamTildes(config4) {
|
|
@@ -39163,7 +39187,7 @@ async function resolvePlanningFolders(fs2) {
|
|
|
39163
39187
|
return promise2;
|
|
39164
39188
|
}
|
|
39165
39189
|
async function resolvePlanningFoldersUncached(fs2) {
|
|
39166
|
-
const configPath = fs2.resolve(
|
|
39190
|
+
const configPath = fs2.resolve(join3(SPUR_CONFIG_DIR, SPUR_CONFIG_FILE));
|
|
39167
39191
|
if (!await fs2.exists(configPath))
|
|
39168
39192
|
return defaultPlanningFolders();
|
|
39169
39193
|
try {
|
|
@@ -39200,7 +39224,8 @@ var init_loader = __esm(() => {
|
|
|
39200
39224
|
init_src();
|
|
39201
39225
|
init_bundled_config();
|
|
39202
39226
|
init_template_renderer();
|
|
39203
|
-
|
|
39227
|
+
init_projects();
|
|
39228
|
+
GLOBAL_CONFIG_FILE = join3(homedir2(), ".config", "spur", "config.yaml");
|
|
39204
39229
|
LOADER_DIR = dirname4(fileURLToPath3(import.meta.url));
|
|
39205
39230
|
embeddedSchemasIds = new WeakMap;
|
|
39206
39231
|
spurConfigCache = new Map;
|
|
@@ -51376,16 +51401,21 @@ var init_run_dao = __esm(() => {
|
|
|
51376
51401
|
});
|
|
51377
51402
|
}
|
|
51378
51403
|
traceRows(filter) {
|
|
51379
|
-
return this.adapter.queryAll(`SELECT id, workflow_name, mode, status, started_at, completed_at, metadata_json
|
|
51404
|
+
return this.adapter.queryAll(`SELECT id, workflow_name, mode, status, agent, started_at, completed_at, metadata_json
|
|
51380
51405
|
FROM runs
|
|
51381
51406
|
WHERE (?1 IS NULL OR workflow_name = ?1)
|
|
51382
51407
|
AND (?2 IS NULL OR status = ?2)
|
|
51383
51408
|
AND (?3 IS NULL OR started_at >= ?3)
|
|
51384
|
-
|
|
51385
|
-
|
|
51409
|
+
AND (
|
|
51410
|
+
?4 IS NULL
|
|
51411
|
+
OR started_at < ?4
|
|
51412
|
+
OR (started_at = ?4 AND id < ?5)
|
|
51413
|
+
)
|
|
51414
|
+
ORDER BY started_at DESC, id DESC
|
|
51415
|
+
LIMIT ?6`, filter.workflow ?? null, filter.status ?? null, filter.since ?? null, filter.before?.started_at ?? null, filter.before?.id ?? null, filter.limit);
|
|
51386
51416
|
}
|
|
51387
51417
|
traceRowById(runId) {
|
|
51388
|
-
return this.adapter.queryFirst("SELECT id, workflow_name, mode, status, started_at, completed_at, metadata_json FROM runs WHERE id = ?", runId);
|
|
51418
|
+
return this.adapter.queryFirst("SELECT id, workflow_name, mode, status, agent, started_at, completed_at, metadata_json FROM runs WHERE id = ?", runId);
|
|
51389
51419
|
}
|
|
51390
51420
|
stampMetadata(runId, metadata) {
|
|
51391
51421
|
return this.adapter.run("UPDATE runs SET metadata_json = ? WHERE id = ?", JSON.stringify(metadata), runId);
|
|
@@ -51430,51 +51460,85 @@ class SystemEventDao {
|
|
|
51430
51460
|
this.db = db2;
|
|
51431
51461
|
}
|
|
51432
51462
|
async insert(input) {
|
|
51433
|
-
await this.db.run(`INSERT INTO system_events (
|
|
51434
|
-
VALUES (
|
|
51435
|
-
}
|
|
51436
|
-
async
|
|
51437
|
-
|
|
51438
|
-
|
|
51439
|
-
|
|
51440
|
-
|
|
51441
|
-
|
|
51442
|
-
|
|
51443
|
-
|
|
51444
|
-
|
|
51445
|
-
|
|
51446
|
-
|
|
51447
|
-
|
|
51448
|
-
|
|
51463
|
+
await this.db.run(`INSERT INTO system_events (${SYSTEM_EVENT_COLUMNS})
|
|
51464
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)`, input.id, input.event_name, input.occurred_at, input.actor ?? null, input.payload_json ?? null, input.run_id ?? null, input.entity_kind ?? null, input.entity_id ?? null, input.sequence ?? null);
|
|
51465
|
+
}
|
|
51466
|
+
async pruneQuotas(quotas, prefix) {
|
|
51467
|
+
try {
|
|
51468
|
+
const entries = prefix !== undefined ? quotas.filter((q) => q.prefix === prefix) : quotas;
|
|
51469
|
+
let deleted = 0;
|
|
51470
|
+
for (const { prefix: p, quota } of entries) {
|
|
51471
|
+
const pattern = `${p}.%`;
|
|
51472
|
+
const before = await this.db.queryFirst("SELECT COUNT(*) AS c FROM system_events WHERE event_name LIKE ?1", pattern);
|
|
51473
|
+
await this.db.run(`DELETE FROM system_events
|
|
51474
|
+
WHERE event_name LIKE ?1
|
|
51475
|
+
AND id NOT IN (
|
|
51476
|
+
SELECT id FROM system_events
|
|
51477
|
+
WHERE event_name LIKE ?1
|
|
51478
|
+
ORDER BY occurred_at DESC
|
|
51479
|
+
LIMIT ?2
|
|
51480
|
+
)`, pattern, quota);
|
|
51481
|
+
const after = await this.db.queryFirst("SELECT COUNT(*) AS c FROM system_events WHERE event_name LIKE ?1", pattern);
|
|
51482
|
+
deleted += (before?.c ?? 0) - (after?.c ?? 0);
|
|
51483
|
+
}
|
|
51484
|
+
return deleted;
|
|
51485
|
+
} catch (error51) {
|
|
51486
|
+
if (error51 instanceof Error && error51.message.includes("no such table: system_events")) {
|
|
51487
|
+
return 0;
|
|
51488
|
+
}
|
|
51489
|
+
throw error51;
|
|
51490
|
+
}
|
|
51449
51491
|
}
|
|
51450
51492
|
async query(spec = {}) {
|
|
51451
51493
|
const limit = spec.limit ?? 100;
|
|
51494
|
+
const clauses = [];
|
|
51495
|
+
const params = [];
|
|
51496
|
+
const pushEq = (column2, value) => {
|
|
51497
|
+
params.push(value);
|
|
51498
|
+
clauses.push(`${column2} = ?${params.length}`);
|
|
51499
|
+
};
|
|
51500
|
+
if (spec.name !== undefined)
|
|
51501
|
+
pushEq("event_name", spec.name);
|
|
51502
|
+
if (spec.names !== undefined && spec.names.length > 0) {
|
|
51503
|
+
const placeholders = [];
|
|
51504
|
+
for (const name of spec.names) {
|
|
51505
|
+
params.push(name);
|
|
51506
|
+
placeholders.push(`?${params.length}`);
|
|
51507
|
+
}
|
|
51508
|
+
clauses.push(`event_name IN (${placeholders.join(", ")})`);
|
|
51509
|
+
}
|
|
51510
|
+
if (spec.prefix !== undefined) {
|
|
51511
|
+
const escaped = spec.prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
|
51512
|
+
params.push(`${escaped}.%`);
|
|
51513
|
+
clauses.push(`event_name LIKE ?${params.length} ESCAPE '\\'`);
|
|
51514
|
+
}
|
|
51515
|
+
if (spec.since !== undefined) {
|
|
51516
|
+
params.push(spec.since);
|
|
51517
|
+
clauses.push(`occurred_at > ?${params.length}`);
|
|
51518
|
+
}
|
|
51519
|
+
if (spec.run_id !== undefined)
|
|
51520
|
+
pushEq("run_id", spec.run_id);
|
|
51521
|
+
if (spec.entity_kind !== undefined)
|
|
51522
|
+
pushEq("entity_kind", spec.entity_kind);
|
|
51523
|
+
if (spec.entity_id !== undefined)
|
|
51524
|
+
pushEq("entity_id", spec.entity_id);
|
|
51525
|
+
if (spec.actor !== undefined)
|
|
51526
|
+
pushEq("actor", spec.actor);
|
|
51527
|
+
if (spec.before !== undefined) {
|
|
51528
|
+
params.push(spec.before.occurred_at);
|
|
51529
|
+
const atIdx = params.length;
|
|
51530
|
+
params.push(spec.before.id);
|
|
51531
|
+
const idIdx = params.length;
|
|
51532
|
+
clauses.push(`(occurred_at < ?${atIdx} OR (occurred_at = ?${atIdx} AND id < ?${idIdx}))`);
|
|
51533
|
+
}
|
|
51534
|
+
const where = clauses.length === 0 ? "" : `WHERE ${clauses.join(" AND ")}`;
|
|
51535
|
+
params.push(limit);
|
|
51452
51536
|
try {
|
|
51453
|
-
|
|
51454
|
-
return await this.db.queryAll(`SELECT id, event_name, occurred_at, actor, payload_json
|
|
51455
|
-
FROM system_events
|
|
51456
|
-
WHERE event_name = ?1 AND occurred_at > ?2
|
|
51457
|
-
ORDER BY occurred_at DESC
|
|
51458
|
-
LIMIT ?3`, spec.name, spec.since, limit);
|
|
51459
|
-
}
|
|
51460
|
-
if (spec.name !== undefined) {
|
|
51461
|
-
return await this.db.queryAll(`SELECT id, event_name, occurred_at, actor, payload_json
|
|
51462
|
-
FROM system_events
|
|
51463
|
-
WHERE event_name = ?1
|
|
51464
|
-
ORDER BY occurred_at DESC
|
|
51465
|
-
LIMIT ?2`, spec.name, limit);
|
|
51466
|
-
}
|
|
51467
|
-
if (spec.since !== undefined) {
|
|
51468
|
-
return await this.db.queryAll(`SELECT id, event_name, occurred_at, actor, payload_json
|
|
51469
|
-
FROM system_events
|
|
51470
|
-
WHERE occurred_at > ?1
|
|
51471
|
-
ORDER BY occurred_at DESC
|
|
51472
|
-
LIMIT ?2`, spec.since, limit);
|
|
51473
|
-
}
|
|
51474
|
-
return await this.db.queryAll(`SELECT id, event_name, occurred_at, actor, payload_json
|
|
51537
|
+
return await this.db.queryAll(`SELECT ${SYSTEM_EVENT_COLUMNS}
|
|
51475
51538
|
FROM system_events
|
|
51476
|
-
|
|
51477
|
-
|
|
51539
|
+
${where}
|
|
51540
|
+
ORDER BY occurred_at DESC, id DESC
|
|
51541
|
+
LIMIT ?${params.length}`, ...params);
|
|
51478
51542
|
} catch (error51) {
|
|
51479
51543
|
if (error51 instanceof Error && error51.message.includes("no such table: system_events")) {
|
|
51480
51544
|
return [];
|
|
@@ -51486,6 +51550,7 @@ class SystemEventDao {
|
|
|
51486
51550
|
await this.db.run("DELETE FROM system_events");
|
|
51487
51551
|
}
|
|
51488
51552
|
}
|
|
51553
|
+
var SYSTEM_EVENT_COLUMNS = "id, event_name, occurred_at, actor, payload_json, run_id, entity_kind, entity_id, sequence";
|
|
51489
51554
|
|
|
51490
51555
|
// ../../packages/domain/src/dao/task-run-link-dao.ts
|
|
51491
51556
|
class TaskRunLinkDao {
|
|
@@ -57586,8 +57651,7 @@ var init_schema3 = __esm(() => {
|
|
|
57586
57651
|
});
|
|
57587
57652
|
|
|
57588
57653
|
// ../../packages/domain/src/migrations.ts
|
|
57589
|
-
import {
|
|
57590
|
-
import { join as join4 } from "path";
|
|
57654
|
+
import { join as join5 } from "path";
|
|
57591
57655
|
async function applyCliMigrations(adapter, migrations = CLI_MIGRATIONS) {
|
|
57592
57656
|
await adapter.exec('CREATE TABLE IF NOT EXISTS "__spur_cli_migrations" (id TEXT PRIMARY KEY, applied_at INTEGER NOT NULL)');
|
|
57593
57657
|
let applied = 0;
|
|
@@ -57608,12 +57672,14 @@ async function applyCliMigrations(adapter, migrations = CLI_MIGRATIONS) {
|
|
|
57608
57672
|
return applied;
|
|
57609
57673
|
}
|
|
57610
57674
|
async function loadSqlMigrations(folder) {
|
|
57611
|
-
const
|
|
57675
|
+
const fs3 = createNodeFileSystem3();
|
|
57676
|
+
const rawEntries = await fs3.readDir(folder);
|
|
57677
|
+
const entries = rawEntries.filter((entry) => entry.endsWith(".sql") && entry.includes(CLI_MIGRATION_FILE_MARKER)).sort((left, right) => left.localeCompare(right));
|
|
57612
57678
|
const migrations = [];
|
|
57613
57679
|
for (const entry of entries) {
|
|
57614
57680
|
migrations.push({
|
|
57615
57681
|
id: entry.replace(/\.sql$/, ""),
|
|
57616
|
-
sql: await
|
|
57682
|
+
sql: await fs3.readFile(join5(folder, entry))
|
|
57617
57683
|
});
|
|
57618
57684
|
}
|
|
57619
57685
|
return migrations.length > 0 ? migrations : CLI_MIGRATIONS;
|
|
@@ -57664,20 +57730,35 @@ CREATE TABLE IF NOT EXISTS system_events (
|
|
|
57664
57730
|
event_name TEXT NOT NULL,
|
|
57665
57731
|
occurred_at TEXT NOT NULL,
|
|
57666
57732
|
actor TEXT,
|
|
57667
|
-
payload_json TEXT
|
|
57733
|
+
payload_json TEXT,
|
|
57734
|
+
run_id TEXT,
|
|
57735
|
+
entity_kind TEXT,
|
|
57736
|
+
entity_id TEXT,
|
|
57737
|
+
sequence INTEGER
|
|
57668
57738
|
);
|
|
57669
57739
|
|
|
57670
57740
|
CREATE INDEX IF NOT EXISTS idx_system_events_occurred_at ON system_events (occurred_at);
|
|
57671
57741
|
CREATE INDEX IF NOT EXISTS idx_system_events_event_name ON system_events (event_name);
|
|
57742
|
+
CREATE INDEX IF NOT EXISTS idx_system_events_run_id ON system_events (run_id);
|
|
57743
|
+
CREATE INDEX IF NOT EXISTS idx_system_events_entity ON system_events (entity_kind, entity_id);
|
|
57672
57744
|
`, CLI_SCHEMA_SQL, RUN_PID_COLUMN_SCHEMA_SQL = `
|
|
57673
57745
|
ALTER TABLE runs ADD COLUMN pid INTEGER;
|
|
57674
57746
|
`, RUNS_EXTERNAL_KEY_COLUMN_SCHEMA_SQL = `
|
|
57675
57747
|
ALTER TABLE runs ADD COLUMN external_key TEXT;
|
|
57748
|
+
`, SYSTEM_EVENTS_CORRELATION_COLUMNS_SCHEMA_SQL = `
|
|
57749
|
+
ALTER TABLE system_events ADD COLUMN run_id TEXT;
|
|
57750
|
+
ALTER TABLE system_events ADD COLUMN entity_kind TEXT;
|
|
57751
|
+
ALTER TABLE system_events ADD COLUMN entity_id TEXT;
|
|
57752
|
+
ALTER TABLE system_events ADD COLUMN sequence INTEGER;
|
|
57753
|
+
|
|
57754
|
+
CREATE INDEX IF NOT EXISTS idx_system_events_run_id ON system_events (run_id);
|
|
57755
|
+
CREATE INDEX IF NOT EXISTS idx_system_events_entity ON system_events (entity_kind, entity_id);
|
|
57676
57756
|
`, CLI_MIGRATIONS, CLI_MIGRATION_FILE_MARKER = "_spur_cli_";
|
|
57677
57757
|
var init_migrations = __esm(() => {
|
|
57678
57758
|
init_dist7();
|
|
57679
57759
|
init_dist8();
|
|
57680
57760
|
init_dist10();
|
|
57761
|
+
init_dist5();
|
|
57681
57762
|
init_schema3();
|
|
57682
57763
|
CLI_SCHEMA_SQL = `
|
|
57683
57764
|
${DOMAIN_SCHEMA_SQL}
|
|
@@ -57712,6 +57793,11 @@ ${SYSTEM_EVENTS_SCHEMA_SQL}
|
|
|
57712
57793
|
id: "0007_spur_cli_runs_external_key",
|
|
57713
57794
|
sql: RUNS_EXTERNAL_KEY_COLUMN_SCHEMA_SQL,
|
|
57714
57795
|
addColumnIfMissing: { table: "runs", column: "external_key" }
|
|
57796
|
+
},
|
|
57797
|
+
{
|
|
57798
|
+
id: "0008_spur_cli_system_events_correlation",
|
|
57799
|
+
sql: SYSTEM_EVENTS_CORRELATION_COLUMNS_SCHEMA_SQL,
|
|
57800
|
+
addColumnIfMissing: { table: "system_events", column: "sequence" }
|
|
57715
57801
|
}
|
|
57716
57802
|
];
|
|
57717
57803
|
});
|
|
@@ -58895,12 +58981,12 @@ var init_envelope = __esm(() => {
|
|
|
58895
58981
|
|
|
58896
58982
|
// ../../packages/domain/src/planning/locks.ts
|
|
58897
58983
|
import { closeSync, fsyncSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync7, renameSync as renameSync3, unlinkSync, writeSync } from "fs";
|
|
58898
|
-
import { dirname as dirname7, join as
|
|
58984
|
+
import { dirname as dirname7, join as join6 } from "path";
|
|
58899
58985
|
function lockPathForEntity(folder, entityId) {
|
|
58900
|
-
return
|
|
58986
|
+
return join6(folder, `.${entityId}.lock`);
|
|
58901
58987
|
}
|
|
58902
58988
|
function createLockPath(folder) {
|
|
58903
|
-
return
|
|
58989
|
+
return join6(folder, ".create.lock");
|
|
58904
58990
|
}
|
|
58905
58991
|
function parseLockContent(raw) {
|
|
58906
58992
|
const str = typeof raw === "string" ? raw : raw.toString();
|
|
@@ -59005,7 +59091,7 @@ function fsyncPath(path9, swallowErrors = false) {
|
|
|
59005
59091
|
function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
|
|
59006
59092
|
const dir = dirname7(dest);
|
|
59007
59093
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
59008
|
-
const tempPath =
|
|
59094
|
+
const tempPath = join6(dir, `.${projectName}.${entityId}.${rand}.tmp`);
|
|
59009
59095
|
fs3.writeFile(tempPath, content);
|
|
59010
59096
|
fsyncPath(tempPath);
|
|
59011
59097
|
renameSync3(tempPath, dest);
|
|
@@ -59014,7 +59100,7 @@ function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
|
|
|
59014
59100
|
async function atomicWriteAsync(dest, content, entityId, fs3, projectName = "spur") {
|
|
59015
59101
|
const dir = dirname7(dest);
|
|
59016
59102
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
59017
|
-
const tempPath =
|
|
59103
|
+
const tempPath = join6(dir, `.${projectName}.${entityId}.${rand}.tmp`);
|
|
59018
59104
|
await fs3.writeFile(tempPath, content);
|
|
59019
59105
|
fsyncPath(tempPath);
|
|
59020
59106
|
renameSync3(tempPath, dest);
|
|
@@ -59029,7 +59115,7 @@ function cleanupTempFiles(folder, entityId, fs3, projectName = "spur") {
|
|
|
59029
59115
|
let count3 = 0;
|
|
59030
59116
|
for (const entry of entries) {
|
|
59031
59117
|
if (entry.startsWith(prefix) && entry.endsWith(suffix)) {
|
|
59032
|
-
fs3.deleteFile(
|
|
59118
|
+
fs3.deleteFile(join6(folder, entry));
|
|
59033
59119
|
count3++;
|
|
59034
59120
|
}
|
|
59035
59121
|
}
|
|
@@ -60093,6 +60179,7 @@ __export(exports_src, {
|
|
|
60093
60179
|
SystemEventDao: () => SystemEventDao,
|
|
60094
60180
|
StageRegistryError: () => StageRegistryError,
|
|
60095
60181
|
SYSTEM_EVENTS_SCHEMA_SQL: () => SYSTEM_EVENTS_SCHEMA_SQL,
|
|
60182
|
+
SYSTEM_EVENTS_CORRELATION_COLUMNS_SCHEMA_SQL: () => SYSTEM_EVENTS_CORRELATION_COLUMNS_SCHEMA_SQL,
|
|
60096
60183
|
STAGE_REGISTRY_SCHEMA_VERSION: () => STAGE_REGISTRY_SCHEMA_VERSION,
|
|
60097
60184
|
STAGE_ID_PATTERN: () => STAGE_ID_PATTERN,
|
|
60098
60185
|
STABLE_LAYER_NAMES: () => STABLE_LAYER_NAMES,
|
|
@@ -60163,7 +60250,6 @@ function bridgeEventBus(serverBus) {
|
|
|
60163
60250
|
}
|
|
60164
60251
|
|
|
60165
60252
|
// ../../packages/app/src/services/agent-service.ts
|
|
60166
|
-
import { stat } from "fs/promises";
|
|
60167
60253
|
import { isatty as isatty3 } from "tty";
|
|
60168
60254
|
|
|
60169
60255
|
class AgentService {
|
|
@@ -60567,7 +60653,7 @@ class AgentService {
|
|
|
60567
60653
|
}
|
|
60568
60654
|
async statCwd(cwd) {
|
|
60569
60655
|
try {
|
|
60570
|
-
return await stat(cwd);
|
|
60656
|
+
return await createNodeFileSystem3(this.ctx.cwd).stat(cwd);
|
|
60571
60657
|
} catch {
|
|
60572
60658
|
return null;
|
|
60573
60659
|
}
|
|
@@ -60746,7 +60832,7 @@ var init_agent_service = __esm(() => {
|
|
|
60746
60832
|
});
|
|
60747
60833
|
|
|
60748
60834
|
// ../../packages/app/src/services/corpus-migrator.ts
|
|
60749
|
-
import { join as
|
|
60835
|
+
import { join as join7, relative } from "path";
|
|
60750
60836
|
function applyM1(data, flags) {
|
|
60751
60837
|
const raw = data.status;
|
|
60752
60838
|
if (typeof raw !== "string")
|
|
@@ -60955,15 +61041,17 @@ function yamlScalar(value) {
|
|
|
60955
61041
|
}
|
|
60956
61042
|
async function loadGitLastModified(cwd, dir) {
|
|
60957
61043
|
try {
|
|
60958
|
-
const
|
|
61044
|
+
const result = await new NodeProcessExecutor3().run({
|
|
61045
|
+
command: "git",
|
|
61046
|
+
args: ["log", "--format=%aI", "--name-only", "-z", "--", dir],
|
|
60959
61047
|
cwd,
|
|
60960
|
-
|
|
60961
|
-
|
|
61048
|
+
maxOutput: 32 * 1024 * 1024,
|
|
61049
|
+
forceBuffered: true,
|
|
61050
|
+
rejectOnError: false
|
|
60962
61051
|
});
|
|
60963
|
-
|
|
60964
|
-
if (await proc.exited !== 0)
|
|
61052
|
+
if (result.exitCode !== 0)
|
|
60965
61053
|
return new Map;
|
|
60966
|
-
return parseGitNameOnlyZ(
|
|
61054
|
+
return parseGitNameOnlyZ(result.stdout);
|
|
60967
61055
|
} catch {
|
|
60968
61056
|
return new Map;
|
|
60969
61057
|
}
|
|
@@ -61044,7 +61132,7 @@ class CorpusMigrator {
|
|
|
61044
61132
|
}
|
|
61045
61133
|
async discoverFiles() {
|
|
61046
61134
|
const entries = await this.fs.readDir(this.corpusDir);
|
|
61047
|
-
return entries.filter((name) => name.endsWith(".md")).filter((name) => name !== "kanban.md").map((name) =>
|
|
61135
|
+
return entries.filter((name) => name.endsWith(".md")).filter((name) => name !== "kanban.md").map((name) => join7(this.corpusDir, name)).sort();
|
|
61048
61136
|
}
|
|
61049
61137
|
async migrateFile(filePath, dryRun, gitDates = new Map) {
|
|
61050
61138
|
const flags = [];
|
|
@@ -61106,8 +61194,8 @@ class CorpusMigrator {
|
|
|
61106
61194
|
applyM5(data, filenameParent ?? "", flags);
|
|
61107
61195
|
applyM6(data);
|
|
61108
61196
|
const gitDate = gitDates.get(relative(this.fs.getProjectRoot(), filePath)) ?? null;
|
|
61109
|
-
const
|
|
61110
|
-
const mtime =
|
|
61197
|
+
const stat = await this.fs.stat(filePath);
|
|
61198
|
+
const mtime = stat ? new Date(stat.mtimeMs).toISOString() : null;
|
|
61111
61199
|
applyM7(data, gitDate, mtime, flags);
|
|
61112
61200
|
const tags = coerceArray(data.tags);
|
|
61113
61201
|
if (tags !== undefined)
|
|
@@ -61167,6 +61255,7 @@ var PRIORITY_MAP, FIELD_ORDER, NULL_PRESERVING_KEYS, ISO_TIMESTAMP_RE;
|
|
|
61167
61255
|
var init_corpus_migrator = __esm(() => {
|
|
61168
61256
|
init_src();
|
|
61169
61257
|
init_src2();
|
|
61258
|
+
init_dist5();
|
|
61170
61259
|
init_dist2();
|
|
61171
61260
|
PRIORITY_MAP = {
|
|
61172
61261
|
p0: "P0",
|
|
@@ -61331,24 +61420,42 @@ function event(name, source, renderer, payloadPolicy = "metadata-only", tier = "
|
|
|
61331
61420
|
function systemEventCatalogEntry(name) {
|
|
61332
61421
|
return SYSTEM_EVENT_CATALOG.find((entry) => entry.name === name);
|
|
61333
61422
|
}
|
|
61334
|
-
function normalizeSystemEventPayload(entry, eventPayload) {
|
|
61423
|
+
function normalizeSystemEventPayload(entry, eventPayload, secretValues = []) {
|
|
61335
61424
|
if (eventPayload === null || eventPayload === undefined)
|
|
61336
61425
|
return null;
|
|
61337
|
-
if (typeof eventPayload !== "object")
|
|
61338
|
-
return { value: eventPayload };
|
|
61426
|
+
if (typeof eventPayload !== "object") {
|
|
61427
|
+
return { value: redactSecretValue(eventPayload, secretValues) };
|
|
61428
|
+
}
|
|
61339
61429
|
const source = eventPayload;
|
|
61340
61430
|
if (entry.payloadPolicy === "raw-safe")
|
|
61341
|
-
return { ...source };
|
|
61431
|
+
return redactSecretValues({ ...source }, secretValues);
|
|
61342
61432
|
const redacted = { ...source };
|
|
61343
61433
|
for (const key of ["body", "content", "message", "prompt", "query", "response", "value"]) {
|
|
61344
61434
|
if (key in redacted) {
|
|
61345
61435
|
redacted[key] = "[redacted]";
|
|
61346
61436
|
}
|
|
61347
61437
|
}
|
|
61348
|
-
return redacted;
|
|
61438
|
+
return redactSecretValues(redacted, secretValues);
|
|
61349
61439
|
}
|
|
61350
|
-
|
|
61440
|
+
function redactSecretValue(value, secretValues) {
|
|
61441
|
+
if (typeof value === "string")
|
|
61442
|
+
return redactAndBound(value, secretValues, MAX_FIELD_LENGTH);
|
|
61443
|
+
if (Array.isArray(value))
|
|
61444
|
+
return value.map((item) => redactSecretValue(item, secretValues));
|
|
61445
|
+
if (value !== null && typeof value === "object") {
|
|
61446
|
+
return redactSecretValues({ ...value }, secretValues);
|
|
61447
|
+
}
|
|
61448
|
+
return value;
|
|
61449
|
+
}
|
|
61450
|
+
function redactSecretValues(payload, secretValues) {
|
|
61451
|
+
for (const key of Object.keys(payload)) {
|
|
61452
|
+
payload[key] = redactSecretValue(payload[key], secretValues);
|
|
61453
|
+
}
|
|
61454
|
+
return payload;
|
|
61455
|
+
}
|
|
61456
|
+
var SYSTEM_EVENT_CATALOG, SYSTEM_EVENT_NAMES, SYSTEM_EVENT_PERSISTED_NAMES, SYSTEM_EVENT_STREAMED_NAMES, SYSTEM_EVENT_PREFIXES, SYSTEM_EVENT_DEFAULT_NAMES, SYSTEM_EVENT_DIAGNOSTIC_NAMES, PLANNING_EVENT_NAMES, SYSTEM_EVENT_CATALOG_METADATA, MAX_FIELD_LENGTH = 256;
|
|
61351
61457
|
var init_event_names = __esm(() => {
|
|
61458
|
+
init_agent_execution();
|
|
61352
61459
|
SYSTEM_EVENT_CATALOG = [
|
|
61353
61460
|
event("task.created", "planning", "planning"),
|
|
61354
61461
|
event("task.updated", "planning", "planning"),
|
|
@@ -61358,12 +61465,12 @@ var init_event_names = __esm(() => {
|
|
|
61358
61465
|
event("feature.transitioned", "planning", "planning"),
|
|
61359
61466
|
event("queue.consumer.started", "queue", "queue"),
|
|
61360
61467
|
event("queue.consumer.stopped", "queue", "queue"),
|
|
61361
|
-
event("queue.job.enqueued", "queue", "queue"),
|
|
61362
|
-
event("queue.job.completed", "queue", "queue"),
|
|
61468
|
+
event("queue.job.enqueued", "queue", "queue", "metadata-only", "diagnostic"),
|
|
61469
|
+
event("queue.job.completed", "queue", "queue", "metadata-only", "diagnostic"),
|
|
61363
61470
|
event("queue.job.failed", "queue", "queue"),
|
|
61364
61471
|
event("queue.job.retrying", "queue", "queue"),
|
|
61365
61472
|
event("queue.stats", "queue", "queue"),
|
|
61366
|
-
event("scheduler.job.executed", "scheduler", "scheduler"),
|
|
61473
|
+
event("scheduler.job.executed", "scheduler", "scheduler", "metadata-only", "diagnostic"),
|
|
61367
61474
|
event("message.sent", "message", "message"),
|
|
61368
61475
|
event("message.replied", "message", "message"),
|
|
61369
61476
|
event("process.spawned", "process", "process"),
|
|
@@ -61375,6 +61482,11 @@ var init_event_names = __esm(() => {
|
|
|
61375
61482
|
event("agent.started", "agent", "agent"),
|
|
61376
61483
|
event("agent.stopped", "agent", "agent"),
|
|
61377
61484
|
event("agent.message.sent", "agent", "agent"),
|
|
61485
|
+
event("team.up", "team", "team"),
|
|
61486
|
+
event("team.down", "team", "team"),
|
|
61487
|
+
event("team.member.assigned", "team", "team"),
|
|
61488
|
+
event("team.member.started", "team", "team"),
|
|
61489
|
+
event("team.member.stopped", "team", "team"),
|
|
61378
61490
|
event("rule.run.start", "rule", "rule"),
|
|
61379
61491
|
event("rule.eval.start", "rule", "rule"),
|
|
61380
61492
|
event("rule.eval.done", "rule", "rule"),
|
|
@@ -61403,6 +61515,8 @@ var init_event_names = __esm(() => {
|
|
|
61403
61515
|
event("workflow.hitl.response", "workflow", "workflow-hitl", "redacted"),
|
|
61404
61516
|
event("workflow.hitl.note", "workflow", "workflow-hitl", "redacted"),
|
|
61405
61517
|
event("workflow.custom", "workflow", "workflow-custom"),
|
|
61518
|
+
event("workflow.agent", "workflow", "workflow-agent", "redacted", "diagnostic"),
|
|
61519
|
+
event("workflow.steering", "workflow", "workflow-steering", "redacted"),
|
|
61406
61520
|
event("api.request.error", "api", "api"),
|
|
61407
61521
|
event("bus.emit.done", "bus", "bus", "metadata-only", "diagnostic"),
|
|
61408
61522
|
event("bus.emit.noop", "bus", "bus", "metadata-only", "diagnostic"),
|
|
@@ -61562,13 +61676,13 @@ __export(exports_feature_check, {
|
|
|
61562
61676
|
FeatureCheckService: () => FeatureCheckService,
|
|
61563
61677
|
DEFAULT_FEATURE_MATRIX: () => DEFAULT_FEATURE_MATRIX
|
|
61564
61678
|
});
|
|
61565
|
-
import { dirname as dirname8, join as
|
|
61679
|
+
import { dirname as dirname8, join as join8 } from "path";
|
|
61566
61680
|
function defaultVerdictRunDir(tasksDir) {
|
|
61567
61681
|
const norm = tasksDir.replace(/\\/g, "/");
|
|
61568
61682
|
if (/\/docs\/tasks\d*$/.test(norm) || /\/docs\/tasks$/.test(norm)) {
|
|
61569
|
-
return
|
|
61683
|
+
return join8(dirname8(dirname8(tasksDir)), ".spur", "run");
|
|
61570
61684
|
}
|
|
61571
|
-
return
|
|
61685
|
+
return join8(dirname8(tasksDir), ".spur", "run");
|
|
61572
61686
|
}
|
|
61573
61687
|
function rowMatchesScenario(id, sc) {
|
|
61574
61688
|
const stripped = id.replace(/^Scenario:\s*/i, "").trim();
|
|
@@ -61638,7 +61752,7 @@ var init_feature_check = __esm(() => {
|
|
|
61638
61752
|
await this.checkOneActiveGoal(fm, featureId2, options.featuresDir, findings);
|
|
61639
61753
|
await this.checkChildrenLimit(featureId2, options.featuresDir, findings);
|
|
61640
61754
|
}
|
|
61641
|
-
const dogfoodDir = options?.dogfoodDir ?? (options?.featuresDir ?
|
|
61755
|
+
const dogfoodDir = options?.dogfoodDir ?? (options?.featuresDir ? join8(dirname8(options.featuresDir), "dogfood") : undefined);
|
|
61642
61756
|
const runDir = options?.runDir ?? (options?.tasksDir ? defaultVerdictRunDir(options.tasksDir) : undefined);
|
|
61643
61757
|
await this.runL4(doc2, featureId2, status, options?.tasksDir, dogfoodDir, runDir, findings);
|
|
61644
61758
|
return { id: featureId2, ...this.summarizeWithStatus(status, findings, strict, options?.severityOverrides) };
|
|
@@ -61831,7 +61945,7 @@ var init_feature_check = __esm(() => {
|
|
|
61831
61945
|
for (const taskAc of linkedTaskAc) {
|
|
61832
61946
|
if (stillOrphan.size === 0)
|
|
61833
61947
|
break;
|
|
61834
|
-
const orphanedByThisTask = new Set(checkAcCoverage(acBody, taskAc).orphans);
|
|
61948
|
+
const orphanedByThisTask = new Set(checkAcCoverage(acBody, taskAc, parseChecklist(taskAc)).orphans);
|
|
61835
61949
|
stillOrphan = new Set([...stillOrphan].filter((o2) => orphanedByThisTask.has(o2)));
|
|
61836
61950
|
}
|
|
61837
61951
|
for (const orphan of stillOrphan) {
|
|
@@ -61899,7 +62013,7 @@ var init_feature_check = __esm(() => {
|
|
|
61899
62013
|
for (const task of linkedTasks) {
|
|
61900
62014
|
const taskCov = checkAcCoverage(`Feature: x
|
|
61901
62015
|
Scenario: ${sc.title}
|
|
61902
|
-
Given x`, task.ac);
|
|
62016
|
+
Given x`, task.ac, parseChecklist(task.ac));
|
|
61903
62017
|
if (!taskCov.orphans.includes(sc.title)) {
|
|
61904
62018
|
linked.push({ wbs: task.wbs, status: task.status });
|
|
61905
62019
|
}
|
|
@@ -61940,7 +62054,7 @@ var init_feature_check = __esm(() => {
|
|
|
61940
62054
|
}
|
|
61941
62055
|
async readVerdictArtifact(runDir, wbs) {
|
|
61942
62056
|
try {
|
|
61943
|
-
const raw = await this.fs.readFile(
|
|
62057
|
+
const raw = await this.fs.readFile(join8(runDir, `${wbs}-verdict.json`));
|
|
61944
62058
|
const parsed = JSON.parse(raw);
|
|
61945
62059
|
if (typeof parsed.verdict !== "string")
|
|
61946
62060
|
return null;
|
|
@@ -62027,6 +62141,9 @@ var init_task_locator = __esm(() => {
|
|
|
62027
62141
|
});
|
|
62028
62142
|
|
|
62029
62143
|
// ../../packages/app/src/services/feature-service.ts
|
|
62144
|
+
function isFeatureActionName(action) {
|
|
62145
|
+
return FEATURE_ACTION_NAMES.includes(action);
|
|
62146
|
+
}
|
|
62030
62147
|
function stripLeadingSectionHeader(body, sectionName) {
|
|
62031
62148
|
const match = body.match(/^(\s*)#{1,6}\s+(.+?)\s*(?:\n|$)/);
|
|
62032
62149
|
if (match === null)
|
|
@@ -62619,6 +62736,23 @@ Feature: ${name}
|
|
|
62619
62736
|
## History
|
|
62620
62737
|
`;
|
|
62621
62738
|
}
|
|
62739
|
+
async fulfillAction(featureId2, action, enqueue, options) {
|
|
62740
|
+
const feature = await this.show(featureId2);
|
|
62741
|
+
if (!feature) {
|
|
62742
|
+
throw new Error(`Feature not found: ${featureId2}`);
|
|
62743
|
+
}
|
|
62744
|
+
if (!isFeatureActionName(action)) {
|
|
62745
|
+
throw new Error(`Unsupported feature action: ${action}`);
|
|
62746
|
+
}
|
|
62747
|
+
const runId = await enqueue({
|
|
62748
|
+
featureId: featureId2,
|
|
62749
|
+
action,
|
|
62750
|
+
command: FEATURE_ACTION_COMMANDS[action](featureId2),
|
|
62751
|
+
channel: options?.channel,
|
|
62752
|
+
skipDeps: options?.skipDeps
|
|
62753
|
+
});
|
|
62754
|
+
return { runId, action, status: "queued" };
|
|
62755
|
+
}
|
|
62622
62756
|
}
|
|
62623
62757
|
function renderIndex(features) {
|
|
62624
62758
|
const sorted = [...features].sort((a2, b) => a2.id.localeCompare(b.id));
|
|
@@ -62671,11 +62805,16 @@ ${body}`;
|
|
|
62671
62805
|
function escapePipes(text4) {
|
|
62672
62806
|
return text4.replace(/\|/g, "\\|");
|
|
62673
62807
|
}
|
|
62674
|
-
var FEATURE_FILE_RE;
|
|
62808
|
+
var FEATURE_ACTION_NAMES, FEATURE_ACTION_COMMANDS, FEATURE_FILE_RE;
|
|
62675
62809
|
var init_feature_service = __esm(() => {
|
|
62676
62810
|
init_src2();
|
|
62677
62811
|
init_feature_check();
|
|
62678
62812
|
init_task_locator();
|
|
62813
|
+
FEATURE_ACTION_NAMES = ["brainstorm", "plan"];
|
|
62814
|
+
FEATURE_ACTION_COMMANDS = {
|
|
62815
|
+
brainstorm: (id) => `spur dev brainstorm --feature ${id}`,
|
|
62816
|
+
plan: (id) => `spur dev plan --feature ${id}`
|
|
62817
|
+
};
|
|
62679
62818
|
FEATURE_FILE_RE = /^([A-Z][1-9]*)_(.+)\.md$/;
|
|
62680
62819
|
});
|
|
62681
62820
|
|
|
@@ -63092,19 +63231,21 @@ function createPsProcessInspector(platform2 = process.platform, runPs = defaultR
|
|
|
63092
63231
|
};
|
|
63093
63232
|
}
|
|
63094
63233
|
async function defaultRunPs() {
|
|
63095
|
-
const
|
|
63096
|
-
const
|
|
63097
|
-
|
|
63098
|
-
|
|
63099
|
-
|
|
63100
|
-
|
|
63101
|
-
|
|
63102
|
-
|
|
63234
|
+
const [command, ...args] = PS_LIST_ARGV;
|
|
63235
|
+
const result = await new NodeProcessExecutor3().run({
|
|
63236
|
+
command: command ?? "ps",
|
|
63237
|
+
args,
|
|
63238
|
+
forceBuffered: true,
|
|
63239
|
+
rejectOnError: false
|
|
63240
|
+
});
|
|
63241
|
+
if (result.exitCode !== 0) {
|
|
63242
|
+
throw new Error(`ps failed (exit ${result.exitCode}): ${result.stderr.trim() || "no stderr"}`);
|
|
63103
63243
|
}
|
|
63104
|
-
return stdout;
|
|
63244
|
+
return result.stdout;
|
|
63105
63245
|
}
|
|
63106
63246
|
var UnsupportedProcessPlatformError, PS_LIST_ARGV;
|
|
63107
63247
|
var init_process_inspector = __esm(() => {
|
|
63248
|
+
init_dist5();
|
|
63108
63249
|
UnsupportedProcessPlatformError = class UnsupportedProcessPlatformError extends Error {
|
|
63109
63250
|
code = "UNSUPPORTED_PLATFORM";
|
|
63110
63251
|
constructor(platform2) {
|
|
@@ -63230,9 +63371,399 @@ var init_process_inventory_service = __esm(() => {
|
|
|
63230
63371
|
init_process_inspector();
|
|
63231
63372
|
});
|
|
63232
63373
|
|
|
63374
|
+
// ../../packages/app/src/services/project-registry.ts
|
|
63375
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync8, realpathSync as realpathSync3, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
63376
|
+
import { connect, createServer } from "net";
|
|
63377
|
+
import { homedir as homedir3 } from "os";
|
|
63378
|
+
import { dirname as dirname9, join as join9, resolve } from "path";
|
|
63379
|
+
function normalizeProjectPath(pathInput) {
|
|
63380
|
+
const trimmed = pathInput.trim();
|
|
63381
|
+
let expanded = trimmed;
|
|
63382
|
+
if (trimmed === "~") {
|
|
63383
|
+
expanded = homedir3();
|
|
63384
|
+
} else if (trimmed.startsWith("~/")) {
|
|
63385
|
+
expanded = join9(homedir3(), trimmed.slice(2));
|
|
63386
|
+
} else {
|
|
63387
|
+
expanded = resolve(trimmed);
|
|
63388
|
+
}
|
|
63389
|
+
try {
|
|
63390
|
+
if (existsSync5(expanded)) {
|
|
63391
|
+
return realpathSync3(expanded);
|
|
63392
|
+
}
|
|
63393
|
+
} catch {}
|
|
63394
|
+
return expanded;
|
|
63395
|
+
}
|
|
63396
|
+
async function isPortLiveOnHost(port, host, timeoutMs) {
|
|
63397
|
+
return new Promise((resolve2) => {
|
|
63398
|
+
const socket = connect({ port, host, timeout: timeoutMs });
|
|
63399
|
+
socket.on("connect", () => {
|
|
63400
|
+
socket.destroy();
|
|
63401
|
+
resolve2(true);
|
|
63402
|
+
});
|
|
63403
|
+
socket.on("timeout", () => {
|
|
63404
|
+
socket.destroy();
|
|
63405
|
+
resolve2(false);
|
|
63406
|
+
});
|
|
63407
|
+
socket.on("error", () => {
|
|
63408
|
+
socket.destroy();
|
|
63409
|
+
resolve2(false);
|
|
63410
|
+
});
|
|
63411
|
+
});
|
|
63412
|
+
}
|
|
63413
|
+
async function isPortLive(port, timeoutMs = 200) {
|
|
63414
|
+
if (port <= 0)
|
|
63415
|
+
return false;
|
|
63416
|
+
if (await isPortLiveOnHost(port, "127.0.0.1", timeoutMs))
|
|
63417
|
+
return true;
|
|
63418
|
+
if (await isPortLiveOnHost(port, "::1", timeoutMs))
|
|
63419
|
+
return true;
|
|
63420
|
+
return false;
|
|
63421
|
+
}
|
|
63422
|
+
async function isPortAvailable(port) {
|
|
63423
|
+
if (port <= 0 || port > 65535)
|
|
63424
|
+
return false;
|
|
63425
|
+
return new Promise((resolve2) => {
|
|
63426
|
+
const server = createServer();
|
|
63427
|
+
server.unref();
|
|
63428
|
+
server.on("error", () => resolve2(false));
|
|
63429
|
+
server.listen({ port, host: "127.0.0.1" }, () => {
|
|
63430
|
+
server.close(() => resolve2(true));
|
|
63431
|
+
});
|
|
63432
|
+
});
|
|
63433
|
+
}
|
|
63434
|
+
|
|
63435
|
+
class ProjectRegistry {
|
|
63436
|
+
filePath;
|
|
63437
|
+
lockDir;
|
|
63438
|
+
constructor(customPath) {
|
|
63439
|
+
this.filePath = customPath ?? getProjectsFilePath();
|
|
63440
|
+
this.lockDir = `${this.filePath}.lock`;
|
|
63441
|
+
}
|
|
63442
|
+
async withLock(fn) {
|
|
63443
|
+
const lockParent = dirname9(this.lockDir);
|
|
63444
|
+
if (!existsSync5(lockParent)) {
|
|
63445
|
+
mkdirSync5(lockParent, { recursive: true });
|
|
63446
|
+
}
|
|
63447
|
+
const maxTries = 50;
|
|
63448
|
+
let acquired = false;
|
|
63449
|
+
for (let i2 = 0;i2 < maxTries; i2++) {
|
|
63450
|
+
try {
|
|
63451
|
+
mkdirSync5(this.lockDir);
|
|
63452
|
+
acquired = true;
|
|
63453
|
+
break;
|
|
63454
|
+
} catch {
|
|
63455
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
63456
|
+
}
|
|
63457
|
+
}
|
|
63458
|
+
if (!acquired) {
|
|
63459
|
+
try {
|
|
63460
|
+
rmSync3(this.lockDir, { recursive: true, force: true });
|
|
63461
|
+
mkdirSync5(this.lockDir);
|
|
63462
|
+
acquired = true;
|
|
63463
|
+
} catch {
|
|
63464
|
+
throw new Error(`Failed to acquire lock for project registry: ${this.lockDir}`);
|
|
63465
|
+
}
|
|
63466
|
+
}
|
|
63467
|
+
try {
|
|
63468
|
+
return await fn();
|
|
63469
|
+
} finally {
|
|
63470
|
+
if (acquired) {
|
|
63471
|
+
try {
|
|
63472
|
+
rmSync3(this.lockDir, { recursive: true, force: true });
|
|
63473
|
+
} catch {}
|
|
63474
|
+
}
|
|
63475
|
+
}
|
|
63476
|
+
}
|
|
63477
|
+
readRaw() {
|
|
63478
|
+
if (!existsSync5(this.filePath)) {
|
|
63479
|
+
return { schema_version: 1, projects: [] };
|
|
63480
|
+
}
|
|
63481
|
+
try {
|
|
63482
|
+
const raw = readFileSync8(this.filePath, "utf-8");
|
|
63483
|
+
const json3 = JSON.parse(raw);
|
|
63484
|
+
return projectsFileSchema.parse(json3);
|
|
63485
|
+
} catch {
|
|
63486
|
+
return { schema_version: 1, projects: [] };
|
|
63487
|
+
}
|
|
63488
|
+
}
|
|
63489
|
+
writeRaw(data) {
|
|
63490
|
+
const parentDir2 = dirname9(this.filePath);
|
|
63491
|
+
if (!existsSync5(parentDir2)) {
|
|
63492
|
+
mkdirSync5(parentDir2, { recursive: true });
|
|
63493
|
+
}
|
|
63494
|
+
const tmpFile = `${this.filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
63495
|
+
const content = JSON.stringify(data, null, 2);
|
|
63496
|
+
writeFileSync4(tmpFile, content, "utf-8");
|
|
63497
|
+
renameSync4(tmpFile, this.filePath);
|
|
63498
|
+
}
|
|
63499
|
+
async healTildePaths() {
|
|
63500
|
+
return this.withLock(async () => {
|
|
63501
|
+
const data = this.readRaw();
|
|
63502
|
+
let changed = false;
|
|
63503
|
+
for (const project of data.projects) {
|
|
63504
|
+
const normalized = normalizeProjectPath(project.path);
|
|
63505
|
+
if (normalized !== project.path) {
|
|
63506
|
+
project.path = normalized;
|
|
63507
|
+
changed = true;
|
|
63508
|
+
}
|
|
63509
|
+
}
|
|
63510
|
+
if (changed) {
|
|
63511
|
+
this.writeRaw(data);
|
|
63512
|
+
}
|
|
63513
|
+
});
|
|
63514
|
+
}
|
|
63515
|
+
async list() {
|
|
63516
|
+
await this.healTildePaths();
|
|
63517
|
+
await this.healStale();
|
|
63518
|
+
return this.readRaw().projects;
|
|
63519
|
+
}
|
|
63520
|
+
async getByPath(pathInput) {
|
|
63521
|
+
const normalized = normalizeProjectPath(pathInput);
|
|
63522
|
+
const projects3 = await this.list();
|
|
63523
|
+
return projects3.find((p) => normalizeProjectPath(p.path) === normalized);
|
|
63524
|
+
}
|
|
63525
|
+
async getByName(nameInput) {
|
|
63526
|
+
const trimmed = nameInput.trim();
|
|
63527
|
+
const projects3 = await this.list();
|
|
63528
|
+
return projects3.find((p) => p.name === trimmed) ?? projects3.find((p) => p.name.toLowerCase() === trimmed.toLowerCase());
|
|
63529
|
+
}
|
|
63530
|
+
async upsert(entry) {
|
|
63531
|
+
return this.withLock(async () => {
|
|
63532
|
+
const currentData = this.readRaw();
|
|
63533
|
+
const normalizedPath = normalizeProjectPath(entry.path);
|
|
63534
|
+
const index2 = currentData.projects.findIndex((p) => normalizeProjectPath(p.path) === normalizedPath);
|
|
63535
|
+
const newEntry = {
|
|
63536
|
+
name: entry.name,
|
|
63537
|
+
path: normalizedPath,
|
|
63538
|
+
port: entry.port ?? 0
|
|
63539
|
+
};
|
|
63540
|
+
if (index2 >= 0 && currentData.projects[index2]) {
|
|
63541
|
+
const existing = currentData.projects[index2];
|
|
63542
|
+
currentData.projects[index2] = {
|
|
63543
|
+
...existing,
|
|
63544
|
+
name: entry.name,
|
|
63545
|
+
port: entry.port ?? existing.port
|
|
63546
|
+
};
|
|
63547
|
+
} else {
|
|
63548
|
+
currentData.projects.push(newEntry);
|
|
63549
|
+
}
|
|
63550
|
+
this.writeRaw(currentData);
|
|
63551
|
+
const res = currentData.projects.find((p) => normalizeProjectPath(p.path) === normalizedPath);
|
|
63552
|
+
if (!res) {
|
|
63553
|
+
throw new Error(`Failed to upsert project: ${normalizedPath}`);
|
|
63554
|
+
}
|
|
63555
|
+
return res;
|
|
63556
|
+
});
|
|
63557
|
+
}
|
|
63558
|
+
async remove(nameOrPath) {
|
|
63559
|
+
return this.withLock(async () => {
|
|
63560
|
+
const currentData = this.readRaw();
|
|
63561
|
+
const normalized = normalizeProjectPath(nameOrPath);
|
|
63562
|
+
const initialLen = currentData.projects.length;
|
|
63563
|
+
currentData.projects = currentData.projects.filter((p) => {
|
|
63564
|
+
const pNorm = normalizeProjectPath(p.path);
|
|
63565
|
+
const nameMatch = p.name.toLowerCase() === nameOrPath.trim().toLowerCase();
|
|
63566
|
+
return pNorm !== normalized && !nameMatch;
|
|
63567
|
+
});
|
|
63568
|
+
if (currentData.projects.length !== initialLen) {
|
|
63569
|
+
this.writeRaw(currentData);
|
|
63570
|
+
return true;
|
|
63571
|
+
}
|
|
63572
|
+
return false;
|
|
63573
|
+
});
|
|
63574
|
+
}
|
|
63575
|
+
async setPort(pathInput, port) {
|
|
63576
|
+
return this.withLock(async () => {
|
|
63577
|
+
const currentData = this.readRaw();
|
|
63578
|
+
const normalized = normalizeProjectPath(pathInput);
|
|
63579
|
+
const project = currentData.projects.find((p) => normalizeProjectPath(p.path) === normalized);
|
|
63580
|
+
if (project) {
|
|
63581
|
+
project.port = port;
|
|
63582
|
+
this.writeRaw(currentData);
|
|
63583
|
+
return true;
|
|
63584
|
+
}
|
|
63585
|
+
return false;
|
|
63586
|
+
});
|
|
63587
|
+
}
|
|
63588
|
+
async allocatePort(preferredPort) {
|
|
63589
|
+
if (preferredPort && preferredPort > 0 && await isPortAvailable(preferredPort)) {
|
|
63590
|
+
return preferredPort;
|
|
63591
|
+
}
|
|
63592
|
+
const data = this.readRaw();
|
|
63593
|
+
const claimedPorts = new Set(data.projects.map((p) => p.port).filter((p) => p > 0));
|
|
63594
|
+
for (let port = 3000;port <= 3999; port++) {
|
|
63595
|
+
if (claimedPorts.has(port))
|
|
63596
|
+
continue;
|
|
63597
|
+
if (await isPortAvailable(port)) {
|
|
63598
|
+
return port;
|
|
63599
|
+
}
|
|
63600
|
+
}
|
|
63601
|
+
throw new Error("No available ports in range 3000\u20133999");
|
|
63602
|
+
}
|
|
63603
|
+
async healStale() {
|
|
63604
|
+
return this.withLock(async () => {
|
|
63605
|
+
const data = this.readRaw();
|
|
63606
|
+
let changed = false;
|
|
63607
|
+
for (const project of data.projects) {
|
|
63608
|
+
if (project.port > 0) {
|
|
63609
|
+
const live = await isPortLive(project.port);
|
|
63610
|
+
if (!live) {
|
|
63611
|
+
project.port = 0;
|
|
63612
|
+
changed = true;
|
|
63613
|
+
}
|
|
63614
|
+
}
|
|
63615
|
+
}
|
|
63616
|
+
if (changed) {
|
|
63617
|
+
this.writeRaw(data);
|
|
63618
|
+
}
|
|
63619
|
+
});
|
|
63620
|
+
}
|
|
63621
|
+
}
|
|
63622
|
+
var init_project_registry = __esm(() => {
|
|
63623
|
+
init_src();
|
|
63624
|
+
});
|
|
63625
|
+
|
|
63626
|
+
// ../../packages/app/src/services/project-start.ts
|
|
63627
|
+
import { existsSync as existsSync6 } from "fs";
|
|
63628
|
+
import { basename as basename2, resolve as resolve2 } from "path";
|
|
63629
|
+
function shQuote(value) {
|
|
63630
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
63631
|
+
}
|
|
63632
|
+
function flattenEnv(env) {
|
|
63633
|
+
const out = {};
|
|
63634
|
+
for (const [key, value] of Object.entries(env)) {
|
|
63635
|
+
if (value !== undefined)
|
|
63636
|
+
out[key] = value;
|
|
63637
|
+
}
|
|
63638
|
+
return out;
|
|
63639
|
+
}
|
|
63640
|
+
function setDetachedServeSpawnForTests(spawn2) {
|
|
63641
|
+
testDetachedServeSpawn = spawn2;
|
|
63642
|
+
}
|
|
63643
|
+
function resolveSpurServeCommand() {
|
|
63644
|
+
if (process.env.SPUR_CLI_PATH && existsSync6(process.env.SPUR_CLI_PATH)) {
|
|
63645
|
+
return [process.execPath, process.env.SPUR_CLI_PATH];
|
|
63646
|
+
}
|
|
63647
|
+
const argv1 = process.argv[1];
|
|
63648
|
+
if (typeof argv1 === "string" && argv1.length > 0) {
|
|
63649
|
+
const base2 = argv1.replace(/\\/g, "/");
|
|
63650
|
+
if (base2.endsWith("/spur.js") || base2.endsWith("/spur") || base2.includes("/apps/cli/src/index") || base2.endsWith("apps/cli/src/index.ts")) {
|
|
63651
|
+
return [process.execPath, argv1];
|
|
63652
|
+
}
|
|
63653
|
+
}
|
|
63654
|
+
const monorepoCli = resolve2(process.cwd(), "apps/cli/src/index.ts");
|
|
63655
|
+
if (existsSync6(monorepoCli)) {
|
|
63656
|
+
return [process.execPath, monorepoCli];
|
|
63657
|
+
}
|
|
63658
|
+
const fromPath = typeof Bun !== "undefined" && typeof Bun.which === "function" ? Bun.which("spur") : null;
|
|
63659
|
+
if (fromPath) {
|
|
63660
|
+
return [fromPath];
|
|
63661
|
+
}
|
|
63662
|
+
throw new Error("Could not resolve the spur CLI to spawn `spur serve`. Ensure `spur` is on PATH (or invoke start via the monorepo CLI).");
|
|
63663
|
+
}
|
|
63664
|
+
async function startRegisteredProject(registry2, target, options = {}) {
|
|
63665
|
+
let entry = await registry2.getByName(target) ?? await registry2.getByPath(target);
|
|
63666
|
+
if (!entry) {
|
|
63667
|
+
const absPath = normalizeProjectPath(target);
|
|
63668
|
+
if (existsSync6(absPath)) {
|
|
63669
|
+
entry = await registry2.upsert({ path: absPath, name: basename2(absPath), port: 0 });
|
|
63670
|
+
} else {
|
|
63671
|
+
throw new Error(`Project not found in registry: "${target}"`);
|
|
63672
|
+
}
|
|
63673
|
+
}
|
|
63674
|
+
const projectPath = normalizeProjectPath(entry.path);
|
|
63675
|
+
if (!existsSync6(projectPath)) {
|
|
63676
|
+
throw new Error(`Project path does not exist: "${entry.path}" (resolved to ${projectPath}). Update ~/.config/spur/projects.json.`);
|
|
63677
|
+
}
|
|
63678
|
+
if (projectPath !== entry.path) {
|
|
63679
|
+
await registry2.upsert({ name: entry.name, path: projectPath, port: entry.port });
|
|
63680
|
+
entry = { ...entry, path: projectPath };
|
|
63681
|
+
}
|
|
63682
|
+
if (entry.port > 0 && await isPortLive(entry.port)) {
|
|
63683
|
+
return {
|
|
63684
|
+
name: entry.name,
|
|
63685
|
+
path: projectPath,
|
|
63686
|
+
port: entry.port,
|
|
63687
|
+
running: true,
|
|
63688
|
+
url: `http://127.0.0.1:${entry.port}`,
|
|
63689
|
+
alreadyRunning: true
|
|
63690
|
+
};
|
|
63691
|
+
}
|
|
63692
|
+
const allocatedPort = options.port && options.port > 0 ? options.port : await registry2.allocatePort();
|
|
63693
|
+
const invocation = resolveSpurServeCommand();
|
|
63694
|
+
const spawn2 = options.spawn ?? testDetachedServeSpawn ?? defaultDetachedServeSpawn;
|
|
63695
|
+
const child = await Promise.resolve(spawn2([
|
|
63696
|
+
...invocation,
|
|
63697
|
+
"serve",
|
|
63698
|
+
"--cwd",
|
|
63699
|
+
projectPath,
|
|
63700
|
+
"--port",
|
|
63701
|
+
String(allocatedPort),
|
|
63702
|
+
"--host",
|
|
63703
|
+
"127.0.0.1",
|
|
63704
|
+
"--no-open"
|
|
63705
|
+
], {
|
|
63706
|
+
cwd: projectPath,
|
|
63707
|
+
detached: true,
|
|
63708
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
63709
|
+
env: process.env
|
|
63710
|
+
}));
|
|
63711
|
+
const pollAttempts = options.pollAttempts ?? 100;
|
|
63712
|
+
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
63713
|
+
let live = false;
|
|
63714
|
+
for (let i2 = 0;i2 < pollAttempts; i2++) {
|
|
63715
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
63716
|
+
if (await isPortLive(allocatedPort)) {
|
|
63717
|
+
live = true;
|
|
63718
|
+
break;
|
|
63719
|
+
}
|
|
63720
|
+
if (child.exitCode !== null) {
|
|
63721
|
+
child.unref();
|
|
63722
|
+
throw new Error(`Project "${entry.name}" serve process exited with code ${child.exitCode} before port ${allocatedPort} became ready (path: ${projectPath}).`);
|
|
63723
|
+
}
|
|
63724
|
+
}
|
|
63725
|
+
child.unref();
|
|
63726
|
+
if (!live) {
|
|
63727
|
+
throw new Error(`Project "${entry.name}" failed to start on port ${allocatedPort} within ${pollAttempts * pollIntervalMs / 1000}s (path: ${projectPath}).`);
|
|
63728
|
+
}
|
|
63729
|
+
await registry2.setPort(projectPath, allocatedPort);
|
|
63730
|
+
return {
|
|
63731
|
+
name: entry.name,
|
|
63732
|
+
path: projectPath,
|
|
63733
|
+
port: allocatedPort,
|
|
63734
|
+
running: true,
|
|
63735
|
+
url: `http://127.0.0.1:${allocatedPort}`,
|
|
63736
|
+
alreadyRunning: false
|
|
63737
|
+
};
|
|
63738
|
+
}
|
|
63739
|
+
var defaultDetachedServeSpawn = async (cmd, options) => {
|
|
63740
|
+
const executor = new NodeProcessExecutor3;
|
|
63741
|
+
const line = cmd.map(shQuote).join(" ");
|
|
63742
|
+
const shell = process.platform === "win32" ? {
|
|
63743
|
+
command: "cmd",
|
|
63744
|
+
args: ["/c", `start /b "" ${cmd.map((c3) => `"${c3.replace(/"/g, '""')}"`).join(" ")}`]
|
|
63745
|
+
} : {
|
|
63746
|
+
command: "/bin/sh",
|
|
63747
|
+
args: ["-c", `nohup ${line} </dev/null >/dev/null 2>&1 &`]
|
|
63748
|
+
};
|
|
63749
|
+
await executor.run({
|
|
63750
|
+
command: shell.command,
|
|
63751
|
+
args: shell.args,
|
|
63752
|
+
...options.cwd !== undefined ? { cwd: options.cwd } : {},
|
|
63753
|
+
env: flattenEnv(options.env ?? process.env),
|
|
63754
|
+
forceBuffered: true,
|
|
63755
|
+
rejectOnError: false
|
|
63756
|
+
});
|
|
63757
|
+
return { exitCode: null, unref: () => {} };
|
|
63758
|
+
}, testDetachedServeSpawn;
|
|
63759
|
+
var init_project_start = __esm(() => {
|
|
63760
|
+
init_dist5();
|
|
63761
|
+
init_project_registry();
|
|
63762
|
+
});
|
|
63763
|
+
|
|
63233
63764
|
// ../../packages/app/src/services/rule-service.ts
|
|
63234
|
-
import { homedir as
|
|
63235
|
-
import { delimiter, join as
|
|
63765
|
+
import { homedir as homedir4 } from "os";
|
|
63766
|
+
import { delimiter, join as join10, relative as relative2, resolve as resolve3 } from "path";
|
|
63236
63767
|
|
|
63237
63768
|
class RuleService {
|
|
63238
63769
|
context;
|
|
@@ -63411,22 +63942,22 @@ ${ruleIds.join(`
|
|
|
63411
63942
|
if (envValue !== undefined && envValue.length > 0) {
|
|
63412
63943
|
for (const entry of envValue.split(delimiter)) {
|
|
63413
63944
|
if (entry.length > 0) {
|
|
63414
|
-
layers.push({ id: "env-override", path:
|
|
63945
|
+
layers.push({ id: "env-override", path: resolve3(cwd, entry), priority: -10 });
|
|
63415
63946
|
}
|
|
63416
63947
|
}
|
|
63417
63948
|
}
|
|
63418
|
-
layers.push({ id: "local", path:
|
|
63949
|
+
layers.push({ id: "local", path: resolve3(cwd, LOCAL_RULES_DIR), priority: 0 });
|
|
63419
63950
|
const globalOverride = env.SPUR_GLOBAL_RULES_DIR;
|
|
63420
63951
|
const hasGlobalOverride = globalOverride !== undefined && globalOverride.length > 0;
|
|
63421
63952
|
layers.push({
|
|
63422
63953
|
id: "global",
|
|
63423
|
-
path: hasGlobalOverride ?
|
|
63954
|
+
path: hasGlobalOverride ? resolve3(cwd, globalOverride) : join10(homedir4(), GLOBAL_RULES_DIR),
|
|
63424
63955
|
priority: 10
|
|
63425
63956
|
});
|
|
63426
63957
|
if (opts.includeBundled && !hasGlobalOverride) {
|
|
63427
63958
|
const bundledConfig = bundledConfigRoot();
|
|
63428
63959
|
if (bundledConfig !== null) {
|
|
63429
|
-
layers.push({ id: "bundled-config", path:
|
|
63960
|
+
layers.push({ id: "bundled-config", path: join10(bundledConfig, "rules"), priority: 15 });
|
|
63430
63961
|
}
|
|
63431
63962
|
const bundled = await bundledRulesRoot();
|
|
63432
63963
|
if (bundled !== null)
|
|
@@ -63576,7 +64107,7 @@ ${ruleIds.join(`
|
|
|
63576
64107
|
async collectValidationErrors(source, validateSchema) {
|
|
63577
64108
|
const { cwd, fs: fs3 } = this.context;
|
|
63578
64109
|
if (source.kind === "file") {
|
|
63579
|
-
const absolute =
|
|
64110
|
+
const absolute = resolve3(cwd, source.value);
|
|
63580
64111
|
if (!await fs3.exists(absolute))
|
|
63581
64112
|
return [`File not found: ${absolute}`];
|
|
63582
64113
|
try {
|
|
@@ -63600,7 +64131,7 @@ ${ruleIds.join(`
|
|
|
63600
64131
|
const { fs: fs3 } = this.context;
|
|
63601
64132
|
for (const root of await this.ruleRoots()) {
|
|
63602
64133
|
for (const ext of ["yaml", "yml", "json"]) {
|
|
63603
|
-
if (await fs3.exists(
|
|
64134
|
+
if (await fs3.exists(join10(root, `${name}.${ext}`)))
|
|
63604
64135
|
return true;
|
|
63605
64136
|
}
|
|
63606
64137
|
}
|
|
@@ -63636,7 +64167,7 @@ ${ruleIds.join(`
|
|
|
63636
64167
|
for (const layer of await this.existingRuleSourceLayers()) {
|
|
63637
64168
|
const entries = await this.context.fs.readDir(layer.path);
|
|
63638
64169
|
const named = entries.map((entry) => ({ entry, name: entry.match(/^([\w-]+)\.(?:ya?ml|json)$/i)?.[1] ?? "" })).filter(({ name }) => name.length > 0);
|
|
63639
|
-
const stats = await Promise.all(named.map(({ entry }) => this.context.fs.stat(
|
|
64170
|
+
const stats = await Promise.all(named.map(({ entry }) => this.context.fs.stat(join10(layer.path, entry))));
|
|
63640
64171
|
named.forEach(({ name }, i2) => {
|
|
63641
64172
|
if (stats[i2]?.isFile())
|
|
63642
64173
|
candidates.add(name);
|
|
@@ -63658,7 +64189,7 @@ ${ruleIds.join(`
|
|
|
63658
64189
|
for (const layer of layers) {
|
|
63659
64190
|
for (const file2 of await this.listRuleFilesInLayer(layer.path)) {
|
|
63660
64191
|
if (!merged.has(file2))
|
|
63661
|
-
merged.set(file2, { absolutePath:
|
|
64192
|
+
merged.set(file2, { absolutePath: join10(layer.path, file2), source: layer.id });
|
|
63662
64193
|
}
|
|
63663
64194
|
}
|
|
63664
64195
|
const files = [];
|
|
@@ -63726,24 +64257,24 @@ ${ruleIds.join(`
|
|
|
63726
64257
|
for (const entry of entries.sort()) {
|
|
63727
64258
|
if (entry === "presets")
|
|
63728
64259
|
continue;
|
|
63729
|
-
const
|
|
63730
|
-
if (
|
|
64260
|
+
const stat = await this.context.fs.stat(join10(root, entry));
|
|
64261
|
+
if (stat?.isDirectory())
|
|
63731
64262
|
dirs.push(entry);
|
|
63732
64263
|
}
|
|
63733
64264
|
return dirs;
|
|
63734
64265
|
}
|
|
63735
64266
|
async listRuleFiles(root, relativeDir) {
|
|
63736
64267
|
const { fs: fs3 } = this.context;
|
|
63737
|
-
const dir =
|
|
64268
|
+
const dir = join10(root, relativeDir);
|
|
63738
64269
|
const entries = await fs3.readDir(dir);
|
|
63739
64270
|
const files = [];
|
|
63740
64271
|
for (const entry of entries.sort()) {
|
|
63741
|
-
const relativePath2 = relativeDir.length === 0 ? entry :
|
|
63742
|
-
const absolutePath =
|
|
63743
|
-
const
|
|
63744
|
-
if (
|
|
64272
|
+
const relativePath2 = relativeDir.length === 0 ? entry : join10(relativeDir, entry);
|
|
64273
|
+
const absolutePath = join10(root, relativePath2);
|
|
64274
|
+
const stat = await fs3.stat(absolutePath);
|
|
64275
|
+
if (stat?.isDirectory()) {
|
|
63745
64276
|
files.push(...await this.listRuleFiles(root, relativePath2));
|
|
63746
|
-
} else if (
|
|
64277
|
+
} else if (stat?.isFile() && /\.(ya?ml|json)$/i.test(entry)) {
|
|
63747
64278
|
files.push(relativePath2);
|
|
63748
64279
|
}
|
|
63749
64280
|
}
|
|
@@ -63764,8 +64295,8 @@ var init_rule_service = __esm(() => {
|
|
|
63764
64295
|
init_src2();
|
|
63765
64296
|
init_dist6();
|
|
63766
64297
|
init_dist10();
|
|
63767
|
-
LOCAL_RULES_DIR =
|
|
63768
|
-
GLOBAL_RULES_DIR =
|
|
64298
|
+
LOCAL_RULES_DIR = join10(".spur", "rules");
|
|
64299
|
+
GLOBAL_RULES_DIR = join10(".config", "spur", "rules");
|
|
63769
64300
|
SEVERITY_RANK2 = {
|
|
63770
64301
|
info: 0,
|
|
63771
64302
|
warning: 1,
|
|
@@ -63773,6 +64304,212 @@ var init_rule_service = __esm(() => {
|
|
|
63773
64304
|
};
|
|
63774
64305
|
});
|
|
63775
64306
|
|
|
64307
|
+
// ../../packages/app/src/services/run-store-service.ts
|
|
64308
|
+
function encodeRunListCursor(id, startedAt) {
|
|
64309
|
+
const payload = { id, startedAt };
|
|
64310
|
+
const bytes = new TextEncoder().encode(JSON.stringify(payload));
|
|
64311
|
+
let binary = "";
|
|
64312
|
+
for (const byte of bytes)
|
|
64313
|
+
binary += String.fromCharCode(byte);
|
|
64314
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
64315
|
+
}
|
|
64316
|
+
function decodeRunListCursor(encoded) {
|
|
64317
|
+
if (encoded.length === 0 || encoded.length > 1024) {
|
|
64318
|
+
throw new RunStoreBadCursorError("malformed cursor: empty or exceeds maximum length");
|
|
64319
|
+
}
|
|
64320
|
+
let json3;
|
|
64321
|
+
try {
|
|
64322
|
+
const base643 = encoded.replaceAll("-", "+").replaceAll("_", "/");
|
|
64323
|
+
const binary = atob(base643);
|
|
64324
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
64325
|
+
json3 = new TextDecoder().decode(bytes);
|
|
64326
|
+
} catch {
|
|
64327
|
+
throw new RunStoreBadCursorError("malformed cursor: not valid base64url");
|
|
64328
|
+
}
|
|
64329
|
+
let parsed;
|
|
64330
|
+
try {
|
|
64331
|
+
parsed = JSON.parse(json3);
|
|
64332
|
+
} catch {
|
|
64333
|
+
throw new RunStoreBadCursorError("malformed cursor: payload is not JSON");
|
|
64334
|
+
}
|
|
64335
|
+
if (!parsed || typeof parsed !== "object") {
|
|
64336
|
+
throw new RunStoreBadCursorError("malformed cursor: payload must be an object");
|
|
64337
|
+
}
|
|
64338
|
+
const obj = parsed;
|
|
64339
|
+
if (typeof obj.id !== "string" || obj.id.length === 0) {
|
|
64340
|
+
throw new RunStoreBadCursorError("malformed cursor: missing or invalid id");
|
|
64341
|
+
}
|
|
64342
|
+
if (typeof obj.startedAt !== "string" || obj.startedAt.length === 0) {
|
|
64343
|
+
throw new RunStoreBadCursorError("malformed cursor: missing or invalid startedAt");
|
|
64344
|
+
}
|
|
64345
|
+
return { id: obj.id, startedAt: obj.startedAt };
|
|
64346
|
+
}
|
|
64347
|
+
function clampRunStoreLimit(raw, defaults = {
|
|
64348
|
+
defaultLimit: RUN_STORE_LIST_DEFAULT_LIMIT,
|
|
64349
|
+
maxLimit: RUN_STORE_LIST_MAX_LIMIT
|
|
64350
|
+
}) {
|
|
64351
|
+
if (raw === undefined || Number.isNaN(raw) || raw <= 0)
|
|
64352
|
+
return defaults.defaultLimit;
|
|
64353
|
+
return Math.min(raw, defaults.maxLimit);
|
|
64354
|
+
}
|
|
64355
|
+
function summarizeActionResult(resultJson, secretValues = []) {
|
|
64356
|
+
if (resultJson === null || resultJson === "")
|
|
64357
|
+
return null;
|
|
64358
|
+
let parsed;
|
|
64359
|
+
try {
|
|
64360
|
+
parsed = JSON.parse(resultJson);
|
|
64361
|
+
} catch {
|
|
64362
|
+
return { summary: redactString(resultJson, secretValues) };
|
|
64363
|
+
}
|
|
64364
|
+
return redactValue3(parsed, secretValues);
|
|
64365
|
+
}
|
|
64366
|
+
function redactString(value, secretValues) {
|
|
64367
|
+
return redactAndBound(value, secretValues, MAX_FIELD_LENGTH2);
|
|
64368
|
+
}
|
|
64369
|
+
function redactValue3(value, secretValues) {
|
|
64370
|
+
if (value === null || value === undefined)
|
|
64371
|
+
return value;
|
|
64372
|
+
if (typeof value === "string")
|
|
64373
|
+
return redactString(value, secretValues);
|
|
64374
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
64375
|
+
return value;
|
|
64376
|
+
if (Array.isArray(value))
|
|
64377
|
+
return value.map((item) => redactValue3(item, secretValues));
|
|
64378
|
+
if (typeof value === "object") {
|
|
64379
|
+
const out = {};
|
|
64380
|
+
for (const [key, child] of Object.entries(value)) {
|
|
64381
|
+
if (SENSITIVE_KEYS.has(key)) {
|
|
64382
|
+
out[key] = "[redacted]";
|
|
64383
|
+
} else {
|
|
64384
|
+
out[key] = redactValue3(child, secretValues);
|
|
64385
|
+
}
|
|
64386
|
+
}
|
|
64387
|
+
return out;
|
|
64388
|
+
}
|
|
64389
|
+
return redactString(String(value), secretValues);
|
|
64390
|
+
}
|
|
64391
|
+
function toListEntry(row) {
|
|
64392
|
+
return {
|
|
64393
|
+
id: row.id,
|
|
64394
|
+
workflowName: row.workflow_name,
|
|
64395
|
+
status: row.status,
|
|
64396
|
+
mode: row.mode,
|
|
64397
|
+
agent: row.agent,
|
|
64398
|
+
startedAt: row.started_at,
|
|
64399
|
+
completedAt: row.completed_at
|
|
64400
|
+
};
|
|
64401
|
+
}
|
|
64402
|
+
|
|
64403
|
+
class RunStoreService {
|
|
64404
|
+
ctx;
|
|
64405
|
+
constructor(ctx) {
|
|
64406
|
+
this.ctx = ctx;
|
|
64407
|
+
}
|
|
64408
|
+
async list(query = {}) {
|
|
64409
|
+
const limit = clampRunStoreLimit(query.limit);
|
|
64410
|
+
let before;
|
|
64411
|
+
if (query.cursor !== undefined && query.cursor.length > 0) {
|
|
64412
|
+
const decoded = decodeRunListCursor(query.cursor);
|
|
64413
|
+
before = { started_at: decoded.startedAt, id: decoded.id };
|
|
64414
|
+
}
|
|
64415
|
+
const db2 = await this.ctx.getDb();
|
|
64416
|
+
const rows = await new RunDao(db2).traceRows({
|
|
64417
|
+
status: query.status,
|
|
64418
|
+
before,
|
|
64419
|
+
limit: limit + 1
|
|
64420
|
+
});
|
|
64421
|
+
const hasMore = rows.length > limit;
|
|
64422
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
64423
|
+
const last = page.at(-1);
|
|
64424
|
+
const nextCursor = hasMore && last ? encodeRunListCursor(last.id, last.started_at) : null;
|
|
64425
|
+
return {
|
|
64426
|
+
runs: page.map(toListEntry),
|
|
64427
|
+
count: page.length,
|
|
64428
|
+
nextCursor,
|
|
64429
|
+
hasMore
|
|
64430
|
+
};
|
|
64431
|
+
}
|
|
64432
|
+
async getDetail(runId) {
|
|
64433
|
+
const db2 = await this.ctx.getDb();
|
|
64434
|
+
const row = await new RunDao(db2).traceRowById(runId);
|
|
64435
|
+
if (!row) {
|
|
64436
|
+
throw new RunStoreNotFoundError(runId);
|
|
64437
|
+
}
|
|
64438
|
+
const [phaseRows, transitionRows, actionRows] = await Promise.all([
|
|
64439
|
+
new PhaseRunDao(db2).phaseRowsByRunId(runId),
|
|
64440
|
+
new TransitionRunDao(db2).transitionRowsByRunId(runId),
|
|
64441
|
+
new ActionRunDao(db2).actionRowsByRunId(runId)
|
|
64442
|
+
]);
|
|
64443
|
+
return {
|
|
64444
|
+
run: toListEntry(row),
|
|
64445
|
+
phases: phaseRows.map((p) => ({
|
|
64446
|
+
phase: p.phase,
|
|
64447
|
+
status: p.status,
|
|
64448
|
+
startedAt: p.started_at,
|
|
64449
|
+
completedAt: p.completed_at
|
|
64450
|
+
})),
|
|
64451
|
+
transitions: transitionRows.map((t) => ({
|
|
64452
|
+
from: t.from_state,
|
|
64453
|
+
to: t.to_state,
|
|
64454
|
+
trigger: t.trigger
|
|
64455
|
+
})),
|
|
64456
|
+
actions: actionRows.map((a2) => ({
|
|
64457
|
+
id: a2.id,
|
|
64458
|
+
node: a2.node,
|
|
64459
|
+
kind: a2.kind,
|
|
64460
|
+
status: a2.status,
|
|
64461
|
+
durationMs: a2.duration_ms,
|
|
64462
|
+
ok: a2.ok === null ? null : a2.ok === 1,
|
|
64463
|
+
resultSummary: summarizeActionResult(a2.result_json, this.ctx.secretValues),
|
|
64464
|
+
startedAt: a2.started_at,
|
|
64465
|
+
completedAt: a2.completed_at
|
|
64466
|
+
}))
|
|
64467
|
+
};
|
|
64468
|
+
}
|
|
64469
|
+
async listByWbs(wbs, limitRaw) {
|
|
64470
|
+
const limit = clampRunStoreLimit(limitRaw, {
|
|
64471
|
+
defaultLimit: RUN_STORE_WBS_DEFAULT_LIMIT,
|
|
64472
|
+
maxLimit: RUN_STORE_WBS_MAX_LIMIT
|
|
64473
|
+
});
|
|
64474
|
+
const db2 = await this.ctx.getDb();
|
|
64475
|
+
const linkRows = await new TaskRunLinkDao(db2).listByWbs(wbs, limit);
|
|
64476
|
+
const runDao = new RunDao(db2);
|
|
64477
|
+
const links = [];
|
|
64478
|
+
for (const link of linkRows) {
|
|
64479
|
+
const runRow = await runDao.traceRowById(link.run_id);
|
|
64480
|
+
links.push({
|
|
64481
|
+
runId: link.run_id,
|
|
64482
|
+
kind: link.kind,
|
|
64483
|
+
linkedAt: link.created_at,
|
|
64484
|
+
run: runRow ? toListEntry(runRow) : null
|
|
64485
|
+
});
|
|
64486
|
+
}
|
|
64487
|
+
return { wbs, links, count: links.length };
|
|
64488
|
+
}
|
|
64489
|
+
}
|
|
64490
|
+
var RUN_STORE_LIST_DEFAULT_LIMIT = 50, RUN_STORE_LIST_MAX_LIMIT = 200, RUN_STORE_WBS_DEFAULT_LIMIT = 50, RUN_STORE_WBS_MAX_LIMIT = 200, MAX_FIELD_LENGTH2 = 256, SENSITIVE_KEYS, RunStoreNotFoundError, RunStoreBadCursorError;
|
|
64491
|
+
var init_run_store_service = __esm(() => {
|
|
64492
|
+
init_src2();
|
|
64493
|
+
init_agent_execution();
|
|
64494
|
+
SENSITIVE_KEYS = new Set(["body", "content", "message", "prompt", "query", "response", "value"]);
|
|
64495
|
+
RunStoreNotFoundError = class RunStoreNotFoundError extends Error {
|
|
64496
|
+
runId;
|
|
64497
|
+
code = "RUN_NOT_FOUND";
|
|
64498
|
+
constructor(runId) {
|
|
64499
|
+
super(`run not found: ${runId}`);
|
|
64500
|
+
this.runId = runId;
|
|
64501
|
+
this.name = "RunStoreNotFoundError";
|
|
64502
|
+
}
|
|
64503
|
+
};
|
|
64504
|
+
RunStoreBadCursorError = class RunStoreBadCursorError extends Error {
|
|
64505
|
+
code = "MALFORMED_CURSOR";
|
|
64506
|
+
constructor(reason) {
|
|
64507
|
+
super(reason);
|
|
64508
|
+
this.name = "RunStoreBadCursorError";
|
|
64509
|
+
}
|
|
64510
|
+
};
|
|
64511
|
+
});
|
|
64512
|
+
|
|
63776
64513
|
// ../../packages/app/src/services/supervisor-service.ts
|
|
63777
64514
|
function defaultWrapperArgv(agentId) {
|
|
63778
64515
|
return {
|
|
@@ -63785,6 +64522,7 @@ class SupervisorService {
|
|
|
63785
64522
|
processExecutor;
|
|
63786
64523
|
eventBus;
|
|
63787
64524
|
configDir;
|
|
64525
|
+
teamMemberStopEmitted = new Set;
|
|
63788
64526
|
ringBufferSize;
|
|
63789
64527
|
processes = new Map;
|
|
63790
64528
|
ringBuffers = new Map;
|
|
@@ -63863,6 +64601,12 @@ class SupervisorService {
|
|
|
63863
64601
|
teamId,
|
|
63864
64602
|
agentType: spec.type
|
|
63865
64603
|
});
|
|
64604
|
+
this.emitTeamMember("team.member.started", {
|
|
64605
|
+
teamId,
|
|
64606
|
+
memberId: agentId,
|
|
64607
|
+
agentType: spec.type ?? null,
|
|
64608
|
+
outcome: "started"
|
|
64609
|
+
});
|
|
63866
64610
|
handle.exited.then(async (code) => {
|
|
63867
64611
|
entry.exitCode = code;
|
|
63868
64612
|
this.emit("process.exited", {
|
|
@@ -63872,6 +64616,16 @@ class SupervisorService {
|
|
|
63872
64616
|
teamId: entry.teamId ?? null,
|
|
63873
64617
|
...entry.agentType ? { agentType: entry.agentType } : {}
|
|
63874
64618
|
});
|
|
64619
|
+
if (!this.teamMemberStopEmitted.has(agentId)) {
|
|
64620
|
+
this.emitTeamMember("team.member.stopped", {
|
|
64621
|
+
teamId: entry.teamId ?? null,
|
|
64622
|
+
memberId: agentId,
|
|
64623
|
+
agentType: entry.agentType ?? null,
|
|
64624
|
+
outcome: code === 0 ? "exited" : "errored"
|
|
64625
|
+
});
|
|
64626
|
+
} else {
|
|
64627
|
+
this.teamMemberStopEmitted.delete(agentId);
|
|
64628
|
+
}
|
|
63875
64629
|
if (code === 0 || entry.status === "stopped") {
|
|
63876
64630
|
entry.status = "exited";
|
|
63877
64631
|
this.scheduleCleanup(agentId, entry);
|
|
@@ -63897,13 +64651,14 @@ class SupervisorService {
|
|
|
63897
64651
|
const proc = this.processes.get(agentId);
|
|
63898
64652
|
if (proc?.entry.status !== "running")
|
|
63899
64653
|
return;
|
|
64654
|
+
this.teamMemberStopEmitted.add(agentId);
|
|
63900
64655
|
proc.handle.kill("SIGTERM");
|
|
63901
64656
|
let timedOut = false;
|
|
63902
64657
|
const exitPromise = proc.handle.exited;
|
|
63903
|
-
const timeout = new Promise((
|
|
64658
|
+
const timeout = new Promise((resolve4) => {
|
|
63904
64659
|
setTimeout(() => {
|
|
63905
64660
|
timedOut = true;
|
|
63906
|
-
|
|
64661
|
+
resolve4();
|
|
63907
64662
|
}, 3000);
|
|
63908
64663
|
});
|
|
63909
64664
|
await Promise.race([exitPromise, timeout]);
|
|
@@ -63924,6 +64679,12 @@ class SupervisorService {
|
|
|
63924
64679
|
teamId: proc.entry.teamId ?? null,
|
|
63925
64680
|
...proc.entry.agentType ? { agentType: proc.entry.agentType } : {}
|
|
63926
64681
|
});
|
|
64682
|
+
this.emitTeamMember("team.member.stopped", {
|
|
64683
|
+
teamId: proc.entry.teamId ?? null,
|
|
64684
|
+
memberId: agentId,
|
|
64685
|
+
agentType: proc.entry.agentType ?? null,
|
|
64686
|
+
outcome: "stopped"
|
|
64687
|
+
});
|
|
63927
64688
|
}
|
|
63928
64689
|
async startAutostart(ids) {
|
|
63929
64690
|
const results = [];
|
|
@@ -64004,25 +64765,49 @@ class SupervisorService {
|
|
|
64004
64765
|
this.eventBus.emit(name, payload);
|
|
64005
64766
|
} catch {}
|
|
64006
64767
|
}
|
|
64768
|
+
emitTeamMember(name, payload) {
|
|
64769
|
+
try {
|
|
64770
|
+
this.eventBus.emit(name, payload);
|
|
64771
|
+
} catch {}
|
|
64772
|
+
}
|
|
64007
64773
|
}
|
|
64008
64774
|
var DEFAULT_RING_BUFFER_SIZE = 500, MAX_RESTART_ATTEMPTS = 5, RESTART_BACKOFF_SCHEDULE, MAX_RESTART_BACKOFF = 30000;
|
|
64009
64775
|
var init_supervisor_service = __esm(() => {
|
|
64010
64776
|
RESTART_BACKOFF_SCHEDULE = [1000, 2000, 4000, 8000, 16000];
|
|
64011
64777
|
});
|
|
64012
64778
|
|
|
64779
|
+
// ../../packages/app/src/services/system-event-retention.ts
|
|
64780
|
+
function resolveRetentionQuotas(config4 = {}, prefixes = SYSTEM_EVENT_PREFIXES) {
|
|
64781
|
+
const defaultQuota = config4.default ?? DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA;
|
|
64782
|
+
const overrides = config4.prefixes ?? {};
|
|
64783
|
+
return prefixes.map((prefix) => ({
|
|
64784
|
+
prefix,
|
|
64785
|
+
quota: overrides[prefix] ?? defaultQuota
|
|
64786
|
+
}));
|
|
64787
|
+
}
|
|
64788
|
+
var DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA = 1e4;
|
|
64789
|
+
var init_system_event_retention = __esm(() => {
|
|
64790
|
+
init_event_names();
|
|
64791
|
+
});
|
|
64792
|
+
|
|
64013
64793
|
// ../../packages/app/src/services/system-event-tap.ts
|
|
64014
64794
|
function registerSystemEventTap(bus, dao2, logger3, options = {}) {
|
|
64015
64795
|
const handlers = new Map;
|
|
64016
64796
|
const inFlight = new Set;
|
|
64017
64797
|
const diagnosticEnabled = options.diagnosticEnabled === true;
|
|
64798
|
+
const quotas = resolveRetentionQuotas(options.retention);
|
|
64018
64799
|
for (const entry of SYSTEM_EVENT_CATALOG) {
|
|
64019
64800
|
if (entry.tier === "diagnostic" && !diagnosticEnabled)
|
|
64020
64801
|
continue;
|
|
64021
64802
|
const handler = (event2) => {
|
|
64022
|
-
const
|
|
64023
|
-
|
|
64024
|
-
|
|
64025
|
-
|
|
64803
|
+
const p = persist(dao2, entry.prefix, {
|
|
64804
|
+
id: createId("sev"),
|
|
64805
|
+
event_name: entry.name,
|
|
64806
|
+
occurred_at: new Date().toISOString(),
|
|
64807
|
+
actor: extractSystemEventActor(event2),
|
|
64808
|
+
payload_json: safeStringify(normalizeSystemEventPayload(entry, event2, options.secretValues)),
|
|
64809
|
+
...extractSystemEventCorrelation(event2)
|
|
64810
|
+
}, quotas, logger3);
|
|
64026
64811
|
inFlight.add(p);
|
|
64027
64812
|
p.finally(() => inFlight.delete(p));
|
|
64028
64813
|
};
|
|
@@ -64043,18 +64828,15 @@ function registerSystemEventTap(bus, dao2, logger3, options = {}) {
|
|
|
64043
64828
|
}
|
|
64044
64829
|
};
|
|
64045
64830
|
}
|
|
64046
|
-
async function persist(dao2,
|
|
64831
|
+
async function persist(dao2, prefix, input, quotas, logger3) {
|
|
64047
64832
|
try {
|
|
64048
|
-
await dao2.insert(
|
|
64049
|
-
|
|
64050
|
-
event_name: name,
|
|
64051
|
-
occurred_at: occurredAt,
|
|
64052
|
-
actor,
|
|
64053
|
-
payload_json: payloadJson
|
|
64054
|
-
});
|
|
64055
|
-
await dao2.prune(SYSTEM_EVENTS_CAP);
|
|
64833
|
+
await dao2.insert(input);
|
|
64834
|
+
await dao2.pruneQuotas(quotas, prefix);
|
|
64056
64835
|
} catch (error51) {
|
|
64057
|
-
logger3.warn("system_events tap: persist failed", {
|
|
64836
|
+
logger3.warn("system_events tap: persist failed", {
|
|
64837
|
+
name: input.event_name,
|
|
64838
|
+
error: stringifyError(error51)
|
|
64839
|
+
});
|
|
64058
64840
|
}
|
|
64059
64841
|
}
|
|
64060
64842
|
function safeStringify(event2) {
|
|
@@ -64071,27 +64853,52 @@ function extractSystemEventActor(event2) {
|
|
|
64071
64853
|
return obj.actor;
|
|
64072
64854
|
if (typeof obj.agentId === "string" && obj.agentId.length > 0)
|
|
64073
64855
|
return obj.agentId;
|
|
64856
|
+
if (typeof obj.memberId === "string" && obj.memberId.length > 0)
|
|
64857
|
+
return obj.memberId;
|
|
64074
64858
|
}
|
|
64075
64859
|
return null;
|
|
64076
64860
|
}
|
|
64861
|
+
function extractSystemEventCorrelation(event2) {
|
|
64862
|
+
const correlation = { run_id: null, entity_kind: null, entity_id: null, sequence: null };
|
|
64863
|
+
if (!event2 || typeof event2 !== "object")
|
|
64864
|
+
return correlation;
|
|
64865
|
+
const obj = event2;
|
|
64866
|
+
if (typeof obj.runId === "string" && obj.runId.length > 0)
|
|
64867
|
+
correlation.run_id = obj.runId;
|
|
64868
|
+
if (typeof obj.sequence === "number" && Number.isFinite(obj.sequence))
|
|
64869
|
+
correlation.sequence = obj.sequence;
|
|
64870
|
+
const entity2 = obj.entity;
|
|
64871
|
+
if (entity2 && typeof entity2 === "object") {
|
|
64872
|
+
const { kind, id } = entity2;
|
|
64873
|
+
if (typeof kind === "string" && kind.length > 0)
|
|
64874
|
+
correlation.entity_kind = kind;
|
|
64875
|
+
if (typeof id === "string" && id.length > 0)
|
|
64876
|
+
correlation.entity_id = id;
|
|
64877
|
+
}
|
|
64878
|
+
return correlation;
|
|
64879
|
+
}
|
|
64077
64880
|
function stringifyError(error51) {
|
|
64078
64881
|
if (error51 instanceof Error)
|
|
64079
64882
|
return `${error51.name}: ${error51.message}`;
|
|
64080
64883
|
return String(error51);
|
|
64081
64884
|
}
|
|
64082
|
-
var SYSTEM_EVENTS_CAP = 1e4;
|
|
64083
64885
|
var init_system_event_tap = __esm(() => {
|
|
64084
64886
|
init_src2();
|
|
64085
64887
|
init_event_names();
|
|
64888
|
+
init_system_event_retention();
|
|
64086
64889
|
});
|
|
64087
64890
|
|
|
64088
64891
|
// ../../packages/app/src/services/system-event-emitter.ts
|
|
64089
64892
|
class SystemEventEmitter {
|
|
64090
64893
|
dao;
|
|
64091
64894
|
logger;
|
|
64092
|
-
|
|
64895
|
+
secretValues;
|
|
64896
|
+
quotas;
|
|
64897
|
+
constructor(dao2, logger3, retention = {}, secretValues = []) {
|
|
64093
64898
|
this.dao = dao2;
|
|
64094
64899
|
this.logger = logger3;
|
|
64900
|
+
this.secretValues = secretValues;
|
|
64901
|
+
this.quotas = resolveRetentionQuotas(retention);
|
|
64095
64902
|
}
|
|
64096
64903
|
async emit(event2) {
|
|
64097
64904
|
const entry = systemEventCatalogEntry(event2.event);
|
|
@@ -64103,9 +64910,10 @@ class SystemEventEmitter {
|
|
|
64103
64910
|
event_name: entry.name,
|
|
64104
64911
|
occurred_at: event2.at,
|
|
64105
64912
|
actor: extractSystemEventActor(event2),
|
|
64106
|
-
payload_json: safeStringify(normalizeSystemEventPayload(entry, event2))
|
|
64913
|
+
payload_json: safeStringify(normalizeSystemEventPayload(entry, event2, this.secretValues)),
|
|
64914
|
+
...extractSystemEventCorrelation(event2)
|
|
64107
64915
|
});
|
|
64108
|
-
await this.dao.
|
|
64916
|
+
await this.dao.pruneQuotas(this.quotas, entry.prefix);
|
|
64109
64917
|
} catch (error51) {
|
|
64110
64918
|
this.logger.warn("system_events emitter: persist failed", {
|
|
64111
64919
|
event: event2.event,
|
|
@@ -64117,11 +64925,12 @@ class SystemEventEmitter {
|
|
|
64117
64925
|
var init_system_event_emitter = __esm(() => {
|
|
64118
64926
|
init_src2();
|
|
64119
64927
|
init_event_names();
|
|
64928
|
+
init_system_event_retention();
|
|
64120
64929
|
init_system_event_tap();
|
|
64121
64930
|
});
|
|
64122
64931
|
|
|
64123
64932
|
// ../../packages/app/src/services/task-check.ts
|
|
64124
|
-
import { dirname as
|
|
64933
|
+
import { dirname as dirname10, join as join11 } from "path";
|
|
64125
64934
|
function isPlaceholderBody(body) {
|
|
64126
64935
|
const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*>\s*TBD\s*$/gim, "").trim();
|
|
64127
64936
|
return stripped.length === 0;
|
|
@@ -64182,9 +64991,9 @@ function isProseOnlyReview(body) {
|
|
|
64182
64991
|
function resolveProjectRootFromTasksDir(tasksDir) {
|
|
64183
64992
|
const norm = tasksDir.replace(/\\/g, "/");
|
|
64184
64993
|
if (/\/docs\/tasks\d*$/.test(norm) || /\/docs\/tasks$/.test(norm)) {
|
|
64185
|
-
return
|
|
64994
|
+
return dirname10(dirname10(tasksDir));
|
|
64186
64995
|
}
|
|
64187
|
-
return
|
|
64996
|
+
return dirname10(tasksDir);
|
|
64188
64997
|
}
|
|
64189
64998
|
function extractBacktickLineAnchors(body) {
|
|
64190
64999
|
const out = [];
|
|
@@ -64269,8 +65078,8 @@ var init_task_check = __esm(() => {
|
|
|
64269
65078
|
const entry = this.resolveMatrixEntry(variant, status);
|
|
64270
65079
|
this.runL2(doc2, entry, findings);
|
|
64271
65080
|
this.runL3(doc2, entry, status, findings);
|
|
64272
|
-
const tasksDir =
|
|
64273
|
-
const featuresDir =
|
|
65081
|
+
const tasksDir = dirname10(filePath);
|
|
65082
|
+
const featuresDir = join11(dirname10(tasksDir), "features");
|
|
64274
65083
|
await this.runL4(doc2, fm, status, findings, featuresDir, tasksDir);
|
|
64275
65084
|
await this.runL4Rollup(doc2, wbs, status, findings, tasksDir);
|
|
64276
65085
|
if (status !== "done" && status !== "cancelled") {
|
|
@@ -64651,7 +65460,7 @@ var init_task_check = __esm(() => {
|
|
|
64651
65460
|
for (const cite of citations) {
|
|
64652
65461
|
if (reported >= 5)
|
|
64653
65462
|
break;
|
|
64654
|
-
const abs =
|
|
65463
|
+
const abs = join11(projectRoot, cite.path);
|
|
64655
65464
|
let exists2 = false;
|
|
64656
65465
|
try {
|
|
64657
65466
|
exists2 = await this.fs.exists(abs);
|
|
@@ -64769,7 +65578,6 @@ var init_task_check = __esm(() => {
|
|
|
64769
65578
|
});
|
|
64770
65579
|
|
|
64771
65580
|
// ../../packages/app/src/services/task-record.ts
|
|
64772
|
-
import { execSync } from "child_process";
|
|
64773
65581
|
function escapeTablePipe(s) {
|
|
64774
65582
|
return s.replace(/\|/g, "\\|");
|
|
64775
65583
|
}
|
|
@@ -64953,19 +65761,23 @@ function extractChangedFiles(diffText) {
|
|
|
64953
65761
|
}
|
|
64954
65762
|
function gitDiffU0(cwd) {
|
|
64955
65763
|
try {
|
|
64956
|
-
|
|
64957
|
-
|
|
64958
|
-
|
|
64959
|
-
|
|
65764
|
+
const result = new BunSyncProcessExecutor2().runSync({
|
|
65765
|
+
command: "git",
|
|
65766
|
+
args: ["diff", "-U0", "HEAD", "--", "*.ts", "*.tsx", "*.js"],
|
|
65767
|
+
...cwd !== undefined ? { cwd } : {},
|
|
65768
|
+
rejectOnError: false
|
|
64960
65769
|
});
|
|
65770
|
+
return result.exitCode === 0 ? result.stdout : "";
|
|
64961
65771
|
} catch {
|
|
64962
65772
|
return "";
|
|
64963
65773
|
}
|
|
64964
65774
|
}
|
|
64965
|
-
var init_task_record = () => {
|
|
65775
|
+
var init_task_record = __esm(() => {
|
|
65776
|
+
init_dist5();
|
|
65777
|
+
});
|
|
64966
65778
|
|
|
64967
65779
|
// ../../packages/app/src/services/task-scaffold.ts
|
|
64968
|
-
import { dirname as
|
|
65780
|
+
import { dirname as dirname11 } from "path";
|
|
64969
65781
|
|
|
64970
65782
|
class TaskScaffoldService {
|
|
64971
65783
|
ctx;
|
|
@@ -65008,7 +65820,7 @@ class TaskScaffoldService {
|
|
|
65008
65820
|
fileExists = false;
|
|
65009
65821
|
}
|
|
65010
65822
|
const mergeResult = mergeStubs(existingContent, stubs);
|
|
65011
|
-
const dir =
|
|
65823
|
+
const dir = dirname11(targetFile);
|
|
65012
65824
|
await this.ctx.fs.ensureDir(dir);
|
|
65013
65825
|
if (mergeResult.created > 0 || !fileExists) {
|
|
65014
65826
|
await this.ctx.fs.writeFile(targetFile, mergeResult.content);
|
|
@@ -65770,8 +66582,8 @@ ${block}` : block);
|
|
|
65770
66582
|
return { wbs: hit.wbs, filePath: hit.filePath };
|
|
65771
66583
|
if (opts.strict === true)
|
|
65772
66584
|
return null;
|
|
65773
|
-
const
|
|
65774
|
-
const capturedWbs = /^(\d{4})_.+\.md$/.exec(
|
|
66585
|
+
const basename3 = filePath.split("/").pop() ?? "";
|
|
66586
|
+
const capturedWbs = /^(\d{4})_.+\.md$/.exec(basename3)?.[1];
|
|
65775
66587
|
if (capturedWbs) {
|
|
65776
66588
|
const taskPath = await this.locator.findPathByWbs(capturedWbs);
|
|
65777
66589
|
if (taskPath !== null)
|
|
@@ -66145,7 +66957,7 @@ var init_task_verdict = __esm(() => {
|
|
|
66145
66957
|
});
|
|
66146
66958
|
|
|
66147
66959
|
// ../../packages/app/src/services/team-service.ts
|
|
66148
|
-
import { join as
|
|
66960
|
+
import { join as join12 } from "path";
|
|
66149
66961
|
|
|
66150
66962
|
class TeamService {
|
|
66151
66963
|
ctx;
|
|
@@ -66153,7 +66965,7 @@ class TeamService {
|
|
|
66153
66965
|
orchestratorPromise;
|
|
66154
66966
|
constructor(ctx) {
|
|
66155
66967
|
this.ctx = ctx;
|
|
66156
|
-
this.configDir =
|
|
66968
|
+
this.configDir = join12(ctx.cwd, ".spur", "agents");
|
|
66157
66969
|
}
|
|
66158
66970
|
async sendMessage(fromId, toId, body, replyTo) {
|
|
66159
66971
|
validateAgentId(toId);
|
|
@@ -66298,6 +67110,14 @@ class TeamService {
|
|
|
66298
67110
|
const doc2 = MarkdownDocument.parse(source, "task");
|
|
66299
67111
|
doc2.setFrontmatterField("assignee", agentId);
|
|
66300
67112
|
await atomicWriteAsync(path9, doc2.serialize(), taskId, fs3);
|
|
67113
|
+
const identity6 = await this.resolveMemberIdentity(agentId);
|
|
67114
|
+
this.emitTeamMemberEvent("team.member.assigned", {
|
|
67115
|
+
teamId: identity6.teamId,
|
|
67116
|
+
memberId: agentId,
|
|
67117
|
+
agentType: identity6.agentType,
|
|
67118
|
+
outcome: "assigned",
|
|
67119
|
+
taskId
|
|
67120
|
+
});
|
|
66301
67121
|
}
|
|
66302
67122
|
async createAgentSpec(input) {
|
|
66303
67123
|
validateAgentId(input.id);
|
|
@@ -66437,12 +67257,18 @@ class TeamService {
|
|
|
66437
67257
|
for (const spec of orphaned) {
|
|
66438
67258
|
await deleteAgentSpec(spec.id, this.configDir);
|
|
66439
67259
|
}
|
|
66440
|
-
|
|
67260
|
+
const result = {
|
|
66441
67261
|
teamId,
|
|
66442
67262
|
upserted: toUpsert.map((s) => s.id),
|
|
66443
67263
|
orphaned: orphaned.map((s) => s.id),
|
|
66444
67264
|
written: true
|
|
66445
67265
|
};
|
|
67266
|
+
this.emitTeamLifecycleEvent("team.up", {
|
|
67267
|
+
teamId,
|
|
67268
|
+
memberCount: result.upserted.length,
|
|
67269
|
+
outcome: "ok"
|
|
67270
|
+
});
|
|
67271
|
+
return result;
|
|
66446
67272
|
}
|
|
66447
67273
|
async teardownTeam(teamId, opts) {
|
|
66448
67274
|
const specs = await loadAgentSpecs(this.configDir);
|
|
@@ -66453,11 +67279,17 @@ class TeamService {
|
|
|
66453
67279
|
await deleteAgentSpec(spec.id, this.configDir);
|
|
66454
67280
|
}
|
|
66455
67281
|
}
|
|
66456
|
-
|
|
67282
|
+
const result = {
|
|
66457
67283
|
teamId,
|
|
66458
67284
|
purged: opts?.purge ? generated.map((s) => s.id) : [],
|
|
66459
67285
|
stopped: teamSpecs.map((s) => s.id)
|
|
66460
67286
|
};
|
|
67287
|
+
this.emitTeamLifecycleEvent("team.down", {
|
|
67288
|
+
teamId,
|
|
67289
|
+
memberCount: result.stopped.length,
|
|
67290
|
+
outcome: opts?.purge ? "purged" : "ok"
|
|
67291
|
+
});
|
|
67292
|
+
return result;
|
|
66461
67293
|
}
|
|
66462
67294
|
async loadTeamConfig() {
|
|
66463
67295
|
try {
|
|
@@ -66478,18 +67310,72 @@ class TeamService {
|
|
|
66478
67310
|
bus.emit(name, payload);
|
|
66479
67311
|
} catch {}
|
|
66480
67312
|
}
|
|
67313
|
+
emitTeamLifecycleEvent(name, payload) {
|
|
67314
|
+
const bus = this.ctx.eventBus;
|
|
67315
|
+
if (!bus)
|
|
67316
|
+
return;
|
|
67317
|
+
try {
|
|
67318
|
+
bus.emit(name, payload);
|
|
67319
|
+
} catch {}
|
|
67320
|
+
}
|
|
67321
|
+
emitTeamMemberEvent(name, payload) {
|
|
67322
|
+
const bus = this.ctx.eventBus;
|
|
67323
|
+
if (!bus)
|
|
67324
|
+
return;
|
|
67325
|
+
try {
|
|
67326
|
+
bus.emit(name, payload);
|
|
67327
|
+
} catch {}
|
|
67328
|
+
}
|
|
67329
|
+
async resolveMemberIdentity(agentId) {
|
|
67330
|
+
try {
|
|
67331
|
+
const specs = await loadAgentSpecs(this.configDir);
|
|
67332
|
+
const spec = specs.find((s) => s.id === agentId);
|
|
67333
|
+
if (!spec)
|
|
67334
|
+
return { teamId: null, agentType: null };
|
|
67335
|
+
const teamTag = spec.tags?.find((t) => t.startsWith("team:"));
|
|
67336
|
+
return {
|
|
67337
|
+
teamId: teamTag ? teamTag.slice("team:".length) : null,
|
|
67338
|
+
agentType: typeof spec.type === "string" && spec.type.length > 0 ? spec.type : null
|
|
67339
|
+
};
|
|
67340
|
+
} catch {
|
|
67341
|
+
return { teamId: null, agentType: null };
|
|
67342
|
+
}
|
|
67343
|
+
}
|
|
66481
67344
|
async inboxRecentDao() {
|
|
66482
67345
|
const db2 = await this.ctx.getDb();
|
|
66483
67346
|
return new InboxRecentDao(db2);
|
|
66484
67347
|
}
|
|
66485
67348
|
orchestrator() {
|
|
66486
|
-
this.orchestratorPromise ??= this.inboxDao().then((dao2) =>
|
|
67349
|
+
this.orchestratorPromise ??= this.inboxDao().then((dao2) => {
|
|
67350
|
+
const orch = new TeamOrchestrator(this.configDir, dao2, { events: this.ctx.events });
|
|
67351
|
+
orch.on("agent.started", (event2) => {
|
|
67352
|
+
this.resolveMemberIdentity(event2.agentId).then((identity6) => {
|
|
67353
|
+
this.emitTeamMemberEvent("team.member.started", {
|
|
67354
|
+
teamId: identity6.teamId,
|
|
67355
|
+
memberId: event2.agentId,
|
|
67356
|
+
agentType: event2.agentType ?? identity6.agentType,
|
|
67357
|
+
outcome: "started"
|
|
67358
|
+
});
|
|
67359
|
+
});
|
|
67360
|
+
});
|
|
67361
|
+
orch.on("agent.stopped", (event2) => {
|
|
67362
|
+
this.resolveMemberIdentity(event2.agentId).then((identity6) => {
|
|
67363
|
+
this.emitTeamMemberEvent("team.member.stopped", {
|
|
67364
|
+
teamId: identity6.teamId,
|
|
67365
|
+
memberId: event2.agentId,
|
|
67366
|
+
agentType: identity6.agentType,
|
|
67367
|
+
outcome: "stopped"
|
|
67368
|
+
});
|
|
67369
|
+
});
|
|
67370
|
+
});
|
|
67371
|
+
return orch;
|
|
67372
|
+
});
|
|
66487
67373
|
return this.orchestratorPromise;
|
|
66488
67374
|
}
|
|
66489
67375
|
async resolveTaskFile(taskId) {
|
|
66490
67376
|
const fs3 = this.ctx.fs;
|
|
66491
67377
|
const { foldersConfig } = await resolvePlanningFolders(fs3);
|
|
66492
|
-
const dirs = [...new Set([foldersConfig.active_folder, ...Object.keys(foldersConfig.folders)])].map((dir) =>
|
|
67378
|
+
const dirs = [...new Set([foldersConfig.active_folder, ...Object.keys(foldersConfig.folders)])].map((dir) => join12(this.ctx.cwd, dir));
|
|
66493
67379
|
return await TaskLocator.forDirs(fs3, dirs).findPathByWbs(taskId);
|
|
66494
67380
|
}
|
|
66495
67381
|
}
|
|
@@ -66525,8 +67411,8 @@ var init_team_service = __esm(() => {
|
|
|
66525
67411
|
});
|
|
66526
67412
|
|
|
66527
67413
|
// ../../packages/app/src/services/token-ledger-service.ts
|
|
66528
|
-
import { closeSync as closeSync2, existsSync as
|
|
66529
|
-
import { join as
|
|
67414
|
+
import { closeSync as closeSync2, existsSync as existsSync7, fstatSync, openSync as openSync2, readSync } from "fs";
|
|
67415
|
+
import { join as join13 } from "path";
|
|
66530
67416
|
function isSparseToolActivity(events2) {
|
|
66531
67417
|
return events2.length === 0 || !events2.some((e) => TOOL_ACTIVITY_TYPES.has(e.type));
|
|
66532
67418
|
}
|
|
@@ -66577,7 +67463,7 @@ function parseLedgerLine(line) {
|
|
|
66577
67463
|
}
|
|
66578
67464
|
}
|
|
66579
67465
|
function tailTokenLedgerFile(filePath, limit, chunkSize = DEFAULT_CHUNK, before) {
|
|
66580
|
-
if (!
|
|
67466
|
+
if (!existsSync7(filePath)) {
|
|
66581
67467
|
return { events: [], truncated: false };
|
|
66582
67468
|
}
|
|
66583
67469
|
const target = limit + 1;
|
|
@@ -66627,7 +67513,7 @@ class TokenLedgerService {
|
|
|
66627
67513
|
ledgerPath;
|
|
66628
67514
|
chunkSize;
|
|
66629
67515
|
constructor(options) {
|
|
66630
|
-
this.ledgerPath = options.ledgerPath ??
|
|
67516
|
+
this.ledgerPath = options.ledgerPath ?? join13(options.cwd, TOKEN_LEDGER_RELATIVE_PATH);
|
|
66631
67517
|
this.chunkSize = options.chunkSize ?? DEFAULT_CHUNK;
|
|
66632
67518
|
}
|
|
66633
67519
|
get path() {
|
|
@@ -66668,12 +67554,12 @@ class TokenLedgerService {
|
|
|
66668
67554
|
var TOOL_ACTIVITY_TYPES, TOKEN_LEDGER_DEFAULT_LIMIT = 200, TOKEN_LEDGER_MAX_LIMIT = 1000, TOKEN_LEDGER_RELATIVE_PATH, DEFAULT_CHUNK;
|
|
66669
67555
|
var init_token_ledger_service = __esm(() => {
|
|
66670
67556
|
TOOL_ACTIVITY_TYPES = new Set(["read", "write", "bash", "grep", "glob"]);
|
|
66671
|
-
TOKEN_LEDGER_RELATIVE_PATH =
|
|
67557
|
+
TOKEN_LEDGER_RELATIVE_PATH = join13(".spur", "context", "token-ledger.jsonl");
|
|
66672
67558
|
DEFAULT_CHUNK = 64 * 1024;
|
|
66673
67559
|
});
|
|
66674
67560
|
|
|
66675
67561
|
// ../../packages/app/src/services/token-ledger-watcher.ts
|
|
66676
|
-
import { closeSync as closeSync3, existsSync as
|
|
67562
|
+
import { closeSync as closeSync3, existsSync as existsSync8, fstatSync as fstatSync2, openSync as openSync3, readSync as readSync2, watch } from "fs";
|
|
66677
67563
|
|
|
66678
67564
|
class TokenLedgerWatcher {
|
|
66679
67565
|
ledgerPath;
|
|
@@ -66694,7 +67580,7 @@ class TokenLedgerWatcher {
|
|
|
66694
67580
|
this.started = true;
|
|
66695
67581
|
this.offset = this.currentSize();
|
|
66696
67582
|
try {
|
|
66697
|
-
const target =
|
|
67583
|
+
const target = existsSync8(this.ledgerPath) ? this.ledgerPath : this.parentDir();
|
|
66698
67584
|
this.watcher = watch(target, () => this.schedulePoll());
|
|
66699
67585
|
} catch {
|
|
66700
67586
|
this.watcher = undefined;
|
|
@@ -66729,7 +67615,7 @@ class TokenLedgerWatcher {
|
|
|
66729
67615
|
return idx > 0 ? this.ledgerPath.slice(0, idx) : ".";
|
|
66730
67616
|
}
|
|
66731
67617
|
currentSize() {
|
|
66732
|
-
if (!
|
|
67618
|
+
if (!existsSync8(this.ledgerPath))
|
|
66733
67619
|
return 0;
|
|
66734
67620
|
try {
|
|
66735
67621
|
const fd = openSync3(this.ledgerPath, "r");
|
|
@@ -66751,7 +67637,7 @@ class TokenLedgerWatcher {
|
|
|
66751
67637
|
}, this.debounceMs);
|
|
66752
67638
|
}
|
|
66753
67639
|
pollNewBytes() {
|
|
66754
|
-
if (!
|
|
67640
|
+
if (!existsSync8(this.ledgerPath)) {
|
|
66755
67641
|
this.offset = 0;
|
|
66756
67642
|
this.carry = "";
|
|
66757
67643
|
return;
|
|
@@ -66842,16 +67728,16 @@ class WorkflowSteeringController {
|
|
|
66842
67728
|
active.state = "boundary";
|
|
66843
67729
|
active.version += 1;
|
|
66844
67730
|
this.publishState(active);
|
|
66845
|
-
return await new Promise((
|
|
67731
|
+
return await new Promise((resolve4) => {
|
|
66846
67732
|
const settle = (decision) => {
|
|
66847
67733
|
clearTimeout(timer);
|
|
66848
|
-
|
|
67734
|
+
resolve4(decision);
|
|
66849
67735
|
};
|
|
66850
67736
|
const timer = setTimeout(() => {
|
|
66851
67737
|
if (active.resolve !== settle)
|
|
66852
67738
|
return;
|
|
66853
67739
|
active.resolve = undefined;
|
|
66854
|
-
this.acceptDefault(active,
|
|
67740
|
+
this.acceptDefault(active, resolve4);
|
|
66855
67741
|
}, active.policy.timeoutMs);
|
|
66856
67742
|
timer.unref?.();
|
|
66857
67743
|
active.resolve = settle;
|
|
@@ -66949,7 +67835,7 @@ class WorkflowSteeringController {
|
|
|
66949
67835
|
deadlineAt: new Date(Date.now() + deadlineMs).toISOString()
|
|
66950
67836
|
});
|
|
66951
67837
|
}
|
|
66952
|
-
acceptDefault(active,
|
|
67838
|
+
acceptDefault(active, resolve4) {
|
|
66953
67839
|
const command = {
|
|
66954
67840
|
commandId: crypto.randomUUID(),
|
|
66955
67841
|
runId: active.runId,
|
|
@@ -66974,7 +67860,7 @@ class WorkflowSteeringController {
|
|
|
66974
67860
|
at: new Date().toISOString()
|
|
66975
67861
|
};
|
|
66976
67862
|
this.onAck?.(ack);
|
|
66977
|
-
|
|
67863
|
+
resolve4({ operation: "continue" });
|
|
66978
67864
|
}
|
|
66979
67865
|
accept(command, active, note) {
|
|
66980
67866
|
return this.publish({
|
|
@@ -67037,11 +67923,7 @@ var init_steering = __esm(() => {
|
|
|
67037
67923
|
});
|
|
67038
67924
|
|
|
67039
67925
|
// ../../packages/app/src/workflow/actions/agent-run.ts
|
|
67040
|
-
import {
|
|
67041
|
-
import { existsSync as existsSync7 } from "fs";
|
|
67042
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
67043
|
-
import { dirname as dirname11, isAbsolute as isAbsolute2, join as join12 } from "path";
|
|
67044
|
-
import { promisify as promisify3 } from "util";
|
|
67926
|
+
import { dirname as dirname12, isAbsolute as isAbsolute2, join as join14 } from "path";
|
|
67045
67927
|
|
|
67046
67928
|
class AgentRunActionRunner {
|
|
67047
67929
|
observabilityBus;
|
|
@@ -67135,13 +68017,15 @@ class AgentRunActionRunner {
|
|
|
67135
68017
|
const ok = exitCode === 0;
|
|
67136
68018
|
const invocation = traced.invocation;
|
|
67137
68019
|
if (capture && answerFile !== undefined) {
|
|
67138
|
-
const target = isAbsolute2(answerFile) ? answerFile :
|
|
67139
|
-
|
|
67140
|
-
await
|
|
68020
|
+
const target = isAbsolute2(answerFile) ? answerFile : join14(cwd, answerFile);
|
|
68021
|
+
const fs3 = createNodeFileSystem3(cwd);
|
|
68022
|
+
await fs3.ensureDir(dirname12(target));
|
|
68023
|
+
await fs3.writeFile(target, answer);
|
|
67141
68024
|
}
|
|
67142
68025
|
if (ok && expectFile !== undefined) {
|
|
67143
|
-
const target = isAbsolute2(expectFile) ? expectFile :
|
|
67144
|
-
|
|
68026
|
+
const target = isAbsolute2(expectFile) ? expectFile : join14(cwd, expectFile);
|
|
68027
|
+
const fs3 = createNodeFileSystem3(cwd);
|
|
68028
|
+
if (!await fs3.exists(target)) {
|
|
67145
68029
|
return {
|
|
67146
68030
|
ok: false,
|
|
67147
68031
|
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
@@ -67248,15 +68132,23 @@ async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd
|
|
|
67248
68132
|
""
|
|
67249
68133
|
].join(`
|
|
67250
68134
|
`);
|
|
67251
|
-
const target =
|
|
67252
|
-
|
|
67253
|
-
await
|
|
68135
|
+
const target = join14(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
|
|
68136
|
+
const fs3 = createNodeFileSystem3(cwd);
|
|
68137
|
+
await fs3.ensureDir(dirname12(target));
|
|
68138
|
+
await fs3.writeFile(target, body);
|
|
67254
68139
|
} catch {}
|
|
67255
68140
|
}
|
|
67256
68141
|
async function gitDiffStat(cwd) {
|
|
67257
68142
|
try {
|
|
67258
|
-
const
|
|
67259
|
-
|
|
68143
|
+
const result = await new NodeProcessExecutor3().run({
|
|
68144
|
+
command: "git",
|
|
68145
|
+
args: ["diff", "--stat"],
|
|
68146
|
+
cwd,
|
|
68147
|
+
maxOutput: 1024 * 1024,
|
|
68148
|
+
forceBuffered: true,
|
|
68149
|
+
rejectOnError: false
|
|
68150
|
+
});
|
|
68151
|
+
return result.exitCode === 0 ? result.stdout.trim() : "";
|
|
67260
68152
|
} catch {
|
|
67261
68153
|
return "";
|
|
67262
68154
|
}
|
|
@@ -67267,10 +68159,10 @@ function tail(text4, maxChars) {
|
|
|
67267
68159
|
return `... (truncated) ...
|
|
67268
68160
|
${text4.slice(text4.length - maxChars)}`;
|
|
67269
68161
|
}
|
|
67270
|
-
var
|
|
68162
|
+
var PARTIAL_ARTIFACT_TAIL_CHARS = 4000, KIND = "agent.run";
|
|
67271
68163
|
var init_agent_run = __esm(() => {
|
|
68164
|
+
init_dist5();
|
|
67272
68165
|
init_steering();
|
|
67273
|
-
execFileAsync = promisify3(execFile);
|
|
67274
68166
|
});
|
|
67275
68167
|
|
|
67276
68168
|
// ../../packages/app/src/workflow/actions/file-exists.ts
|
|
@@ -67326,17 +68218,17 @@ class FileReadActionRunner {
|
|
|
67326
68218
|
}
|
|
67327
68219
|
const maxSize = asNumber(options.maxSize);
|
|
67328
68220
|
const resolved = joinPath2(context4.workdir ?? ".", rawPath);
|
|
67329
|
-
const
|
|
67330
|
-
if (
|
|
68221
|
+
const stat = await this.fileSystem.stat(resolved);
|
|
68222
|
+
if (stat === null) {
|
|
67331
68223
|
return { ok: false, error: `file.read: file not found: ${resolved}` };
|
|
67332
68224
|
}
|
|
67333
|
-
if (maxSize !== undefined &&
|
|
67334
|
-
return { ok: false, error: `file.read: file too large (${
|
|
68225
|
+
if (maxSize !== undefined && stat.size > maxSize) {
|
|
68226
|
+
return { ok: false, error: `file.read: file too large (${stat.size} > ${maxSize}): ${resolved}` };
|
|
67335
68227
|
}
|
|
67336
68228
|
const content = await this.fileSystem.readFile(resolved);
|
|
67337
68229
|
return {
|
|
67338
68230
|
ok: true,
|
|
67339
|
-
data: { content, size:
|
|
68231
|
+
data: { content, size: stat.size, path: resolved }
|
|
67340
68232
|
};
|
|
67341
68233
|
}
|
|
67342
68234
|
}
|
|
@@ -67374,15 +68266,15 @@ class FileReadIntoVarActionRunner {
|
|
|
67374
68266
|
}
|
|
67375
68267
|
const trim = options.trim === undefined ? true : options.trim !== false;
|
|
67376
68268
|
const resolved = isAbsolutePath2(rawPath) ? normalizeSeparators2(rawPath) : joinPath2(context4.workdir ?? ".", rawPath);
|
|
67377
|
-
const
|
|
67378
|
-
if (
|
|
68269
|
+
const stat = await this.fileSystem.stat(resolved);
|
|
68270
|
+
if (stat === null) {
|
|
67379
68271
|
return { ok: false, error: `file.read.into-var: file not found: ${resolved}` };
|
|
67380
68272
|
}
|
|
67381
68273
|
const raw = await this.fileSystem.readFile(resolved);
|
|
67382
68274
|
const projected = trim ? raw.replace(/\s+$/u, "") : raw;
|
|
67383
68275
|
return {
|
|
67384
68276
|
ok: true,
|
|
67385
|
-
data: { content: projected, raw, var: varName, path: resolved, size:
|
|
68277
|
+
data: { content: projected, raw, var: varName, path: resolved, size: stat.size },
|
|
67386
68278
|
setVars: { [varName]: projected }
|
|
67387
68279
|
};
|
|
67388
68280
|
}
|
|
@@ -67826,7 +68718,7 @@ var init_builtins2 = __esm(() => {
|
|
|
67826
68718
|
// ../../packages/app/src/workflow/observability.ts
|
|
67827
68719
|
function bounded(value) {
|
|
67828
68720
|
const redacted = value.replace(SECRET_PATTERN2, "[REDACTED]");
|
|
67829
|
-
return redacted.length <=
|
|
68721
|
+
return redacted.length <= MAX_FIELD_LENGTH3 ? redacted : `${redacted.slice(0, MAX_FIELD_LENGTH3)}\u2026`;
|
|
67830
68722
|
}
|
|
67831
68723
|
function projectActionMetadata(kind, options) {
|
|
67832
68724
|
if (options === undefined)
|
|
@@ -67971,16 +68863,19 @@ class ObservableWorkflowAdapter {
|
|
|
67971
68863
|
return this.inner.listPausedRuns(options);
|
|
67972
68864
|
}
|
|
67973
68865
|
}
|
|
67974
|
-
var now = () => new Date().toISOString(),
|
|
68866
|
+
var now = () => new Date().toISOString(), MAX_FIELD_LENGTH3 = 256, SECRET_PATTERN2;
|
|
67975
68867
|
var init_observability = __esm(() => {
|
|
67976
68868
|
SECRET_PATTERN2 = /(?:sk-[a-z0-9_-]{8,}|(?:api[_-]?key|token|secret|password)\s*[:=]\s*\S+|bearer\s+\S+)/gi;
|
|
67977
68869
|
});
|
|
67978
68870
|
|
|
67979
68871
|
// ../../packages/app/src/services/workflow-service.ts
|
|
67980
|
-
import {
|
|
67981
|
-
import {
|
|
67982
|
-
import { join as join13, resolve as resolve2 } from "path";
|
|
68872
|
+
import { homedir as homedir5 } from "os";
|
|
68873
|
+
import { join as join15, resolve as resolve4 } from "path";
|
|
67983
68874
|
function signalSubprocess(pid) {
|
|
68875
|
+
if (!Number.isInteger(pid) || pid <= 1)
|
|
68876
|
+
return false;
|
|
68877
|
+
if (pid === process.pid || pid === process.ppid)
|
|
68878
|
+
return false;
|
|
67984
68879
|
try {
|
|
67985
68880
|
process.kill(-pid, "SIGTERM");
|
|
67986
68881
|
return true;
|
|
@@ -68045,7 +68940,7 @@ class WorkflowAppService {
|
|
|
68045
68940
|
};
|
|
68046
68941
|
}
|
|
68047
68942
|
async validate(file2, opts = {}) {
|
|
68048
|
-
const absolute =
|
|
68943
|
+
const absolute = resolve4(this.ctx.cwd, file2);
|
|
68049
68944
|
if (!await fileExists(absolute)) {
|
|
68050
68945
|
return { ok: false, valid: false, file: file2, errors: [`File not found: ${absolute}`] };
|
|
68051
68946
|
}
|
|
@@ -68096,7 +68991,7 @@ class WorkflowAppService {
|
|
|
68096
68991
|
return;
|
|
68097
68992
|
let workflowName;
|
|
68098
68993
|
try {
|
|
68099
|
-
const def = await loadWorkflowDef(
|
|
68994
|
+
const def = await loadWorkflowDef(resolve4(this.ctx.cwd, file2));
|
|
68100
68995
|
workflowName = def.name;
|
|
68101
68996
|
} catch {
|
|
68102
68997
|
return;
|
|
@@ -68156,7 +69051,8 @@ class WorkflowAppService {
|
|
|
68156
69051
|
return { runId, finalized, status: after?.status ?? "not_found", killed };
|
|
68157
69052
|
}
|
|
68158
69053
|
async continuePaused(runId) {
|
|
68159
|
-
const
|
|
69054
|
+
const eventsBus = this.ctx.events?.();
|
|
69055
|
+
const svc = await this.createEngineService({ events: eventsBus });
|
|
68160
69056
|
const run = await svc.listPausedRuns();
|
|
68161
69057
|
const target = run.find((r) => r.id === runId);
|
|
68162
69058
|
if (target === undefined) {
|
|
@@ -68166,7 +69062,10 @@ class WorkflowAppService {
|
|
|
68166
69062
|
if (workflow === null) {
|
|
68167
69063
|
throw new Error(`Cannot resume run "${runId}": workflow definition "${target.workflow_name}" not found in the workflow search paths.`);
|
|
68168
69064
|
}
|
|
68169
|
-
const result = await svc.resumeRun(workflow, runId, {
|
|
69065
|
+
const result = await svc.resumeRun(workflow, runId, {
|
|
69066
|
+
workdir: this.ctx.cwd,
|
|
69067
|
+
...eventsBus !== undefined ? { events: bridgeEventBus(eventsBus) } : {}
|
|
69068
|
+
});
|
|
68170
69069
|
await this.stampFailureReason(runId, result);
|
|
68171
69070
|
return result;
|
|
68172
69071
|
}
|
|
@@ -68183,7 +69082,7 @@ class WorkflowAppService {
|
|
|
68183
69082
|
if (entry === undefined)
|
|
68184
69083
|
return null;
|
|
68185
69084
|
for (const layer of listing.layers) {
|
|
68186
|
-
const abs =
|
|
69085
|
+
const abs = resolve4(layer.path, entry.path);
|
|
68187
69086
|
if (await fileExists(abs)) {
|
|
68188
69087
|
try {
|
|
68189
69088
|
return await loadWorkflowDef(abs, { validateSchema: false });
|
|
@@ -68194,12 +69093,12 @@ class WorkflowAppService {
|
|
|
68194
69093
|
}
|
|
68195
69094
|
async list(workflowPaths = [".spur/workflows/"]) {
|
|
68196
69095
|
const projectRoot = this.ctx.cwd;
|
|
68197
|
-
const globalRoot =
|
|
69096
|
+
const globalRoot = join15(homedir5(), ".config", "spur");
|
|
68198
69097
|
const layers = [];
|
|
68199
69098
|
const entries = [];
|
|
68200
69099
|
const scannedPaths = new Set;
|
|
68201
69100
|
for (const relPath of workflowPaths) {
|
|
68202
|
-
const absPath =
|
|
69101
|
+
const absPath = resolve4(projectRoot, relPath);
|
|
68203
69102
|
layers.push({ id: "project", path: absPath });
|
|
68204
69103
|
scannedPaths.add(absPath);
|
|
68205
69104
|
try {
|
|
@@ -68208,7 +69107,7 @@ class WorkflowAppService {
|
|
|
68208
69107
|
} catch {}
|
|
68209
69108
|
}
|
|
68210
69109
|
for (const relPath of workflowPaths) {
|
|
68211
|
-
const absPath =
|
|
69110
|
+
const absPath = resolve4(globalRoot, relPath);
|
|
68212
69111
|
if (scannedPaths.has(absPath))
|
|
68213
69112
|
continue;
|
|
68214
69113
|
layers.push({ id: "global", path: absPath });
|
|
@@ -68301,7 +69200,8 @@ class WorkflowAppService {
|
|
|
68301
69200
|
return { run, events: events2 };
|
|
68302
69201
|
}
|
|
68303
69202
|
async createEngineService(opts = {}) {
|
|
68304
|
-
const
|
|
69203
|
+
const processExec = this.ctx.processExecutor?.();
|
|
69204
|
+
const host = createDefaultWorkflowEngineHost(processExec !== undefined ? { processExecutor: processExec } : {});
|
|
68305
69205
|
const bus = this.ctx.observabilityBus?.();
|
|
68306
69206
|
registerSpurBuiltins(host, {
|
|
68307
69207
|
agentService: this.ctx.agentService(),
|
|
@@ -68322,39 +69222,40 @@ class WorkflowAppService {
|
|
|
68322
69222
|
}
|
|
68323
69223
|
}
|
|
68324
69224
|
async function fileExists(path9) {
|
|
68325
|
-
|
|
68326
|
-
|
|
68327
|
-
return true;
|
|
68328
|
-
} catch {
|
|
68329
|
-
return false;
|
|
68330
|
-
}
|
|
69225
|
+
const fs3 = createNodeFileSystem3();
|
|
69226
|
+
return await fs3.exists(path9);
|
|
68331
69227
|
}
|
|
68332
69228
|
async function scanWorkflowFiles(rootPath, source) {
|
|
68333
69229
|
const entries = [];
|
|
68334
|
-
|
|
68335
|
-
|
|
68336
|
-
|
|
68337
|
-
} catch {
|
|
69230
|
+
const fs3 = createNodeFileSystem3();
|
|
69231
|
+
const rootStat = await fs3.stat(rootPath);
|
|
69232
|
+
if (!rootStat?.isDirectory()) {
|
|
68338
69233
|
return entries;
|
|
68339
69234
|
}
|
|
68340
|
-
const realPath =
|
|
68341
|
-
|
|
68342
|
-
|
|
68343
|
-
|
|
68344
|
-
|
|
68345
|
-
|
|
68346
|
-
|
|
68347
|
-
|
|
68348
|
-
|
|
68349
|
-
|
|
68350
|
-
|
|
68351
|
-
|
|
69235
|
+
const realPath = fs3.realPath ? await fs3.realPath(rootPath) : rootPath;
|
|
69236
|
+
async function walk3(dirPath) {
|
|
69237
|
+
const names = await fs3.readDir(dirPath);
|
|
69238
|
+
for (const name of names) {
|
|
69239
|
+
const fullPath = resolve4(dirPath, name);
|
|
69240
|
+
const st = await fs3.stat(fullPath);
|
|
69241
|
+
if (!st)
|
|
69242
|
+
continue;
|
|
69243
|
+
if (st.isDirectory()) {
|
|
69244
|
+
await walk3(fullPath);
|
|
69245
|
+
} else if (st.isFile() && (name.endsWith(".yaml") || name.endsWith(".yml"))) {
|
|
69246
|
+
const displayPath = fullPath.startsWith(rootPath) ? fullPath.slice(rootPath.length + 1) : fullPath;
|
|
69247
|
+
const meta3 = await extractWorkflowMeta(fullPath);
|
|
69248
|
+
entries.push({ ...meta3, path: displayPath, source });
|
|
69249
|
+
}
|
|
69250
|
+
}
|
|
68352
69251
|
}
|
|
69252
|
+
await walk3(realPath);
|
|
68353
69253
|
return entries;
|
|
68354
69254
|
}
|
|
68355
69255
|
async function extractWorkflowMeta(filePath) {
|
|
68356
69256
|
try {
|
|
68357
|
-
const
|
|
69257
|
+
const fs3 = createNodeFileSystem3();
|
|
69258
|
+
const text4 = await fs3.readFile(filePath);
|
|
68358
69259
|
const parsed = parseYamlObject(text4);
|
|
68359
69260
|
if (typeof parsed !== "object" || parsed === null) {
|
|
68360
69261
|
return { name: "<unparseable>", kind: "unknown", valid: false, error: "Top-level value is not an object" };
|
|
@@ -68411,8 +69312,6 @@ var init_workflow_service = __esm(() => {
|
|
|
68411
69312
|
});
|
|
68412
69313
|
|
|
68413
69314
|
// ../../packages/app/src/workflow/lifecycle-adapter.ts
|
|
68414
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
68415
|
-
|
|
68416
69315
|
class LifecycleAdapter {
|
|
68417
69316
|
opts;
|
|
68418
69317
|
workflowCache;
|
|
@@ -68523,7 +69422,8 @@ class LifecycleAdapter {
|
|
|
68523
69422
|
return this.opts.readTaskMarkdown(ref);
|
|
68524
69423
|
}
|
|
68525
69424
|
try {
|
|
68526
|
-
|
|
69425
|
+
const fs3 = createNodeFileSystem3(this.opts.cwd);
|
|
69426
|
+
return await fs3.readFile(ref.filePath);
|
|
68527
69427
|
} catch {
|
|
68528
69428
|
return null;
|
|
68529
69429
|
}
|
|
@@ -68539,6 +69439,7 @@ var TASK_LIFECYCLE_PROFILE, FEATURE_LIFECYCLE_PROFILE;
|
|
|
68539
69439
|
var init_lifecycle_adapter = __esm(() => {
|
|
68540
69440
|
init_src2();
|
|
68541
69441
|
init_dist7();
|
|
69442
|
+
init_dist5();
|
|
68542
69443
|
init_task_check();
|
|
68543
69444
|
TASK_LIFECYCLE_PROFILE = {
|
|
68544
69445
|
linkKind: "lifecycle",
|
|
@@ -68636,8 +69537,7 @@ function formatDuration(ms) {
|
|
|
68636
69537
|
var isAgentExecution = (e) => ("executionId" in e), isActionStarted = (e) => ("kind" in e) && ("node" in e), isActionFinished = (e) => ("durationMs" in e) && ("ok" in e), isPhase = (e) => ("phase" in e), isTransition = (e) => ("from" in e) && ("to" in e);
|
|
68637
69538
|
|
|
68638
69539
|
// ../../packages/app/src/workflow/trace-writer.ts
|
|
68639
|
-
import {
|
|
68640
|
-
import { dirname as dirname12, join as join14 } from "path";
|
|
69540
|
+
import { dirname as dirname13, join as join16 } from "path";
|
|
68641
69541
|
|
|
68642
69542
|
class WorkflowTraceWriter {
|
|
68643
69543
|
path;
|
|
@@ -68645,7 +69545,7 @@ class WorkflowTraceWriter {
|
|
|
68645
69545
|
pending = Promise.resolve();
|
|
68646
69546
|
constructor(cwd, runId) {
|
|
68647
69547
|
const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
68648
|
-
this.path =
|
|
69548
|
+
this.path = join16(cwd, ".spur", "runs", "workflow", `${safeRunId}.jsonl`);
|
|
68649
69549
|
}
|
|
68650
69550
|
attach(bus) {
|
|
68651
69551
|
bus.on("workflow.run.started", (event2) => this.enqueue("workflow.run.started", event2));
|
|
@@ -68670,18 +69570,26 @@ class WorkflowTraceWriter {
|
|
|
68670
69570
|
})}
|
|
68671
69571
|
`;
|
|
68672
69572
|
this.pending = this.pending.then(async () => {
|
|
68673
|
-
|
|
68674
|
-
await
|
|
69573
|
+
const fs3 = createNodeFileSystem3();
|
|
69574
|
+
await fs3.ensureDir(dirname13(this.path));
|
|
69575
|
+
await fs3.appendFile(this.path, line);
|
|
68675
69576
|
});
|
|
68676
69577
|
}
|
|
68677
69578
|
}
|
|
68678
|
-
var init_trace_writer = () => {
|
|
69579
|
+
var init_trace_writer = __esm(() => {
|
|
69580
|
+
init_dist5();
|
|
69581
|
+
});
|
|
68679
69582
|
|
|
68680
69583
|
// ../../packages/app/src/index.ts
|
|
68681
69584
|
var exports_src2 = {};
|
|
68682
69585
|
__export(exports_src2, {
|
|
68683
69586
|
tailTokenLedgerFile: () => tailTokenLedgerFile,
|
|
68684
69587
|
systemEventCatalogEntry: () => systemEventCatalogEntry,
|
|
69588
|
+
summarizeActionResult: () => summarizeActionResult,
|
|
69589
|
+
startRegisteredProject: () => startRegisteredProject,
|
|
69590
|
+
setDetachedServeSpawnForTests: () => setDetachedServeSpawnForTests,
|
|
69591
|
+
resolveSpurServeCommand: () => resolveSpurServeCommand,
|
|
69592
|
+
resolveRetentionQuotas: () => resolveRetentionQuotas,
|
|
68685
69593
|
resolvePlanningFolders: () => resolvePlanningFolders,
|
|
68686
69594
|
resolveAutostartSet: () => resolveAutostartSet,
|
|
68687
69595
|
renderTesting: () => renderTesting,
|
|
@@ -68701,21 +69609,29 @@ __export(exports_src2, {
|
|
|
68701
69609
|
parseLedgerLine: () => parseLedgerLine,
|
|
68702
69610
|
parseEtimeToSeconds: () => parseEtimeToSeconds,
|
|
68703
69611
|
normalizeSystemEventPayload: () => normalizeSystemEventPayload,
|
|
69612
|
+
normalizeProjectPath: () => normalizeProjectPath,
|
|
69613
|
+
isPortLive: () => isPortLive,
|
|
69614
|
+
isPortAvailable: () => isPortAvailable,
|
|
68704
69615
|
isFindingCode: () => isFindingCode,
|
|
69616
|
+
isFeatureActionName: () => isFeatureActionName,
|
|
68705
69617
|
hitlConfirmDefault: () => hitlConfirmDefault,
|
|
68706
69618
|
hitlAutoApproveEnabled: () => hitlAutoApproveEnabled,
|
|
68707
69619
|
hasPopulatedPriorityTable: () => hasPopulatedPriorityTable,
|
|
68708
69620
|
formatNoopMessage: () => formatNoopMessage,
|
|
68709
69621
|
formatDenialMessage: () => formatDenialMessage,
|
|
69622
|
+
extractSystemEventCorrelation: () => extractSystemEventCorrelation,
|
|
68710
69623
|
extractSystemEventActor: () => extractSystemEventActor,
|
|
68711
69624
|
extractReviewSectionBody: () => extractReviewSectionBody,
|
|
68712
69625
|
evaluateDoneTransition: () => evaluateDoneTransition,
|
|
69626
|
+
encodeRunListCursor: () => encodeRunListCursor,
|
|
68713
69627
|
deriveVerdict: () => deriveVerdict,
|
|
69628
|
+
decodeRunListCursor: () => decodeRunListCursor,
|
|
68714
69629
|
createPsProcessInspector: () => createPsProcessInspector,
|
|
68715
69630
|
configuredSecretValues: () => configuredSecretValues,
|
|
68716
69631
|
computeAggregate: () => computeAggregate,
|
|
68717
69632
|
classifyTaskOutcome: () => classifyTaskOutcome,
|
|
68718
69633
|
clampToolUseLimit: () => clampToolUseLimit,
|
|
69634
|
+
clampRunStoreLimit: () => clampRunStoreLimit,
|
|
68719
69635
|
bridgeEventBus: () => bridgeEventBus,
|
|
68720
69636
|
aggregateBatchVerdicts: () => aggregateBatchVerdicts,
|
|
68721
69637
|
WorkflowTraceWriter: () => WorkflowTraceWriter,
|
|
@@ -68743,10 +69659,17 @@ __export(exports_src2, {
|
|
|
68743
69659
|
SYSTEM_EVENT_NAMES: () => SYSTEM_EVENT_NAMES,
|
|
68744
69660
|
SYSTEM_EVENT_CATALOG_METADATA: () => SYSTEM_EVENT_CATALOG_METADATA,
|
|
68745
69661
|
SYSTEM_EVENT_CATALOG: () => SYSTEM_EVENT_CATALOG,
|
|
68746
|
-
|
|
69662
|
+
RunStoreService: () => RunStoreService,
|
|
69663
|
+
RunStoreNotFoundError: () => RunStoreNotFoundError,
|
|
69664
|
+
RunStoreBadCursorError: () => RunStoreBadCursorError,
|
|
68747
69665
|
RuleService: () => RuleService,
|
|
68748
69666
|
RuleCheckActionRunner: () => RuleCheckActionRunner,
|
|
68749
69667
|
ResponseValidateActionRunner: () => ResponseValidateActionRunner,
|
|
69668
|
+
RUN_STORE_WBS_MAX_LIMIT: () => RUN_STORE_WBS_MAX_LIMIT,
|
|
69669
|
+
RUN_STORE_WBS_DEFAULT_LIMIT: () => RUN_STORE_WBS_DEFAULT_LIMIT,
|
|
69670
|
+
RUN_STORE_LIST_MAX_LIMIT: () => RUN_STORE_LIST_MAX_LIMIT,
|
|
69671
|
+
RUN_STORE_LIST_DEFAULT_LIMIT: () => RUN_STORE_LIST_DEFAULT_LIMIT,
|
|
69672
|
+
ProjectRegistry: () => ProjectRegistry,
|
|
68750
69673
|
ProcessInventoryService: () => ProcessInventoryService,
|
|
68751
69674
|
PluginService: () => PluginService,
|
|
68752
69675
|
PlanningWriteService: () => PlanningWriteService,
|
|
@@ -68768,8 +69691,11 @@ __export(exports_src2, {
|
|
|
68768
69691
|
FeatureCheckService: () => FeatureCheckService,
|
|
68769
69692
|
FINDING_CODES: () => FINDING_CODES,
|
|
68770
69693
|
FEATURE_LIFECYCLE_PROFILE: () => FEATURE_LIFECYCLE_PROFILE,
|
|
69694
|
+
FEATURE_ACTION_NAMES: () => FEATURE_ACTION_NAMES,
|
|
69695
|
+
FEATURE_ACTION_COMMANDS: () => FEATURE_ACTION_COMMANDS,
|
|
68771
69696
|
DuplicateFollowUpError: () => DuplicateFollowUpError,
|
|
68772
69697
|
DependencyMutationError: () => DependencyMutationError,
|
|
69698
|
+
DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA: () => DEFAULT_SYSTEM_EVENT_RETENTION_QUOTA,
|
|
68773
69699
|
DEFAULT_FEATURE_MATRIX: () => DEFAULT_FEATURE_MATRIX,
|
|
68774
69700
|
CorpusMigrator: () => CorpusMigrator,
|
|
68775
69701
|
BusPlanningEventEmitter: () => BusPlanningEventEmitter,
|
|
@@ -68793,9 +69719,13 @@ var init_src3 = __esm(() => {
|
|
|
68793
69719
|
init_planning_write_service();
|
|
68794
69720
|
init_process_inspector();
|
|
68795
69721
|
init_process_inventory_service();
|
|
69722
|
+
init_project_registry();
|
|
69723
|
+
init_project_start();
|
|
68796
69724
|
init_rule_service();
|
|
69725
|
+
init_run_store_service();
|
|
68797
69726
|
init_supervisor_service();
|
|
68798
69727
|
init_system_event_emitter();
|
|
69728
|
+
init_system_event_retention();
|
|
68799
69729
|
init_system_event_tap();
|
|
68800
69730
|
init_task_check();
|
|
68801
69731
|
init_task_locator();
|
|
@@ -76082,6 +77012,8 @@ Font modified May 20, 2012 by patorjk to add the 0xCA0 character
|
|
|
76082
77012
|
|
|
76083
77013
|
// src/commands/agent.ts
|
|
76084
77014
|
init_src3();
|
|
77015
|
+
init_dist6();
|
|
77016
|
+
init_dist5();
|
|
76085
77017
|
|
|
76086
77018
|
// src/output.ts
|
|
76087
77019
|
init_dist();
|
|
@@ -76097,6 +77029,40 @@ function toJson2(value) {
|
|
|
76097
77029
|
return JSON.stringify(value, null, 2);
|
|
76098
77030
|
}
|
|
76099
77031
|
|
|
77032
|
+
// src/system-event-ledger.ts
|
|
77033
|
+
init_src3();
|
|
77034
|
+
init_src2();
|
|
77035
|
+
async function attachSystemEventLedger(bus, context4, options = {}) {
|
|
77036
|
+
const logger3 = {
|
|
77037
|
+
warn: (msg, data) => {
|
|
77038
|
+
context4.output.error(`${msg}${data ? ` ${JSON.stringify(data)}` : ""}`);
|
|
77039
|
+
}
|
|
77040
|
+
};
|
|
77041
|
+
let tap;
|
|
77042
|
+
try {
|
|
77043
|
+
const dao2 = new SystemEventDao(await context4.getDb());
|
|
77044
|
+
tap = registerSystemEventTap(bus, dao2, logger3, {
|
|
77045
|
+
diagnosticEnabled: options.diagnosticEnabled === true,
|
|
77046
|
+
secretValues: configuredSecretValues(context4.env ?? {})
|
|
77047
|
+
});
|
|
77048
|
+
} catch (error51) {
|
|
77049
|
+
context4.output.error(`system_events ledger attach: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
77050
|
+
return {
|
|
77051
|
+
unsubscribe: () => {},
|
|
77052
|
+
flush: async () => {}
|
|
77053
|
+
};
|
|
77054
|
+
}
|
|
77055
|
+
return {
|
|
77056
|
+
unsubscribe: () => {
|
|
77057
|
+
tap?.unsubscribe();
|
|
77058
|
+
tap = undefined;
|
|
77059
|
+
},
|
|
77060
|
+
flush: async () => {
|
|
77061
|
+
await tap?.flush();
|
|
77062
|
+
}
|
|
77063
|
+
};
|
|
77064
|
+
}
|
|
77065
|
+
|
|
76100
77066
|
// src/commands/agent.ts
|
|
76101
77067
|
function registerAgentCommand(program2, context4) {
|
|
76102
77068
|
const agent = program2.command("agent").summary("run and inspect supported coding agents");
|
|
@@ -76255,8 +77221,20 @@ async function runAgentEdit(id, context4) {
|
|
|
76255
77221
|
context4.output.write(path9);
|
|
76256
77222
|
return 0;
|
|
76257
77223
|
}
|
|
76258
|
-
const
|
|
76259
|
-
|
|
77224
|
+
const [editorCmd, ...editorArgs] = editorArgv;
|
|
77225
|
+
if (editorCmd === undefined) {
|
|
77226
|
+
context4.output.write(path9);
|
|
77227
|
+
return 0;
|
|
77228
|
+
}
|
|
77229
|
+
const result = await new NodeProcessExecutor3({
|
|
77230
|
+
output: { mode: "stream", isTTY: true }
|
|
77231
|
+
}).run({
|
|
77232
|
+
command: editorCmd,
|
|
77233
|
+
args: [...editorArgs, path9],
|
|
77234
|
+
forceBuffered: false,
|
|
77235
|
+
rejectOnError: false
|
|
77236
|
+
});
|
|
77237
|
+
return result.exitCode ?? 1;
|
|
76260
77238
|
}
|
|
76261
77239
|
async function runAgentDelete(id, context4, flags) {
|
|
76262
77240
|
if (id === undefined) {
|
|
@@ -76277,12 +77255,19 @@ async function runAgentDelete(id, context4, flags) {
|
|
|
76277
77255
|
}
|
|
76278
77256
|
}
|
|
76279
77257
|
async function runAgentRun(prompt, context4, flags, deps) {
|
|
76280
|
-
const
|
|
76281
|
-
|
|
76282
|
-
|
|
76283
|
-
|
|
77258
|
+
const bus = new EventBus;
|
|
77259
|
+
const ledger = await attachSystemEventLedger(bus, context4);
|
|
77260
|
+
const svc = context4.agentService({ events: bus });
|
|
77261
|
+
try {
|
|
77262
|
+
if (flags.drain === true) {
|
|
77263
|
+
const { prompt: drained, flags: rewritten } = await drainIntoPrompt(prompt, context4, flags);
|
|
77264
|
+
return await svc.run(drained, rewritten, deps);
|
|
77265
|
+
}
|
|
77266
|
+
return await svc.run(prompt, flags, deps);
|
|
77267
|
+
} finally {
|
|
77268
|
+
await ledger.flush();
|
|
77269
|
+
ledger.unsubscribe();
|
|
76284
77270
|
}
|
|
76285
|
-
return svc.run(prompt, flags, deps);
|
|
76286
77271
|
}
|
|
76287
77272
|
async function drainIntoPrompt(prompt, context4, flags) {
|
|
76288
77273
|
const recipient = typeof flags.agent === "string" ? flags.agent : "";
|
|
@@ -76313,15 +77298,15 @@ function parseLoopPoll(raw) {
|
|
|
76313
77298
|
return Number.isFinite(n2) && n2 > 0 ? n2 : DEFAULT_LOOP_POLL_MS;
|
|
76314
77299
|
}
|
|
76315
77300
|
function loopSleep(ms, signal) {
|
|
76316
|
-
return new Promise((
|
|
77301
|
+
return new Promise((resolve5) => {
|
|
76317
77302
|
if (signal?.aborted) {
|
|
76318
|
-
|
|
77303
|
+
resolve5();
|
|
76319
77304
|
return;
|
|
76320
77305
|
}
|
|
76321
|
-
const timer = setTimeout(
|
|
77306
|
+
const timer = setTimeout(resolve5, ms);
|
|
76322
77307
|
signal?.addEventListener("abort", () => {
|
|
76323
77308
|
clearTimeout(timer);
|
|
76324
|
-
|
|
77309
|
+
resolve5();
|
|
76325
77310
|
}, { once: true });
|
|
76326
77311
|
});
|
|
76327
77312
|
}
|
|
@@ -76366,7 +77351,7 @@ function makePlanningEmitter(context4) {
|
|
|
76366
77351
|
return {
|
|
76367
77352
|
async emit(event2) {
|
|
76368
77353
|
try {
|
|
76369
|
-
cached2 ??= new SystemEventEmitter(new SystemEventDao(await context4.getDb()), logger3);
|
|
77354
|
+
cached2 ??= new SystemEventEmitter(new SystemEventDao(await context4.getDb()), logger3, {}, configuredSecretValues(context4.env ?? {}));
|
|
76370
77355
|
await cached2.emit(event2);
|
|
76371
77356
|
} catch (error51) {
|
|
76372
77357
|
context4.output.error(`system_events emitter: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
@@ -76379,14 +77364,14 @@ function makePlanningEmitter(context4) {
|
|
|
76379
77364
|
init_src3();
|
|
76380
77365
|
init_loader();
|
|
76381
77366
|
init_src2();
|
|
76382
|
-
import { existsSync as
|
|
76383
|
-
import { homedir as
|
|
76384
|
-
import { join as
|
|
77367
|
+
import { existsSync as existsSync9 } from "fs";
|
|
77368
|
+
import { homedir as homedir6 } from "os";
|
|
77369
|
+
import { join as join17, resolve as resolve5 } from "path";
|
|
76385
77370
|
|
|
76386
77371
|
// src/workflow/resolve-spur-bin.ts
|
|
76387
|
-
import { basename as
|
|
77372
|
+
import { basename as basename3 } from "path";
|
|
76388
77373
|
function resolveSpurBinFrom(launch) {
|
|
76389
|
-
const runtime =
|
|
77374
|
+
const runtime = basename3(launch.execPath).toLowerCase().replace(/\.exe$/, "");
|
|
76390
77375
|
const isJsRuntime = runtime === "bun" || runtime === "node";
|
|
76391
77376
|
if (isJsRuntime && launch.mainModule) {
|
|
76392
77377
|
return `${launch.execPath} ${launch.mainModule}`;
|
|
@@ -76399,23 +77384,23 @@ function resolveSpurBin() {
|
|
|
76399
77384
|
}
|
|
76400
77385
|
|
|
76401
77386
|
// src/workflow/make-lifecycle-adapter.ts
|
|
76402
|
-
var GLOBAL_CONFIG_DIR =
|
|
77387
|
+
var GLOBAL_CONFIG_DIR = join17(".config", "spur");
|
|
76403
77388
|
function globalConfigRoot(context4) {
|
|
76404
77389
|
const override = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
76405
|
-
return override !== undefined && override.length > 0 ?
|
|
77390
|
+
return override !== undefined && override.length > 0 ? resolve5(context4.cwd, override) : join17(homedir6(), GLOBAL_CONFIG_DIR);
|
|
76406
77391
|
}
|
|
76407
77392
|
function resolveWorkflowPath(context4, profile) {
|
|
76408
77393
|
const bundledRoot = bundledConfigRoot();
|
|
76409
77394
|
if (bundledRoot !== null) {
|
|
76410
|
-
const bundledPath =
|
|
76411
|
-
if (
|
|
77395
|
+
const bundledPath = join17(bundledRoot, "workflows", `${profile.workflowName}.yaml`);
|
|
77396
|
+
if (existsSync9(bundledPath))
|
|
76412
77397
|
return bundledPath;
|
|
76413
77398
|
}
|
|
76414
|
-
const projectPath =
|
|
76415
|
-
if (
|
|
77399
|
+
const projectPath = join17(context4.cwd, ".spur", "workflows", `${profile.workflowName}.yaml`);
|
|
77400
|
+
if (existsSync9(projectPath))
|
|
76416
77401
|
return projectPath;
|
|
76417
|
-
const globalPath =
|
|
76418
|
-
if (
|
|
77402
|
+
const globalPath = join17(globalConfigRoot(context4), "workflows", `${profile.workflowName}.yaml`);
|
|
77403
|
+
if (existsSync9(globalPath))
|
|
76419
77404
|
return globalPath;
|
|
76420
77405
|
return null;
|
|
76421
77406
|
}
|
|
@@ -76815,14 +77800,14 @@ function formatImportResult(r) {
|
|
|
76815
77800
|
init_loader();
|
|
76816
77801
|
init_src2();
|
|
76817
77802
|
init_dist10();
|
|
76818
|
-
import { homedir as
|
|
76819
|
-
import { join as
|
|
77803
|
+
import { homedir as homedir7 } from "os";
|
|
77804
|
+
import { join as join18, resolve as resolve6 } from "path";
|
|
76820
77805
|
|
|
76821
77806
|
// src/config.ts
|
|
76822
77807
|
var CLI_CONFIG = {
|
|
76823
77808
|
binaryName: "spur",
|
|
76824
77809
|
binaryLabel: "spur",
|
|
76825
|
-
binaryVersion: "0.3.
|
|
77810
|
+
binaryVersion: "0.3.27",
|
|
76826
77811
|
configDir: ".spur",
|
|
76827
77812
|
configFile: ".spur/config.yaml",
|
|
76828
77813
|
databaseFile: ".spur/spur.db"
|
|
@@ -76873,8 +77858,8 @@ var SCAFFOLD_MANIFEST = [
|
|
|
76873
77858
|
];
|
|
76874
77859
|
|
|
76875
77860
|
// src/commands/init.ts
|
|
76876
|
-
var GLOBAL_CONFIG_DIR2 =
|
|
76877
|
-
var GLOBAL_RULES_DIR2 =
|
|
77861
|
+
var GLOBAL_CONFIG_DIR2 = join18(".config", "spur");
|
|
77862
|
+
var GLOBAL_RULES_DIR2 = join18(GLOBAL_CONFIG_DIR2, "rules");
|
|
76878
77863
|
var GLOBAL_CONFIG_EXAMPLE = "config.example.yaml";
|
|
76879
77864
|
var GLOBAL_CONFIG_FILE2 = "config.yaml";
|
|
76880
77865
|
var INDEXED_CONTEXT_MARKER = "## Indexed context";
|
|
@@ -76905,7 +77890,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
|
|
|
76905
77890
|
`;
|
|
76906
77891
|
function globalRulesRoot(context4) {
|
|
76907
77892
|
const override = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
76908
|
-
return override !== undefined && override.length > 0 ?
|
|
77893
|
+
return override !== undefined && override.length > 0 ? resolve6(context4.cwd, override) : join18(homedir7(), GLOBAL_RULES_DIR2);
|
|
76909
77894
|
}
|
|
76910
77895
|
async function writeIfNew(context4, path9, content, force, result) {
|
|
76911
77896
|
if (!force && await context4.fs.exists(path9)) {
|
|
@@ -76922,11 +77907,11 @@ async function seedGlobalRules(context4) {
|
|
|
76922
77907
|
const target = globalRulesRoot(context4);
|
|
76923
77908
|
let written = 0;
|
|
76924
77909
|
for (const relPath of await listBundledRuleFiles()) {
|
|
76925
|
-
const destination =
|
|
77910
|
+
const destination = join18(target, relPath);
|
|
76926
77911
|
if (await context4.fs.exists(destination))
|
|
76927
77912
|
continue;
|
|
76928
|
-
await context4.fs.ensureDir(
|
|
76929
|
-
await context4.fs.writeFile(destination, await context4.fs.readFile(
|
|
77913
|
+
await context4.fs.ensureDir(join18(target, ...relPath.split("/").slice(0, -1)));
|
|
77914
|
+
await context4.fs.writeFile(destination, await context4.fs.readFile(join18(source, relPath)));
|
|
76930
77915
|
written += 1;
|
|
76931
77916
|
}
|
|
76932
77917
|
return written;
|
|
@@ -76936,20 +77921,20 @@ async function seedGlobalConfig(context4) {
|
|
|
76936
77921
|
if (source === null)
|
|
76937
77922
|
return 0;
|
|
76938
77923
|
const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
76939
|
-
const target = globalOverride !== undefined && globalOverride.length > 0 ?
|
|
77924
|
+
const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve6(context4.cwd, globalOverride) : join18(homedir7(), GLOBAL_CONFIG_DIR2);
|
|
76940
77925
|
let written = 0;
|
|
76941
77926
|
for (const relPath of listBundledConfigFiles()) {
|
|
76942
77927
|
if (relPath === GLOBAL_CONFIG_EXAMPLE)
|
|
76943
77928
|
continue;
|
|
76944
|
-
const destination =
|
|
77929
|
+
const destination = join18(target, relPath);
|
|
76945
77930
|
if (await context4.fs.exists(destination))
|
|
76946
77931
|
continue;
|
|
76947
|
-
await context4.fs.ensureDir(
|
|
76948
|
-
await context4.fs.writeFile(destination, await context4.fs.readFile(
|
|
77932
|
+
await context4.fs.ensureDir(join18(target, ...relPath.split("/").slice(0, -1)));
|
|
77933
|
+
await context4.fs.writeFile(destination, await context4.fs.readFile(join18(source, relPath)));
|
|
76949
77934
|
written += 1;
|
|
76950
77935
|
}
|
|
76951
|
-
const examplePath =
|
|
76952
|
-
const globalConfigPath =
|
|
77936
|
+
const examplePath = join18(source, GLOBAL_CONFIG_EXAMPLE);
|
|
77937
|
+
const globalConfigPath = join18(target, GLOBAL_CONFIG_FILE2);
|
|
76953
77938
|
if (await context4.fs.exists(examplePath) && !await context4.fs.exists(globalConfigPath)) {
|
|
76954
77939
|
await context4.fs.ensureDir(target);
|
|
76955
77940
|
await context4.fs.writeFile(globalConfigPath, await context4.fs.readFile(examplePath));
|
|
@@ -76963,7 +77948,7 @@ function registerInitCommand(program2, context4) {
|
|
|
76963
77948
|
const force = options.force === true;
|
|
76964
77949
|
const minimal = options.minimal === true;
|
|
76965
77950
|
const projectName = options.name ?? "default";
|
|
76966
|
-
const configPath =
|
|
77951
|
+
const configPath = join18(context4.cwd, CLI_CONFIG.configFile);
|
|
76967
77952
|
if (!force && await context4.fs.exists(configPath)) {
|
|
76968
77953
|
const message = `Already initialized: ${CLI_CONFIG.configFile}. Use --force to overwrite.`;
|
|
76969
77954
|
context4.output.write(json3 ? toJson2({ ok: false, reason: "already-initialized", config: CLI_CONFIG.configFile }) : message);
|
|
@@ -76998,13 +77983,13 @@ function registerInitCommand(program2, context4) {
|
|
|
76998
77983
|
].join(`
|
|
76999
77984
|
`)}
|
|
77000
77985
|
`;
|
|
77001
|
-
await context4.fs.ensureDir(
|
|
77986
|
+
await context4.fs.ensureDir(join18(context4.cwd, CLI_CONFIG.configDir));
|
|
77002
77987
|
await context4.fs.writeFile(configPath, configYaml);
|
|
77003
77988
|
result.created.push(configPath);
|
|
77004
|
-
const agentsDir =
|
|
77989
|
+
const agentsDir = join18(context4.cwd, CLI_CONFIG.configDir, "agents");
|
|
77005
77990
|
await context4.fs.ensureDir(agentsDir);
|
|
77006
|
-
await writeIfNew(context4,
|
|
77007
|
-
const gitignorePath =
|
|
77991
|
+
await writeIfNew(context4, join18(agentsDir, ".gitkeep"), "", force, result);
|
|
77992
|
+
const gitignorePath = join18(context4.cwd, ".gitignore");
|
|
77008
77993
|
const contextEntry = ".spur/context/";
|
|
77009
77994
|
if (await context4.fs.exists(gitignorePath)) {
|
|
77010
77995
|
const existing = await context4.fs.readFile(gitignorePath);
|
|
@@ -77026,20 +78011,20 @@ ${contextEntry}
|
|
|
77026
78011
|
for (const relPath of listBundledProjectSeedFiles()) {
|
|
77027
78012
|
if (relPath.startsWith("templates/docs/"))
|
|
77028
78013
|
continue;
|
|
77029
|
-
const sourcePath =
|
|
78014
|
+
const sourcePath = join18(configRoot, relPath);
|
|
77030
78015
|
if (!await context4.fs.exists(sourcePath))
|
|
77031
78016
|
continue;
|
|
77032
|
-
const targetPath =
|
|
77033
|
-
await context4.fs.ensureDir(
|
|
78017
|
+
const targetPath = join18(context4.cwd, CLI_CONFIG.configDir, relPath);
|
|
78018
|
+
await context4.fs.ensureDir(join18(targetPath, ".."));
|
|
77034
78019
|
await writeIfNew(context4, targetPath, await context4.fs.readFile(sourcePath), force, result);
|
|
77035
78020
|
}
|
|
77036
78021
|
for (const entry of SCAFFOLD_MANIFEST) {
|
|
77037
|
-
const sourcePath =
|
|
78022
|
+
const sourcePath = join18(configRoot, entry.source);
|
|
77038
78023
|
if (!await context4.fs.exists(sourcePath))
|
|
77039
78024
|
continue;
|
|
77040
|
-
const baseDir = entry.root === true ? context4.cwd :
|
|
77041
|
-
const targetPath =
|
|
77042
|
-
await context4.fs.ensureDir(
|
|
78025
|
+
const baseDir = entry.root === true ? context4.cwd : join18(context4.cwd, CLI_CONFIG.configDir);
|
|
78026
|
+
const targetPath = join18(baseDir, entry.target);
|
|
78027
|
+
await context4.fs.ensureDir(join18(targetPath, ".."));
|
|
77043
78028
|
const entryForce = entry.preserve === true ? false : force;
|
|
77044
78029
|
let body = await context4.fs.readFile(sourcePath);
|
|
77045
78030
|
if (entry.target === "AGENTS.md") {
|
|
@@ -77052,7 +78037,7 @@ ${contextEntry}
|
|
|
77052
78037
|
}
|
|
77053
78038
|
}
|
|
77054
78039
|
}
|
|
77055
|
-
const agentsMdPath =
|
|
78040
|
+
const agentsMdPath = join18(context4.cwd, "AGENTS.md");
|
|
77056
78041
|
if (await context4.fs.exists(agentsMdPath)) {
|
|
77057
78042
|
const existing = await context4.fs.readFile(agentsMdPath);
|
|
77058
78043
|
if (!existing.includes(INDEXED_CONTEXT_MARKER)) {
|
|
@@ -77209,15 +78194,15 @@ async function runMessageWatch(svc, output2, options, runtime = {}) {
|
|
|
77209
78194
|
}
|
|
77210
78195
|
}
|
|
77211
78196
|
function defaultSleep(ms, signal) {
|
|
77212
|
-
return new Promise((
|
|
78197
|
+
return new Promise((resolve7) => {
|
|
77213
78198
|
if (signal?.aborted) {
|
|
77214
|
-
|
|
78199
|
+
resolve7();
|
|
77215
78200
|
return;
|
|
77216
78201
|
}
|
|
77217
|
-
const timer = setTimeout(
|
|
78202
|
+
const timer = setTimeout(resolve7, ms);
|
|
77218
78203
|
signal?.addEventListener("abort", () => {
|
|
77219
78204
|
clearTimeout(timer);
|
|
77220
|
-
|
|
78205
|
+
resolve7();
|
|
77221
78206
|
}, { once: true });
|
|
77222
78207
|
});
|
|
77223
78208
|
}
|
|
@@ -77230,10 +78215,10 @@ function parseInterval2(raw) {
|
|
|
77230
78215
|
|
|
77231
78216
|
// src/commands/migrate.ts
|
|
77232
78217
|
init_src2();
|
|
77233
|
-
import { join as
|
|
78218
|
+
import { join as join19 } from "path";
|
|
77234
78219
|
function registerMigrateCommand(program2, context4) {
|
|
77235
78220
|
program2.command("migrate").summary("apply CLI-owned schema migrations").option("--json", "Output machine-readable JSON").action(async (options) => {
|
|
77236
|
-
const migrations = await loadSqlMigrations(
|
|
78221
|
+
const migrations = await loadSqlMigrations(join19(context4.cwd, "drizzle")).catch(() => {
|
|
77237
78222
|
return;
|
|
77238
78223
|
});
|
|
77239
78224
|
const applied = await applyCliMigrations(await context4.getDb(), migrations);
|
|
@@ -77242,6 +78227,170 @@ function registerMigrateCommand(program2, context4) {
|
|
|
77242
78227
|
});
|
|
77243
78228
|
}
|
|
77244
78229
|
|
|
78230
|
+
// src/commands/projects.ts
|
|
78231
|
+
init_src3();
|
|
78232
|
+
init_dist5();
|
|
78233
|
+
import { basename as basename4, resolve as resolve7 } from "path";
|
|
78234
|
+
function registerProjectsCommand(program2, context4) {
|
|
78235
|
+
const projectsCmd = program2.command("projects").summary("manage the Spur multi-project registry");
|
|
78236
|
+
projectsCmd.command("add").argument("<path>", "Project root directory path").option("--name <name>", "Display name for the project").option("--json", "Output JSON response").action(async (pathArg, options) => {
|
|
78237
|
+
try {
|
|
78238
|
+
const absolutePath = resolve7(context4.cwd, pathArg);
|
|
78239
|
+
if (!await context4.fs.exists(absolutePath)) {
|
|
78240
|
+
throw new Error(`Directory does not exist: ${absolutePath}`);
|
|
78241
|
+
}
|
|
78242
|
+
const name = options.name ?? basename4(absolutePath);
|
|
78243
|
+
const registry2 = new ProjectRegistry;
|
|
78244
|
+
const entry = await registry2.upsert({ path: absolutePath, name, port: 0 });
|
|
78245
|
+
if (options.json) {
|
|
78246
|
+
context4.output.write(toJson2({ ok: true, project: entry }));
|
|
78247
|
+
} else {
|
|
78248
|
+
context4.output.write(`Registered project "${entry.name}" at ${entry.path}`);
|
|
78249
|
+
}
|
|
78250
|
+
} catch (err) {
|
|
78251
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78252
|
+
if (options.json) {
|
|
78253
|
+
context4.output.write(toJson2({ ok: false, error: message }));
|
|
78254
|
+
} else {
|
|
78255
|
+
context4.output.error(`Error: ${message}`);
|
|
78256
|
+
}
|
|
78257
|
+
context4.setExitCode(1);
|
|
78258
|
+
}
|
|
78259
|
+
});
|
|
78260
|
+
projectsCmd.command("remove").argument("<target>", "Project display name or directory path").option("--json", "Output JSON response").action(async (target, options) => {
|
|
78261
|
+
try {
|
|
78262
|
+
const registry2 = new ProjectRegistry;
|
|
78263
|
+
const removed = await registry2.remove(target);
|
|
78264
|
+
if (!removed) {
|
|
78265
|
+
throw new Error(`Project not found in registry: "${target}"`);
|
|
78266
|
+
}
|
|
78267
|
+
if (options.json) {
|
|
78268
|
+
context4.output.write(toJson2({ ok: true, removed: target }));
|
|
78269
|
+
} else {
|
|
78270
|
+
context4.output.write(`Removed project "${target}" from registry`);
|
|
78271
|
+
}
|
|
78272
|
+
} catch (err) {
|
|
78273
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78274
|
+
if (options.json) {
|
|
78275
|
+
context4.output.write(toJson2({ ok: false, error: message }));
|
|
78276
|
+
} else {
|
|
78277
|
+
context4.output.error(`Error: ${message}`);
|
|
78278
|
+
}
|
|
78279
|
+
context4.setExitCode(1);
|
|
78280
|
+
}
|
|
78281
|
+
});
|
|
78282
|
+
projectsCmd.command("list").option("--json", "Output JSON array of projects").action(async (options) => {
|
|
78283
|
+
try {
|
|
78284
|
+
const registry2 = new ProjectRegistry;
|
|
78285
|
+
const rawProjects = await registry2.list();
|
|
78286
|
+
const projects3 = await Promise.all(rawProjects.map(async (p) => ({
|
|
78287
|
+
...p,
|
|
78288
|
+
running: p.port > 0 ? await isPortLive(p.port) : false
|
|
78289
|
+
})));
|
|
78290
|
+
if (options.json) {
|
|
78291
|
+
context4.output.write(toJson2({ projects: projects3 }));
|
|
78292
|
+
} else {
|
|
78293
|
+
if (projects3.length === 0) {
|
|
78294
|
+
context4.output.write("No projects registered.");
|
|
78295
|
+
return;
|
|
78296
|
+
}
|
|
78297
|
+
context4.output.write(`Registered Projects:
|
|
78298
|
+
`);
|
|
78299
|
+
for (const p of projects3) {
|
|
78300
|
+
const status = p.running ? `[RUNNING: ${p.port}]` : "[STOPPED]";
|
|
78301
|
+
context4.output.write(`- ${p.name} ${status} (${p.path})`);
|
|
78302
|
+
}
|
|
78303
|
+
}
|
|
78304
|
+
} catch (err) {
|
|
78305
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78306
|
+
if (options.json) {
|
|
78307
|
+
context4.output.write(toJson2({ ok: false, error: message }));
|
|
78308
|
+
} else {
|
|
78309
|
+
context4.output.error(`Error: ${message}`);
|
|
78310
|
+
}
|
|
78311
|
+
context4.setExitCode(1);
|
|
78312
|
+
}
|
|
78313
|
+
});
|
|
78314
|
+
projectsCmd.command("start").argument("<target>", "Project display name or path").option("--port <n>", "Explicit port to bind", parseInt).option("--json", "Output JSON response").action(async (target, options) => {
|
|
78315
|
+
try {
|
|
78316
|
+
const registry2 = new ProjectRegistry;
|
|
78317
|
+
const result = await startRegisteredProject(registry2, target, {
|
|
78318
|
+
port: typeof options.port === "number" && !Number.isNaN(options.port) ? options.port : undefined
|
|
78319
|
+
});
|
|
78320
|
+
if (options.json) {
|
|
78321
|
+
context4.output.write(toJson2({
|
|
78322
|
+
ok: true,
|
|
78323
|
+
project: {
|
|
78324
|
+
name: result.name,
|
|
78325
|
+
path: result.path,
|
|
78326
|
+
port: result.port
|
|
78327
|
+
},
|
|
78328
|
+
running: true,
|
|
78329
|
+
alreadyRunning: result.alreadyRunning,
|
|
78330
|
+
url: result.url
|
|
78331
|
+
}));
|
|
78332
|
+
} else if (result.alreadyRunning) {
|
|
78333
|
+
context4.output.write(`Project "${result.name}" is already running at ${result.url}`);
|
|
78334
|
+
} else {
|
|
78335
|
+
context4.output.write(`Started project "${result.name}" on ${result.url}`);
|
|
78336
|
+
}
|
|
78337
|
+
} catch (err) {
|
|
78338
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78339
|
+
if (options.json) {
|
|
78340
|
+
context4.output.write(toJson2({ ok: false, error: message }));
|
|
78341
|
+
} else {
|
|
78342
|
+
context4.output.error(`Error: ${message}`);
|
|
78343
|
+
}
|
|
78344
|
+
context4.setExitCode(1);
|
|
78345
|
+
}
|
|
78346
|
+
});
|
|
78347
|
+
projectsCmd.command("stop").argument("<target>", "Project display name or path").option("--json", "Output JSON response").action(async (target, options) => {
|
|
78348
|
+
try {
|
|
78349
|
+
const registry2 = new ProjectRegistry;
|
|
78350
|
+
const entry = await registry2.getByName(target) ?? await registry2.getByPath(target);
|
|
78351
|
+
if (!entry) {
|
|
78352
|
+
throw new Error(`Project not found: "${target}"`);
|
|
78353
|
+
}
|
|
78354
|
+
if (entry.port > 0) {
|
|
78355
|
+
try {
|
|
78356
|
+
const result = await new NodeProcessExecutor3().run({
|
|
78357
|
+
command: "fuser",
|
|
78358
|
+
args: [`${entry.port}/tcp`],
|
|
78359
|
+
forceBuffered: true,
|
|
78360
|
+
rejectOnError: false
|
|
78361
|
+
});
|
|
78362
|
+
const outputStr = `${result.stdout}
|
|
78363
|
+
${result.stderr}`;
|
|
78364
|
+
const pids = outputStr.trim().split(/\s+/).map(Number).filter((n2) => Number.isInteger(n2) && n2 > 1);
|
|
78365
|
+
const self2 = process.pid;
|
|
78366
|
+
const parent = process.ppid;
|
|
78367
|
+
for (const pid of pids) {
|
|
78368
|
+
if (pid === self2 || pid === parent)
|
|
78369
|
+
continue;
|
|
78370
|
+
try {
|
|
78371
|
+
process.kill(pid, "SIGTERM");
|
|
78372
|
+
} catch {}
|
|
78373
|
+
}
|
|
78374
|
+
} catch {}
|
|
78375
|
+
await registry2.setPort(entry.path, 0);
|
|
78376
|
+
}
|
|
78377
|
+
if (options.json) {
|
|
78378
|
+
context4.output.write(toJson2({ ok: true, stopped: entry.name }));
|
|
78379
|
+
} else {
|
|
78380
|
+
context4.output.write(`Stopped project "${entry.name}"`);
|
|
78381
|
+
}
|
|
78382
|
+
} catch (err) {
|
|
78383
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78384
|
+
if (options.json) {
|
|
78385
|
+
context4.output.write(toJson2({ ok: false, error: message }));
|
|
78386
|
+
} else {
|
|
78387
|
+
context4.output.error(`Error: ${message}`);
|
|
78388
|
+
}
|
|
78389
|
+
context4.setExitCode(1);
|
|
78390
|
+
}
|
|
78391
|
+
});
|
|
78392
|
+
}
|
|
78393
|
+
|
|
77245
78394
|
// src/commands/rule.ts
|
|
77246
78395
|
init_src3();
|
|
77247
78396
|
|
|
@@ -77485,7 +78634,7 @@ function formatTraceDetail(detail) {
|
|
|
77485
78634
|
|
|
77486
78635
|
// src/commands/serve.ts
|
|
77487
78636
|
init_src();
|
|
77488
|
-
import { join as
|
|
78637
|
+
import { join as join23 } from "path";
|
|
77489
78638
|
|
|
77490
78639
|
// ../server/src/index.ts
|
|
77491
78640
|
init_src();
|
|
@@ -77495,11 +78644,11 @@ init_src3();
|
|
|
77495
78644
|
init_src();
|
|
77496
78645
|
init_loader();
|
|
77497
78646
|
init_src2();
|
|
77498
|
-
import { dirname as
|
|
78647
|
+
import { basename as basename6, dirname as dirname15, isAbsolute as isAbsolute3, join as join22 } from "path";
|
|
77499
78648
|
init_dist5();
|
|
77500
78649
|
|
|
77501
78650
|
// ../server/src/bootstrap.ts
|
|
77502
|
-
import { join as
|
|
78651
|
+
import { join as join20 } from "path";
|
|
77503
78652
|
|
|
77504
78653
|
// ../../node_modules/.bun/radash@12.1.1/node_modules/radash/dist/esm/async.mjs
|
|
77505
78654
|
var guard = (func, shouldGuard) => {
|
|
@@ -77772,8 +78921,8 @@ class EventPublisher {
|
|
|
77772
78921
|
if (bufferedEvents.length > 0) {
|
|
77773
78922
|
return { done: false, value: bufferedEvents.shift() };
|
|
77774
78923
|
}
|
|
77775
|
-
return new Promise((
|
|
77776
|
-
pullResolvers.push([
|
|
78924
|
+
return new Promise((resolve8, reject) => {
|
|
78925
|
+
pullResolvers.push([resolve8, reject]);
|
|
77777
78926
|
});
|
|
77778
78927
|
}, async () => {
|
|
77779
78928
|
unsubscribe();
|
|
@@ -82906,6 +84055,56 @@ function enqueueSseFrame(closed, controller, encoder, frame) {
|
|
|
82906
84055
|
init_event_names();
|
|
82907
84056
|
|
|
82908
84057
|
// ../server/src/modules/events/index.ts
|
|
84058
|
+
var HISTORY_LIMIT_DEFAULT = 100;
|
|
84059
|
+
var HISTORY_LIMIT_MAX = 500;
|
|
84060
|
+
function encodeHistoryCursor(id, occurredAt) {
|
|
84061
|
+
const payload = { id, occurredAt };
|
|
84062
|
+
const bytes = new TextEncoder().encode(JSON.stringify(payload));
|
|
84063
|
+
let binary = "";
|
|
84064
|
+
for (const byte of bytes)
|
|
84065
|
+
binary += String.fromCharCode(byte);
|
|
84066
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
84067
|
+
}
|
|
84068
|
+
function decodeHistoryCursor(encoded) {
|
|
84069
|
+
if (encoded.length === 0 || encoded.length > 1024) {
|
|
84070
|
+
return { ok: false, reason: "malformed cursor: empty or exceeds maximum length" };
|
|
84071
|
+
}
|
|
84072
|
+
let json3;
|
|
84073
|
+
try {
|
|
84074
|
+
const base643 = encoded.replaceAll("-", "+").replaceAll("_", "/");
|
|
84075
|
+
const binary = atob(base643);
|
|
84076
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
84077
|
+
json3 = new TextDecoder().decode(bytes);
|
|
84078
|
+
} catch {
|
|
84079
|
+
return { ok: false, reason: "malformed cursor: not valid base64url" };
|
|
84080
|
+
}
|
|
84081
|
+
let parsed;
|
|
84082
|
+
try {
|
|
84083
|
+
parsed = JSON.parse(json3);
|
|
84084
|
+
} catch {
|
|
84085
|
+
return { ok: false, reason: "malformed cursor: payload is not JSON" };
|
|
84086
|
+
}
|
|
84087
|
+
if (!parsed || typeof parsed !== "object") {
|
|
84088
|
+
return { ok: false, reason: "malformed cursor: payload must be an object" };
|
|
84089
|
+
}
|
|
84090
|
+
const obj = parsed;
|
|
84091
|
+
if (typeof obj.id !== "string" || obj.id.length === 0) {
|
|
84092
|
+
return { ok: false, reason: "malformed cursor: missing or invalid id" };
|
|
84093
|
+
}
|
|
84094
|
+
if (typeof obj.occurredAt !== "string" || obj.occurredAt.length === 0) {
|
|
84095
|
+
return { ok: false, reason: "malformed cursor: missing or invalid occurredAt" };
|
|
84096
|
+
}
|
|
84097
|
+
if (Number.isNaN(Date.parse(obj.occurredAt))) {
|
|
84098
|
+
return { ok: false, reason: "malformed cursor: occurredAt is not a valid timestamp" };
|
|
84099
|
+
}
|
|
84100
|
+
return { ok: true, value: { id: obj.id, occurredAt: obj.occurredAt } };
|
|
84101
|
+
}
|
|
84102
|
+
function parseHistoryNamesParam(raw2) {
|
|
84103
|
+
if (raw2 === undefined)
|
|
84104
|
+
return;
|
|
84105
|
+
const parts = (Array.isArray(raw2) ? raw2 : [raw2]).flatMap((chunk) => chunk.split(",")).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
84106
|
+
return parts.length > 0 ? parts : undefined;
|
|
84107
|
+
}
|
|
82909
84108
|
function extractSystemEventActor2(event2) {
|
|
82910
84109
|
if (event2 && typeof event2 === "object") {
|
|
82911
84110
|
const obj = event2;
|
|
@@ -82913,6 +84112,8 @@ function extractSystemEventActor2(event2) {
|
|
|
82913
84112
|
return obj.actor;
|
|
82914
84113
|
if (typeof obj.agentId === "string" && obj.agentId.length > 0)
|
|
82915
84114
|
return obj.agentId;
|
|
84115
|
+
if (typeof obj.memberId === "string" && obj.memberId.length > 0)
|
|
84116
|
+
return obj.memberId;
|
|
82916
84117
|
}
|
|
82917
84118
|
return null;
|
|
82918
84119
|
}
|
|
@@ -83001,29 +84202,71 @@ var eventsModule = {
|
|
|
83001
84202
|
const nameParam = c3.req.query("name");
|
|
83002
84203
|
const sinceParam = c3.req.query("since");
|
|
83003
84204
|
const limitParam = c3.req.query("limit");
|
|
83004
|
-
|
|
84205
|
+
const prefixParam = c3.req.query("prefix");
|
|
84206
|
+
const runIdParam = c3.req.query("runId");
|
|
84207
|
+
const actorParam = c3.req.query("actor");
|
|
84208
|
+
const cursorParam = c3.req.query("cursor");
|
|
84209
|
+
const namesRaw = c3.req.queries("names");
|
|
84210
|
+
const namesParam = namesRaw && namesRaw.length > 0 ? parseHistoryNamesParam(namesRaw) : parseHistoryNamesParam(c3.req.query("names"));
|
|
84211
|
+
let limit = HISTORY_LIMIT_DEFAULT;
|
|
83005
84212
|
if (limitParam !== undefined) {
|
|
83006
84213
|
const parsed = Number.parseInt(limitParam, 10);
|
|
83007
84214
|
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
83008
|
-
limit = Math.min(parsed,
|
|
84215
|
+
limit = Math.min(parsed, HISTORY_LIMIT_MAX);
|
|
83009
84216
|
}
|
|
83010
84217
|
}
|
|
83011
|
-
|
|
83012
|
-
|
|
84218
|
+
if (prefixParam !== undefined && prefixParam.length > 0) {
|
|
84219
|
+
if (!SYSTEM_EVENT_PREFIXES.includes(prefixParam)) {
|
|
84220
|
+
return c3.json({
|
|
84221
|
+
error: `unknown prefix: "${prefixParam}" is not a cataloged event family`,
|
|
84222
|
+
code: "UNKNOWN_PREFIX"
|
|
84223
|
+
}, 400);
|
|
84224
|
+
}
|
|
84225
|
+
}
|
|
84226
|
+
let before;
|
|
84227
|
+
if (cursorParam !== undefined && cursorParam.length > 0) {
|
|
84228
|
+
const decoded = decodeHistoryCursor(cursorParam);
|
|
84229
|
+
if (!decoded.ok) {
|
|
84230
|
+
return c3.json({ error: decoded.reason, code: "MALFORMED_CURSOR" }, 400);
|
|
84231
|
+
}
|
|
84232
|
+
before = { occurred_at: decoded.value.occurredAt, id: decoded.value.id };
|
|
84233
|
+
}
|
|
84234
|
+
const query = {
|
|
83013
84235
|
name: nameParam || undefined,
|
|
84236
|
+
names: namesParam,
|
|
84237
|
+
prefix: prefixParam || undefined,
|
|
83014
84238
|
since: sinceParam || undefined,
|
|
83015
|
-
|
|
83016
|
-
|
|
83017
|
-
|
|
84239
|
+
run_id: runIdParam || undefined,
|
|
84240
|
+
actor: actorParam || undefined,
|
|
84241
|
+
before,
|
|
84242
|
+
limit: limit + 1
|
|
84243
|
+
};
|
|
84244
|
+
const dao2 = await ctx.systemEventDao();
|
|
84245
|
+
const rows = await dao2.query(query);
|
|
84246
|
+
const hasMore = rows.length > limit;
|
|
84247
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
84248
|
+
const last = page.at(-1);
|
|
84249
|
+
const nextCursor = hasMore && last ? encodeHistoryCursor(last.id, last.occurred_at) : null;
|
|
84250
|
+
const events2 = page.map((row) => ({
|
|
83018
84251
|
id: row.id,
|
|
83019
84252
|
eventName: row.event_name,
|
|
83020
84253
|
occurredAt: row.occurred_at,
|
|
83021
84254
|
actor: row.actor,
|
|
83022
84255
|
prefix: systemEventCatalogEntry(row.event_name)?.prefix ?? row.event_name.split(".")[0],
|
|
83023
84256
|
renderer: systemEventCatalogEntry(row.event_name)?.renderer ?? "generic",
|
|
83024
|
-
payload: row.payload_json ? JSON.parse(row.payload_json) : null
|
|
84257
|
+
payload: row.payload_json ? JSON.parse(row.payload_json) : null,
|
|
84258
|
+
runId: row.run_id,
|
|
84259
|
+
entityKind: row.entity_kind,
|
|
84260
|
+
entityId: row.entity_id,
|
|
84261
|
+
sequence: row.sequence
|
|
83025
84262
|
}));
|
|
83026
|
-
return c3.json({
|
|
84263
|
+
return c3.json({
|
|
84264
|
+
events: events2,
|
|
84265
|
+
count: events2.length,
|
|
84266
|
+
catalog: SYSTEM_EVENT_CATALOG_METADATA,
|
|
84267
|
+
nextCursor,
|
|
84268
|
+
hasMore
|
|
84269
|
+
});
|
|
83027
84270
|
});
|
|
83028
84271
|
}
|
|
83029
84272
|
};
|
|
@@ -83128,7 +84371,12 @@ var featureActionInputSchema = exports_external.object({
|
|
|
83128
84371
|
channel: featureActionChannelSchema.optional(),
|
|
83129
84372
|
skipDeps: exports_external.boolean().optional()
|
|
83130
84373
|
});
|
|
83131
|
-
var
|
|
84374
|
+
var featureActionResultSchema = exports_external.object({
|
|
84375
|
+
runId: exports_external.string(),
|
|
84376
|
+
action: exports_external.string(),
|
|
84377
|
+
status: exports_external.literal("queued")
|
|
84378
|
+
});
|
|
84379
|
+
var featureActionResponseSchema = apiSuccessSchema(featureActionResultSchema);
|
|
83132
84380
|
var featureCreateChildInputSchema = exports_external.object({
|
|
83133
84381
|
id: featureIdSchema,
|
|
83134
84382
|
name: exports_external.string().min(1)
|
|
@@ -83490,8 +84738,13 @@ function createFeatureHandlers(ctx) {
|
|
|
83490
84738
|
await ctx.featureService().updateBody(input.id, input.body);
|
|
83491
84739
|
return { ok: true, data: {} };
|
|
83492
84740
|
}),
|
|
83493
|
-
action: os2.feature.action.handler(async () => {
|
|
83494
|
-
|
|
84741
|
+
action: os2.feature.action.handler(async ({ input }) => {
|
|
84742
|
+
const jobQueue = await ctx.jobQueue();
|
|
84743
|
+
const result = await ctx.featureService().fulfillAction(input.id, input.action, async (job) => {
|
|
84744
|
+
const runId = await jobQueue.enqueue("feature-action", job);
|
|
84745
|
+
return runId;
|
|
84746
|
+
}, { channel: input.channel, skipDeps: input.skipDeps });
|
|
84747
|
+
return { ok: true, data: result };
|
|
83495
84748
|
}),
|
|
83496
84749
|
children: os2.feature.children.handler(async ({ input }) => {
|
|
83497
84750
|
const r2 = await ctx.featureService().create(input.name, input.id);
|
|
@@ -83535,7 +84788,8 @@ var featureModule = {
|
|
|
83535
84788
|
};
|
|
83536
84789
|
|
|
83537
84790
|
// ../server/src/modules/health/index.ts
|
|
83538
|
-
|
|
84791
|
+
init_src3();
|
|
84792
|
+
import { basename as basename5 } from "path";
|
|
83539
84793
|
var startedAt = Date.now();
|
|
83540
84794
|
var healthModule = {
|
|
83541
84795
|
name: "health",
|
|
@@ -83561,7 +84815,56 @@ var healthModule = {
|
|
|
83561
84815
|
return c3.json({ status: "error", db: "unreachable" }, 503);
|
|
83562
84816
|
});
|
|
83563
84817
|
app.get("/api/project", (c3) => {
|
|
83564
|
-
return c3.json({ name: ctx ?
|
|
84818
|
+
return c3.json({ name: ctx ? basename5(ctx.cwd) : null });
|
|
84819
|
+
});
|
|
84820
|
+
app.get("/api/projects", async (c3) => {
|
|
84821
|
+
if (!ctx) {
|
|
84822
|
+
return c3.json({ projects: [] });
|
|
84823
|
+
}
|
|
84824
|
+
const registry2 = new ProjectRegistry;
|
|
84825
|
+
const rawProjects = await registry2.list();
|
|
84826
|
+
const currentNorm = normalizeProjectPath(ctx.cwd);
|
|
84827
|
+
const projects3 = await Promise.all(rawProjects.map(async (p2) => {
|
|
84828
|
+
const normPath = normalizeProjectPath(p2.path);
|
|
84829
|
+
const running = p2.port > 0 ? await isPortLive(p2.port) : false;
|
|
84830
|
+
return {
|
|
84831
|
+
name: p2.name,
|
|
84832
|
+
path: p2.path,
|
|
84833
|
+
port: p2.port,
|
|
84834
|
+
running,
|
|
84835
|
+
current: normPath === currentNorm
|
|
84836
|
+
};
|
|
84837
|
+
}));
|
|
84838
|
+
return c3.json({ projects: projects3 });
|
|
84839
|
+
});
|
|
84840
|
+
app.post("/api/projects/start", async (c3) => {
|
|
84841
|
+
if (!ctx) {
|
|
84842
|
+
return c3.json({ error: "Multi-project registry unavailable on Cloudflare Workers" }, 501);
|
|
84843
|
+
}
|
|
84844
|
+
let body = {};
|
|
84845
|
+
try {
|
|
84846
|
+
body = await c3.req.json();
|
|
84847
|
+
} catch {}
|
|
84848
|
+
const target = body.name ?? body.path;
|
|
84849
|
+
if (!target) {
|
|
84850
|
+
return c3.json({ error: "Missing name or path in request body" }, 400);
|
|
84851
|
+
}
|
|
84852
|
+
try {
|
|
84853
|
+
const registry2 = new ProjectRegistry;
|
|
84854
|
+
const result = await startRegisteredProject(registry2, target);
|
|
84855
|
+
return c3.json({
|
|
84856
|
+
name: result.name,
|
|
84857
|
+
path: result.path,
|
|
84858
|
+
port: result.port,
|
|
84859
|
+
running: true,
|
|
84860
|
+
alreadyRunning: result.alreadyRunning,
|
|
84861
|
+
url: result.url
|
|
84862
|
+
});
|
|
84863
|
+
} catch (err) {
|
|
84864
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84865
|
+
const status = message.includes("not found") ? 404 : 500;
|
|
84866
|
+
return c3.json({ error: message }, status);
|
|
84867
|
+
}
|
|
83565
84868
|
});
|
|
83566
84869
|
}
|
|
83567
84870
|
};
|
|
@@ -83811,6 +85114,63 @@ var observabilityModule = {
|
|
|
83811
85114
|
}
|
|
83812
85115
|
};
|
|
83813
85116
|
|
|
85117
|
+
// ../server/src/modules/runs/index.ts
|
|
85118
|
+
init_src3();
|
|
85119
|
+
var runsModule = {
|
|
85120
|
+
name: "runs",
|
|
85121
|
+
mount(app, ctx) {
|
|
85122
|
+
if (!ctx)
|
|
85123
|
+
return;
|
|
85124
|
+
app.get("/api/runs", async (c3) => {
|
|
85125
|
+
const status = c3.req.query("status") || undefined;
|
|
85126
|
+
const limitParam = c3.req.query("limit");
|
|
85127
|
+
const cursor2 = c3.req.query("cursor") || undefined;
|
|
85128
|
+
let limit;
|
|
85129
|
+
if (limitParam !== undefined) {
|
|
85130
|
+
const parsed = Number.parseInt(limitParam, 10);
|
|
85131
|
+
if (!Number.isNaN(parsed))
|
|
85132
|
+
limit = parsed;
|
|
85133
|
+
}
|
|
85134
|
+
try {
|
|
85135
|
+
const service = ctx.runStoreService();
|
|
85136
|
+
const result = await service.list({ status, limit, cursor: cursor2 });
|
|
85137
|
+
return c3.json(result);
|
|
85138
|
+
} catch (err) {
|
|
85139
|
+
if (err instanceof RunStoreBadCursorError) {
|
|
85140
|
+
return c3.json({ error: err.message, code: err.code }, 400);
|
|
85141
|
+
}
|
|
85142
|
+
throw err;
|
|
85143
|
+
}
|
|
85144
|
+
});
|
|
85145
|
+
app.get("/api/runs/by-wbs/:wbs", async (c3) => {
|
|
85146
|
+
const wbs = c3.req.param("wbs");
|
|
85147
|
+
const limitParam = c3.req.query("limit");
|
|
85148
|
+
let limit;
|
|
85149
|
+
if (limitParam !== undefined) {
|
|
85150
|
+
const parsed = Number.parseInt(limitParam, 10);
|
|
85151
|
+
if (!Number.isNaN(parsed))
|
|
85152
|
+
limit = parsed;
|
|
85153
|
+
}
|
|
85154
|
+
const service = ctx.runStoreService();
|
|
85155
|
+
const result = await service.listByWbs(wbs, limit);
|
|
85156
|
+
return c3.json(result);
|
|
85157
|
+
});
|
|
85158
|
+
app.get("/api/runs/:runId", async (c3) => {
|
|
85159
|
+
const runId = c3.req.param("runId");
|
|
85160
|
+
const service = ctx.runStoreService();
|
|
85161
|
+
try {
|
|
85162
|
+
const detail = await service.getDetail(runId);
|
|
85163
|
+
return c3.json(detail);
|
|
85164
|
+
} catch (err) {
|
|
85165
|
+
if (err instanceof RunStoreNotFoundError) {
|
|
85166
|
+
return c3.json({ error: err.message, code: err.code, runId: err.runId }, 404);
|
|
85167
|
+
}
|
|
85168
|
+
throw err;
|
|
85169
|
+
}
|
|
85170
|
+
});
|
|
85171
|
+
}
|
|
85172
|
+
};
|
|
85173
|
+
|
|
83814
85174
|
// ../server/src/modules/task/handlers.ts
|
|
83815
85175
|
init_schema6();
|
|
83816
85176
|
var os3 = implement(contract);
|
|
@@ -83998,6 +85358,7 @@ var teamModule = {
|
|
|
83998
85358
|
const closed = { current: false };
|
|
83999
85359
|
const signal = c3.req.raw.signal;
|
|
84000
85360
|
let heartbeatInterval;
|
|
85361
|
+
let pollInterval;
|
|
84001
85362
|
let closeController = () => {};
|
|
84002
85363
|
const teardown = () => {
|
|
84003
85364
|
if (closed.current)
|
|
@@ -84005,6 +85366,8 @@ var teamModule = {
|
|
|
84005
85366
|
closed.current = true;
|
|
84006
85367
|
if (heartbeatInterval)
|
|
84007
85368
|
clearInterval(heartbeatInterval);
|
|
85369
|
+
if (pollInterval)
|
|
85370
|
+
clearInterval(pollInterval);
|
|
84008
85371
|
signal.removeEventListener("abort", teardown);
|
|
84009
85372
|
closeController();
|
|
84010
85373
|
};
|
|
@@ -84035,29 +85398,24 @@ var teamModule = {
|
|
|
84035
85398
|
line: "--replay-done--"
|
|
84036
85399
|
}))
|
|
84037
85400
|
return;
|
|
84038
|
-
|
|
85401
|
+
pollInterval = setInterval(() => {
|
|
84039
85402
|
if (closed.current) {
|
|
84040
|
-
|
|
85403
|
+
if (pollInterval)
|
|
85404
|
+
clearInterval(pollInterval);
|
|
84041
85405
|
return;
|
|
84042
85406
|
}
|
|
84043
85407
|
for (const frame of supervisor.getRingBuffer(id)) {
|
|
84044
85408
|
if (frame.seq <= lastSeq)
|
|
84045
85409
|
continue;
|
|
84046
85410
|
if (!enqueueFrame(closed, controller, encoder, frame)) {
|
|
84047
|
-
|
|
85411
|
+
teardown();
|
|
84048
85412
|
return;
|
|
84049
85413
|
}
|
|
84050
85414
|
lastSeq = frame.seq;
|
|
84051
85415
|
}
|
|
84052
85416
|
}, 500);
|
|
84053
|
-
const origClose = closeController;
|
|
84054
|
-
closeController = () => {
|
|
84055
|
-
clearInterval(pollInterval);
|
|
84056
|
-
origClose();
|
|
84057
|
-
};
|
|
84058
85417
|
},
|
|
84059
85418
|
cancel() {
|
|
84060
|
-
closeController = () => {};
|
|
84061
85419
|
teardown();
|
|
84062
85420
|
}
|
|
84063
85421
|
});
|
|
@@ -84153,7 +85511,8 @@ var builtins = [
|
|
|
84153
85511
|
messagesModule,
|
|
84154
85512
|
jobsModule,
|
|
84155
85513
|
teamModule,
|
|
84156
|
-
observabilityModule
|
|
85514
|
+
observabilityModule,
|
|
85515
|
+
runsModule
|
|
84157
85516
|
];
|
|
84158
85517
|
function registerModules(app, ctx, modules = builtins) {
|
|
84159
85518
|
for (const mod of modules) {
|
|
@@ -86100,21 +87459,55 @@ function createRouter2(ctx) {
|
|
|
86100
87459
|
};
|
|
86101
87460
|
}
|
|
86102
87461
|
|
|
86103
|
-
// ../server/src/
|
|
87462
|
+
// ../server/src/server-config.ts
|
|
86104
87463
|
function serverBootstrapConfig(env) {
|
|
86105
87464
|
const isTest = env.NODE_ENV === "test";
|
|
86106
87465
|
const raw2 = env.SPUR_TEAM_AUTOSTART;
|
|
86107
87466
|
const teamAutostart = raw2 ? raw2.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
|
|
86108
87467
|
const diagnosticEvents = env.SPUR_DIAGNOSTIC_EVENTS === "1" || env.SPUR_DIAGNOSTIC_EVENTS === "true";
|
|
87468
|
+
const retentionDefault = parseRetentionNumber(env.SPUR_EVENT_RETENTION_DEFAULT);
|
|
87469
|
+
const retentionPrefixes = parseRetentionPrefixes(env);
|
|
86109
87470
|
return {
|
|
86110
87471
|
logging: { enabled: !isTest, level: env.SPUR_LOG_LEVEL ?? "info", console: false },
|
|
86111
87472
|
telemetry: { enabled: false },
|
|
86112
|
-
events: {
|
|
87473
|
+
events: {
|
|
87474
|
+
enabled: true,
|
|
87475
|
+
diagnostic: diagnosticEvents,
|
|
87476
|
+
retention: {
|
|
87477
|
+
...retentionDefault !== undefined ? { default: retentionDefault } : {},
|
|
87478
|
+
...Object.keys(retentionPrefixes).length > 0 ? { prefixes: retentionPrefixes } : {}
|
|
87479
|
+
}
|
|
87480
|
+
},
|
|
86113
87481
|
jobqueue: { enabled: !isTest },
|
|
86114
87482
|
scheduler: { enabled: !isTest },
|
|
86115
87483
|
teamAutostart
|
|
86116
87484
|
};
|
|
86117
87485
|
}
|
|
87486
|
+
function parseRetentionNumber(raw2) {
|
|
87487
|
+
if (raw2 === undefined || raw2.trim() === "")
|
|
87488
|
+
return;
|
|
87489
|
+
const value2 = Number(raw2);
|
|
87490
|
+
if (!Number.isInteger(value2) || value2 < 0)
|
|
87491
|
+
return;
|
|
87492
|
+
return value2;
|
|
87493
|
+
}
|
|
87494
|
+
function parseRetentionPrefixes(env) {
|
|
87495
|
+
const prefixes = {};
|
|
87496
|
+
for (const [key, value2] of Object.entries(env)) {
|
|
87497
|
+
const match2 = /^SPUR_EVENT_RETENTION_(.+)$/.exec(key);
|
|
87498
|
+
if (!match2 || match2[1] === "DEFAULT")
|
|
87499
|
+
continue;
|
|
87500
|
+
const prefix = match2[1];
|
|
87501
|
+
if (!prefix)
|
|
87502
|
+
continue;
|
|
87503
|
+
const quota = parseRetentionNumber(value2);
|
|
87504
|
+
if (quota !== undefined)
|
|
87505
|
+
prefixes[prefix.toLowerCase()] = quota;
|
|
87506
|
+
}
|
|
87507
|
+
return prefixes;
|
|
87508
|
+
}
|
|
87509
|
+
|
|
87510
|
+
// ../server/src/bootstrap.ts
|
|
86118
87511
|
function createApp(appRt, opts) {
|
|
86119
87512
|
const app = new Hono2;
|
|
86120
87513
|
mountMiddleware(app, appRt);
|
|
@@ -86139,7 +87532,7 @@ function createApp(appRt, opts) {
|
|
|
86139
87532
|
app.use("*", async (c3, next) => {
|
|
86140
87533
|
const pathname = c3.req.path === "/" ? "/index.html" : c3.req.path;
|
|
86141
87534
|
try {
|
|
86142
|
-
const file2 = Bun.file(
|
|
87535
|
+
const file2 = Bun.file(join20(webDistPath, pathname));
|
|
86143
87536
|
if (await file2.exists()) {
|
|
86144
87537
|
const headers = new Headers({ "content-type": file2.type });
|
|
86145
87538
|
return new Response(file2.stream(), { headers });
|
|
@@ -86152,7 +87545,7 @@ function createApp(appRt, opts) {
|
|
|
86152
87545
|
return c3.json({ error: "Not Found" }, 404);
|
|
86153
87546
|
}
|
|
86154
87547
|
try {
|
|
86155
|
-
const indexFile = Bun.file(
|
|
87548
|
+
const indexFile = Bun.file(join20(webDistPath, "index.html"));
|
|
86156
87549
|
if (await indexFile.exists()) {
|
|
86157
87550
|
return new Response(indexFile.stream(), {
|
|
86158
87551
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -86173,7 +87566,7 @@ init_src3();
|
|
|
86173
87566
|
init_src();
|
|
86174
87567
|
init_src2();
|
|
86175
87568
|
init_dist5();
|
|
86176
|
-
import { dirname as
|
|
87569
|
+
import { dirname as dirname14, join as join21 } from "path";
|
|
86177
87570
|
var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
|
|
86178
87571
|
|
|
86179
87572
|
class LazyPlanningEventEmitter {
|
|
@@ -86208,7 +87601,7 @@ class NotConfiguredError extends Error {
|
|
|
86208
87601
|
function createServerContext(appRt, options) {
|
|
86209
87602
|
const cwd = options.cwd;
|
|
86210
87603
|
const fs3 = options.fs;
|
|
86211
|
-
const dbUrl = options.dbUrl ??
|
|
87604
|
+
const dbUrl = options.dbUrl ?? join21(cwd, DEFAULT_DATABASE_URL);
|
|
86212
87605
|
const eventsBus = options.eventsBus ?? appRt.events;
|
|
86213
87606
|
const jobQueueEnabled = options.jobQueueEnabled ?? false;
|
|
86214
87607
|
const folders = options.folders ?? DEFAULT_PLANNING_FOLDERS;
|
|
@@ -86231,6 +87624,7 @@ function createServerContext(appRt, options) {
|
|
|
86231
87624
|
let agentSvc;
|
|
86232
87625
|
let ruleSvc;
|
|
86233
87626
|
let workflowSvc;
|
|
87627
|
+
let runStoreSvc;
|
|
86234
87628
|
let systemEventDaoPromise;
|
|
86235
87629
|
let jobQueuePromise;
|
|
86236
87630
|
let queueConsumerPromise;
|
|
@@ -86242,7 +87636,7 @@ function createServerContext(appRt, options) {
|
|
|
86242
87636
|
if (!dbPromise) {
|
|
86243
87637
|
const created = (async () => {
|
|
86244
87638
|
if (dbUrl !== IN_MEMORY_DATABASE_URL) {
|
|
86245
|
-
await fs3.ensureDir(
|
|
87639
|
+
await fs3.ensureDir(dirname14(dbUrl));
|
|
86246
87640
|
}
|
|
86247
87641
|
return createMigratedDbViaRuntime({ url: dbUrl });
|
|
86248
87642
|
})();
|
|
@@ -86372,6 +87766,13 @@ function createServerContext(appRt, options) {
|
|
|
86372
87766
|
systemEventDaoPromise ??= this.getDb().then((db2) => new SystemEventDao(db2));
|
|
86373
87767
|
return systemEventDaoPromise;
|
|
86374
87768
|
},
|
|
87769
|
+
runStoreService() {
|
|
87770
|
+
runStoreSvc ??= new RunStoreService({
|
|
87771
|
+
getDb: this.getDb.bind(this),
|
|
87772
|
+
secretValues: configuredSecretValues(options.env ?? process.env)
|
|
87773
|
+
});
|
|
87774
|
+
return runStoreSvc;
|
|
87775
|
+
},
|
|
86375
87776
|
planningFolders() {
|
|
86376
87777
|
return folders;
|
|
86377
87778
|
},
|
|
@@ -86416,15 +87817,23 @@ function createServerContext(appRt, options) {
|
|
|
86416
87817
|
init_system_event_tap();
|
|
86417
87818
|
|
|
86418
87819
|
// ../server/src/open-url.ts
|
|
86419
|
-
|
|
87820
|
+
init_dist5();
|
|
87821
|
+
async function openUrl(url2, deps = {}) {
|
|
86420
87822
|
const argv = process.platform === "darwin" ? ["open", url2] : process.platform === "win32" ? ["cmd", "/c", "start", "", url2] : ["xdg-open", url2];
|
|
86421
|
-
|
|
87823
|
+
const executor = deps.executor ?? new NodeProcessExecutor3;
|
|
87824
|
+
await executor.run({
|
|
87825
|
+
command: argv[0],
|
|
87826
|
+
args: [...argv.slice(1)],
|
|
87827
|
+
forceBuffered: true,
|
|
87828
|
+
rejectOnError: false
|
|
87829
|
+
});
|
|
86422
87830
|
}
|
|
86423
87831
|
|
|
86424
87832
|
// ../server/src/serve.ts
|
|
86425
87833
|
var SYSTEM_EVENTS_PRUNE_JOB = "system-events-prune";
|
|
86426
87834
|
var SMOKE_JOB = "smoke";
|
|
86427
87835
|
var TASK_ACTION_JOB = "task-action";
|
|
87836
|
+
var FEATURE_ACTION_JOB = "feature-action";
|
|
86428
87837
|
var SYSTEM_EVENTS_PRUNE_CRON = "300000";
|
|
86429
87838
|
var SMOKE_CRON = "600000";
|
|
86430
87839
|
var defaultDeps = {
|
|
@@ -86508,16 +87917,59 @@ async function runTaskActionJob(ctx, env, payload, createAgentService = createTa
|
|
|
86508
87917
|
throw new Error(`Task action ${job.action} for ${job.wbs} failed with exit code ${exitCode}`);
|
|
86509
87918
|
}
|
|
86510
87919
|
}
|
|
87920
|
+
function parseFeatureActionJob(payload) {
|
|
87921
|
+
if (typeof payload !== "object" || payload === null) {
|
|
87922
|
+
throw new Error("Invalid feature-action payload: expected object");
|
|
87923
|
+
}
|
|
87924
|
+
const candidate = payload;
|
|
87925
|
+
if (typeof candidate.featureId !== "string" || typeof candidate.action !== "string") {
|
|
87926
|
+
throw new Error("Invalid feature-action payload: missing featureId/action");
|
|
87927
|
+
}
|
|
87928
|
+
if (typeof candidate.command !== "string" || candidate.command.trim() === "") {
|
|
87929
|
+
throw new Error("Invalid feature-action payload: missing command");
|
|
87930
|
+
}
|
|
87931
|
+
return {
|
|
87932
|
+
featureId: candidate.featureId,
|
|
87933
|
+
action: candidate.action,
|
|
87934
|
+
command: candidate.command,
|
|
87935
|
+
channel: typeof candidate.channel === "string" ? candidate.channel : undefined,
|
|
87936
|
+
skipDeps: typeof candidate.skipDeps === "boolean" ? candidate.skipDeps : undefined
|
|
87937
|
+
};
|
|
87938
|
+
}
|
|
87939
|
+
async function runFeatureActionJob(ctx, env, payload, createAgentService = createTaskActionAgentService) {
|
|
87940
|
+
const job = parseFeatureActionJob(payload);
|
|
87941
|
+
const agentService = createAgentService === createTaskActionAgentService ? ctx.agentService() : createAgentService({
|
|
87942
|
+
cwd: ctx.cwd,
|
|
87943
|
+
env,
|
|
87944
|
+
events: ctx.eventBus(),
|
|
87945
|
+
output: { write: () => {}, error: () => {} }
|
|
87946
|
+
});
|
|
87947
|
+
const flags = {
|
|
87948
|
+
cwd: ctx.cwd,
|
|
87949
|
+
json: true,
|
|
87950
|
+
...job.channel !== undefined ? { agent: job.channel } : {}
|
|
87951
|
+
};
|
|
87952
|
+
const exitCode = await agentService.run(job.command, flags);
|
|
87953
|
+
if (exitCode !== 0) {
|
|
87954
|
+
throw new Error(`Feature action ${job.action} for ${job.featureId} failed with exit code ${exitCode}`);
|
|
87955
|
+
}
|
|
87956
|
+
}
|
|
87957
|
+
async function handleTaskActionJob(ctx, env, payload) {
|
|
87958
|
+
await runTaskActionJob(ctx, env, payload);
|
|
87959
|
+
}
|
|
87960
|
+
async function handleFeatureActionJob(ctx, env, payload) {
|
|
87961
|
+
await runFeatureActionJob(ctx, env, payload);
|
|
87962
|
+
}
|
|
86511
87963
|
async function resolveWebDistPath(configuredPath) {
|
|
86512
|
-
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath :
|
|
86513
|
-
|
|
86514
|
-
|
|
86515
|
-
|
|
86516
|
-
|
|
86517
|
-
|
|
87964
|
+
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join22(process.cwd(), configuredPath)] : [
|
|
87965
|
+
join22(process.cwd(), "dist/web"),
|
|
87966
|
+
join22(import.meta.dir, "web"),
|
|
87967
|
+
join22(dirname15(process.execPath), "web"),
|
|
87968
|
+
join22(dirname15(process.execPath), "../web"),
|
|
87969
|
+
join22(import.meta.dir, "../../../dist/web")
|
|
86518
87970
|
];
|
|
86519
87971
|
for (const candidate of candidates) {
|
|
86520
|
-
if (await Bun.file(
|
|
87972
|
+
if (await Bun.file(join22(candidate, "index.html")).exists()) {
|
|
86521
87973
|
return candidate;
|
|
86522
87974
|
}
|
|
86523
87975
|
}
|
|
@@ -86533,7 +87985,7 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
86533
87985
|
async start(appRt) {
|
|
86534
87986
|
const fs3 = deps.createNodeFileSystem(process.cwd());
|
|
86535
87987
|
if (options.dbUrl && options.dbUrl !== IN_MEMORY_DATABASE_URL) {
|
|
86536
|
-
await fs3.ensureDir(
|
|
87988
|
+
await fs3.ensureDir(dirname15(options.dbUrl));
|
|
86537
87989
|
}
|
|
86538
87990
|
let scheduler3;
|
|
86539
87991
|
if (bootConfig.scheduler.enabled) {
|
|
@@ -86573,7 +88025,9 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
86573
88025
|
try {
|
|
86574
88026
|
const dao2 = new SystemEventDao(await ctx.getDb());
|
|
86575
88027
|
registerSystemEventTap(ctx.eventBus(), dao2, appRt.logger, {
|
|
86576
|
-
diagnosticEnabled: bootConfig.events.diagnostic === true
|
|
88028
|
+
diagnosticEnabled: bootConfig.events.diagnostic === true,
|
|
88029
|
+
retention: bootConfig.events.retention,
|
|
88030
|
+
secretValues: configuredSecretValues(env)
|
|
86577
88031
|
});
|
|
86578
88032
|
appRt.logger.debug("system_events tap registered", {
|
|
86579
88033
|
diagnostic: bootConfig.events.diagnostic === true
|
|
@@ -86585,14 +88039,14 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
86585
88039
|
const app = deps.createApp(appRt, { fs: fs3, ctx });
|
|
86586
88040
|
if (bootConfig.jobqueue.enabled) {
|
|
86587
88041
|
const registry2 = new JobHandlerRegistry;
|
|
88042
|
+
const retentionQuotas = resolveRetentionQuotas(bootConfig.events.retention);
|
|
86588
88043
|
registry2.register(SYSTEM_EVENTS_PRUNE_JOB, async () => {
|
|
86589
88044
|
const dao2 = await ctx.systemEventDao();
|
|
86590
|
-
await dao2.
|
|
88045
|
+
await dao2.pruneQuotas(retentionQuotas);
|
|
86591
88046
|
});
|
|
86592
88047
|
registry2.register(SMOKE_JOB, async () => {});
|
|
86593
|
-
registry2.register(TASK_ACTION_JOB,
|
|
86594
|
-
|
|
86595
|
-
});
|
|
88048
|
+
registry2.register(TASK_ACTION_JOB, (payload) => handleTaskActionJob(ctx, env, payload));
|
|
88049
|
+
registry2.register(FEATURE_ACTION_JOB, (payload) => handleFeatureActionJob(ctx, env, payload));
|
|
86596
88050
|
jobWorker = new JobWorkerService({
|
|
86597
88051
|
consumer: await ctx.queueConsumer(),
|
|
86598
88052
|
registry: registry2
|
|
@@ -86610,8 +88064,33 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
86610
88064
|
port: options.port,
|
|
86611
88065
|
hostname: options.host
|
|
86612
88066
|
});
|
|
88067
|
+
const projectRegistry = new ProjectRegistry;
|
|
88068
|
+
const projectCwd = process.cwd();
|
|
88069
|
+
const projectName = basename6(projectCwd);
|
|
88070
|
+
try {
|
|
88071
|
+
await projectRegistry.upsert({ path: projectCwd, name: projectName, port: server.port });
|
|
88072
|
+
} catch (err) {
|
|
88073
|
+
appRt.logger.warn("Failed to register project in ProjectRegistry", { error: String(err) });
|
|
88074
|
+
}
|
|
88075
|
+
let shuttingDown = false;
|
|
88076
|
+
const onSigInt = () => {
|
|
88077
|
+
shutdown("SIGINT");
|
|
88078
|
+
};
|
|
88079
|
+
const onSigTerm = () => {
|
|
88080
|
+
shutdown("SIGTERM");
|
|
88081
|
+
};
|
|
86613
88082
|
const shutdown = async (signal) => {
|
|
88083
|
+
if (shuttingDown)
|
|
88084
|
+
return;
|
|
88085
|
+
shuttingDown = true;
|
|
88086
|
+
process.off("SIGINT", onSigInt);
|
|
88087
|
+
process.off("SIGTERM", onSigTerm);
|
|
86614
88088
|
appRt.logger.info("Shutting down server", { signal });
|
|
88089
|
+
try {
|
|
88090
|
+
await projectRegistry.setPort(projectCwd, 0);
|
|
88091
|
+
} catch (err) {
|
|
88092
|
+
appRt.logger.warn("Failed to deregister project port in ProjectRegistry", { error: String(err) });
|
|
88093
|
+
}
|
|
86615
88094
|
if (scheduler3)
|
|
86616
88095
|
await scheduler3.stop();
|
|
86617
88096
|
if (jobWorker)
|
|
@@ -86625,8 +88104,8 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
86625
88104
|
await appRt.stop("shutdown");
|
|
86626
88105
|
process.exit(0);
|
|
86627
88106
|
};
|
|
86628
|
-
process.on("SIGINT",
|
|
86629
|
-
process.on("SIGTERM",
|
|
88107
|
+
process.on("SIGINT", onSigInt);
|
|
88108
|
+
process.on("SIGTERM", onSigTerm);
|
|
86630
88109
|
const url2 = `http://${options.host}:${options.port}`;
|
|
86631
88110
|
appRt.logger.info("Server started", {
|
|
86632
88111
|
port: options.port,
|
|
@@ -86642,12 +88121,14 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
86642
88121
|
}
|
|
86643
88122
|
});
|
|
86644
88123
|
}
|
|
88124
|
+
// ../server/src/worker-app.ts
|
|
88125
|
+
var startedAt2 = Date.now();
|
|
86645
88126
|
// ../server/src/index.ts
|
|
86646
88127
|
if (false) {}
|
|
86647
88128
|
|
|
86648
88129
|
// src/commands/serve.ts
|
|
86649
88130
|
function resolveServeDbUrl(cwd, env, configuredUrl) {
|
|
86650
|
-
return env.DATABASE_URL === undefined ?
|
|
88131
|
+
return env.DATABASE_URL === undefined ? join23(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
|
|
86651
88132
|
}
|
|
86652
88133
|
function registerServeCommand(program2, context4) {
|
|
86653
88134
|
program2.command("serve").summary("start the Spur web server (local fallback)").option("--port <n>", "Server port (env: PORT, default: 3000)", parseInt).option("--host <addr>", "Bind address (env: HOST, default: localhost)").option("--no-open", "Skip opening the browser").option("--cwd <path>", "Working directory", context4.cwd).option("--json", "Output { port, url, pid } and exit").action(async (options) => {
|
|
@@ -86686,7 +88167,7 @@ function registerServeCommand(program2, context4) {
|
|
|
86686
88167
|
}
|
|
86687
88168
|
|
|
86688
88169
|
// src/commands/status.ts
|
|
86689
|
-
import { join as
|
|
88170
|
+
import { join as join24 } from "path";
|
|
86690
88171
|
|
|
86691
88172
|
// src/errors.ts
|
|
86692
88173
|
class CommandError extends Error {
|
|
@@ -86757,8 +88238,8 @@ function registerStatusCommand(program2, context4) {
|
|
|
86757
88238
|
}
|
|
86758
88239
|
async function runStatusCore(path9, options, context4) {
|
|
86759
88240
|
const [packageJsonExists, spurConfigExists, git, agentSpecs] = await Promise.all([
|
|
86760
|
-
context4.fs.exists(
|
|
86761
|
-
context4.fs.exists(
|
|
88241
|
+
context4.fs.exists(join24(context4.cwd, "package.json")),
|
|
88242
|
+
context4.fs.exists(join24(context4.cwd, ".spur", "config.yaml")),
|
|
86762
88243
|
gitContext(context4.cwd),
|
|
86763
88244
|
listAgentSpecIds(context4)
|
|
86764
88245
|
]);
|
|
@@ -86786,26 +88267,27 @@ async function runStatusCore(path9, options, context4) {
|
|
|
86786
88267
|
return status.ok ? 0 : 1;
|
|
86787
88268
|
}
|
|
86788
88269
|
async function listAgentSpecIds(context4) {
|
|
86789
|
-
const dir =
|
|
88270
|
+
const dir = join24(context4.cwd, ".spur", "agents");
|
|
86790
88271
|
if (!await context4.fs.exists(dir))
|
|
86791
88272
|
return [];
|
|
86792
88273
|
const entries = await context4.fs.readDir(dir);
|
|
86793
88274
|
return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
|
|
86794
88275
|
}
|
|
86795
88276
|
async function readTargetStatus(context4, targetPath) {
|
|
86796
|
-
const resolved =
|
|
86797
|
-
const
|
|
86798
|
-
if (
|
|
88277
|
+
const resolved = join24(context4.cwd, targetPath);
|
|
88278
|
+
const stat = await context4.fs.stat(resolved);
|
|
88279
|
+
if (stat === null)
|
|
86799
88280
|
throw new CommandError(`status failed: path does not exist at ${resolved}`);
|
|
86800
|
-
return { path: targetPath, size:
|
|
88281
|
+
return { path: targetPath, size: stat.size, isFile: stat.isFile(), isDirectory: stat.isDirectory() };
|
|
86801
88282
|
}
|
|
86802
88283
|
|
|
86803
88284
|
// src/commands/task.ts
|
|
86804
88285
|
init_src3();
|
|
86805
88286
|
init_loader();
|
|
86806
88287
|
init_src2();
|
|
86807
|
-
|
|
86808
|
-
import {
|
|
88288
|
+
init_dist5();
|
|
88289
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
88290
|
+
import { join as join25 } from "path";
|
|
86809
88291
|
// schemas/section-matrix.schema.json
|
|
86810
88292
|
var section_matrix_schema_default = {
|
|
86811
88293
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -87489,7 +88971,7 @@ ${result.content}`);
|
|
|
87489
88971
|
let forcedDoneVerdict;
|
|
87490
88972
|
if (status === "done") {
|
|
87491
88973
|
const current = await svc.show(wbs);
|
|
87492
|
-
const verdictDir = options.verdictDir ??
|
|
88974
|
+
const verdictDir = options.verdictDir ?? join25(context4.cwd, ".spur", "run");
|
|
87493
88975
|
const loaded = await readVerdictArtifact(context4.fs, verdictDir, wbs);
|
|
87494
88976
|
const guardOutcome = evaluateDoneTransition({
|
|
87495
88977
|
wbs,
|
|
@@ -87784,7 +89266,7 @@ ${result.content}`);
|
|
|
87784
89266
|
const answerPath = options.fromAnswer ?? `.spur/run/${wbs}-verify-answer.txt`;
|
|
87785
89267
|
let answerText;
|
|
87786
89268
|
try {
|
|
87787
|
-
answerText =
|
|
89269
|
+
answerText = await context4.fs.readFile(context4.fs.resolve(answerPath));
|
|
87788
89270
|
} catch {
|
|
87789
89271
|
context4.output.error(`Answer file not found: ${answerPath}`);
|
|
87790
89272
|
context4.setExitCode(1);
|
|
@@ -87793,9 +89275,8 @@ ${result.content}`);
|
|
|
87793
89275
|
const taskCheckPassed = true;
|
|
87794
89276
|
const result = deriveVerdict2(answerText, taskCheckPassed);
|
|
87795
89277
|
const jsonOut = JSON.stringify({ wbs, ...result, source: "spur-task-verdict" }, null, 2);
|
|
87796
|
-
|
|
87797
|
-
|
|
87798
|
-
writeFileSync4(`.spur/run/${wbs}-verdict.json`, `${jsonOut}
|
|
89278
|
+
await context4.fs.ensureDir(".spur/run");
|
|
89279
|
+
await context4.fs.writeFile(`.spur/run/${wbs}-verdict.json`, `${jsonOut}
|
|
87799
89280
|
`);
|
|
87800
89281
|
if (options.json) {
|
|
87801
89282
|
context4.output.write(jsonOut);
|
|
@@ -87810,7 +89291,7 @@ ${result.content}`);
|
|
|
87810
89291
|
const inputPath = options.fromFile ?? ".spur/run/verifyall-batch-input.json";
|
|
87811
89292
|
let raw2;
|
|
87812
89293
|
try {
|
|
87813
|
-
raw2 =
|
|
89294
|
+
raw2 = await context4.fs.readFile(context4.fs.resolve(inputPath));
|
|
87814
89295
|
} catch {
|
|
87815
89296
|
context4.output.error(`Batch input file not found: ${inputPath}`);
|
|
87816
89297
|
context4.setExitCode(1);
|
|
@@ -88030,17 +89511,17 @@ function loadTemplateContent(projectRoot, variant) {
|
|
|
88030
89511
|
return templateContentCache.get(variant);
|
|
88031
89512
|
if (templateMissSet.has(variant))
|
|
88032
89513
|
return;
|
|
88033
|
-
const localPath =
|
|
88034
|
-
if (
|
|
88035
|
-
const content =
|
|
89514
|
+
const localPath = join25(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
|
|
89515
|
+
if (existsSync10(localPath)) {
|
|
89516
|
+
const content = readFileSync9(localPath, "utf8");
|
|
88036
89517
|
templateContentCache.set(variant, content);
|
|
88037
89518
|
return content;
|
|
88038
89519
|
}
|
|
88039
89520
|
const root = bundledConfigRoot();
|
|
88040
89521
|
if (root !== null) {
|
|
88041
|
-
const templatePath =
|
|
88042
|
-
if (
|
|
88043
|
-
const content =
|
|
89522
|
+
const templatePath = join25(root, "templates", "task", `${variant}.md`);
|
|
89523
|
+
if (existsSync10(templatePath)) {
|
|
89524
|
+
const content = readFileSync9(templatePath, "utf8");
|
|
88044
89525
|
templateContentCache.set(variant, content);
|
|
88045
89526
|
return content;
|
|
88046
89527
|
}
|
|
@@ -88054,15 +89535,15 @@ function loadTemplateBodies(projectRoot, variant) {
|
|
|
88054
89535
|
if (cached2 !== undefined)
|
|
88055
89536
|
return cached2;
|
|
88056
89537
|
let bodies = {};
|
|
88057
|
-
const localPath =
|
|
88058
|
-
if (
|
|
88059
|
-
bodies = extractTemplateBodies(
|
|
89538
|
+
const localPath = join25(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
|
|
89539
|
+
if (existsSync10(localPath)) {
|
|
89540
|
+
bodies = extractTemplateBodies(readFileSync9(localPath, "utf8"));
|
|
88060
89541
|
} else {
|
|
88061
89542
|
const root = bundledConfigRoot();
|
|
88062
89543
|
if (root !== null) {
|
|
88063
|
-
const templatePath =
|
|
88064
|
-
if (
|
|
88065
|
-
bodies = extractTemplateBodies(
|
|
89544
|
+
const templatePath = join25(root, "templates", "task", `${variant}.md`);
|
|
89545
|
+
if (existsSync10(templatePath)) {
|
|
89546
|
+
bodies = extractTemplateBodies(readFileSync9(templatePath, "utf8"));
|
|
88066
89547
|
}
|
|
88067
89548
|
}
|
|
88068
89549
|
}
|
|
@@ -88102,8 +89583,9 @@ async function loadSectionMatrix(projectRoot) {
|
|
|
88102
89583
|
return promise2;
|
|
88103
89584
|
}
|
|
88104
89585
|
async function loadSectionMatrixUncached(projectRoot) {
|
|
88105
|
-
const
|
|
88106
|
-
|
|
89586
|
+
const fs3 = createNodeFileSystem3(projectRoot);
|
|
89587
|
+
const localPath = fs3.resolve(".spur", "tasks", "section-matrix.yaml");
|
|
89588
|
+
if (await fs3.exists(localPath)) {
|
|
88107
89589
|
const data = await loadStructuredSpurConfig(localPath, {
|
|
88108
89590
|
validateJsonSchema: true,
|
|
88109
89591
|
embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
|
|
@@ -88112,8 +89594,8 @@ async function loadSectionMatrixUncached(projectRoot) {
|
|
|
88112
89594
|
}
|
|
88113
89595
|
const root = bundledConfigRoot();
|
|
88114
89596
|
if (root !== null) {
|
|
88115
|
-
const matrixPath =
|
|
88116
|
-
if (
|
|
89597
|
+
const matrixPath = join25(root, "tasks", "section-matrix.yaml");
|
|
89598
|
+
if (await fs3.exists(matrixPath)) {
|
|
88117
89599
|
const data = await loadStructuredSpurConfig(matrixPath, {
|
|
88118
89600
|
validateJsonSchema: true,
|
|
88119
89601
|
embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
|
|
@@ -88134,6 +89616,7 @@ var FALLBACK_MATRIX = {
|
|
|
88134
89616
|
|
|
88135
89617
|
// src/commands/team.ts
|
|
88136
89618
|
init_src3();
|
|
89619
|
+
init_dist6();
|
|
88137
89620
|
var _testFetch;
|
|
88138
89621
|
function teamFetch(url2, init) {
|
|
88139
89622
|
const fetcher = _testFetch ?? fetch;
|
|
@@ -88168,10 +89651,15 @@ function registerTeamCommand(program2, context4) {
|
|
|
88168
89651
|
});
|
|
88169
89652
|
}
|
|
88170
89653
|
async function runTeamAssign(taskId, agentId, context4) {
|
|
88171
|
-
const svc =
|
|
88172
|
-
|
|
88173
|
-
|
|
88174
|
-
|
|
89654
|
+
const { svc, ledger } = await makeTeamServiceWithLedger(context4);
|
|
89655
|
+
try {
|
|
89656
|
+
await svc.assignTask(taskId, agentId);
|
|
89657
|
+
context4.output.write(`assigned ${taskId} \u2192 ${agentId}`);
|
|
89658
|
+
return 0;
|
|
89659
|
+
} finally {
|
|
89660
|
+
await ledger.flush();
|
|
89661
|
+
ledger.unsubscribe();
|
|
89662
|
+
}
|
|
88175
89663
|
}
|
|
88176
89664
|
async function runTeamStatus(options, context4) {
|
|
88177
89665
|
const svc = new TeamService(context4);
|
|
@@ -88314,55 +89802,74 @@ function formatTeamBlock(team) {
|
|
|
88314
89802
|
return [header, ...rows].join(`
|
|
88315
89803
|
`);
|
|
88316
89804
|
}
|
|
89805
|
+
async function makeTeamServiceWithLedger(context4) {
|
|
89806
|
+
const bus = new EventBus;
|
|
89807
|
+
const ledger = await attachSystemEventLedger(bus, context4);
|
|
89808
|
+
const svc = new TeamService({
|
|
89809
|
+
...context4,
|
|
89810
|
+
eventBus: bus
|
|
89811
|
+
});
|
|
89812
|
+
return { svc, ledger };
|
|
89813
|
+
}
|
|
88317
89814
|
async function runTeamUp(team, options, context4) {
|
|
88318
|
-
const svc =
|
|
88319
|
-
let result;
|
|
89815
|
+
const { svc, ledger } = await makeTeamServiceWithLedger(context4);
|
|
88320
89816
|
try {
|
|
88321
|
-
result
|
|
88322
|
-
|
|
88323
|
-
|
|
88324
|
-
|
|
88325
|
-
|
|
88326
|
-
|
|
88327
|
-
if (options.check !== true && result.upserted.length > 0) {
|
|
88328
|
-
const specs = await svc.listAgentSpecs();
|
|
88329
|
-
const autostart = specs.filter((spec) => spec.autoStart === true && result.upserted.includes(spec.id));
|
|
88330
|
-
for (const spec of autostart) {
|
|
88331
|
-
const res = await performTeamStart(spec.id, { server: options.server });
|
|
88332
|
-
if ("ok" in res && res.ok === true)
|
|
88333
|
-
started.push(spec.id);
|
|
89817
|
+
let result;
|
|
89818
|
+
try {
|
|
89819
|
+
result = await svc.materializeTeam(team, { check: options.check === true });
|
|
89820
|
+
} catch (error51) {
|
|
89821
|
+
context4.output.error(error51 instanceof Error ? error51.message : String(error51));
|
|
89822
|
+
return 1;
|
|
88334
89823
|
}
|
|
89824
|
+
const started = [];
|
|
89825
|
+
if (options.check !== true && result.upserted.length > 0) {
|
|
89826
|
+
const specs = await svc.listAgentSpecs();
|
|
89827
|
+
const autostart = specs.filter((spec) => spec.autoStart === true && result.upserted.includes(spec.id));
|
|
89828
|
+
for (const spec of autostart) {
|
|
89829
|
+
const res = await performTeamStart(spec.id, { server: options.server });
|
|
89830
|
+
if ("ok" in res && res.ok === true)
|
|
89831
|
+
started.push(spec.id);
|
|
89832
|
+
}
|
|
89833
|
+
}
|
|
89834
|
+
if (options.json === true) {
|
|
89835
|
+
context4.output.write(toJson2({ ...result, started }));
|
|
89836
|
+
} else {
|
|
89837
|
+
const verb = options.check === true ? "would materialize" : "materialized";
|
|
89838
|
+
const startNote = started.length > 0 ? `, started ${started.length}` : "";
|
|
89839
|
+
context4.output.write(`team ${team}: ${verb} ${result.upserted.length} member(s), prune ${result.orphaned.length}${startNote}`);
|
|
89840
|
+
}
|
|
89841
|
+
return 0;
|
|
89842
|
+
} finally {
|
|
89843
|
+
await ledger.flush();
|
|
89844
|
+
ledger.unsubscribe();
|
|
88335
89845
|
}
|
|
88336
|
-
if (options.json === true) {
|
|
88337
|
-
context4.output.write(toJson2({ ...result, started }));
|
|
88338
|
-
} else {
|
|
88339
|
-
const verb = options.check === true ? "would materialize" : "materialized";
|
|
88340
|
-
const startNote = started.length > 0 ? `, started ${started.length}` : "";
|
|
88341
|
-
context4.output.write(`team ${team}: ${verb} ${result.upserted.length} member(s), prune ${result.orphaned.length}${startNote}`);
|
|
88342
|
-
}
|
|
88343
|
-
return 0;
|
|
88344
89846
|
}
|
|
88345
89847
|
async function runTeamDown(team, options, context4) {
|
|
88346
|
-
const svc =
|
|
88347
|
-
let result;
|
|
89848
|
+
const { svc, ledger } = await makeTeamServiceWithLedger(context4);
|
|
88348
89849
|
try {
|
|
88349
|
-
result
|
|
88350
|
-
|
|
88351
|
-
|
|
88352
|
-
|
|
88353
|
-
|
|
88354
|
-
|
|
88355
|
-
|
|
88356
|
-
const
|
|
88357
|
-
|
|
88358
|
-
|
|
88359
|
-
|
|
88360
|
-
|
|
88361
|
-
|
|
88362
|
-
|
|
88363
|
-
|
|
89850
|
+
let result;
|
|
89851
|
+
try {
|
|
89852
|
+
result = await svc.teardownTeam(team, { purge: options.purge === true });
|
|
89853
|
+
} catch (error51) {
|
|
89854
|
+
context4.output.error(error51 instanceof Error ? error51.message : String(error51));
|
|
89855
|
+
return 1;
|
|
89856
|
+
}
|
|
89857
|
+
const stopped = [];
|
|
89858
|
+
for (const id of result.stopped) {
|
|
89859
|
+
const res = await performTeamStop(id, { server: options.server });
|
|
89860
|
+
if ("ok" in res && res.ok === true)
|
|
89861
|
+
stopped.push(id);
|
|
89862
|
+
}
|
|
89863
|
+
if (options.json === true) {
|
|
89864
|
+
context4.output.write(toJson2({ ...result, stopped }));
|
|
89865
|
+
} else {
|
|
89866
|
+
context4.output.write(`team ${team}: stopped ${stopped.length}, purged ${result.purged.length}`);
|
|
89867
|
+
}
|
|
89868
|
+
return 0;
|
|
89869
|
+
} finally {
|
|
89870
|
+
await ledger.flush();
|
|
89871
|
+
ledger.unsubscribe();
|
|
88364
89872
|
}
|
|
88365
|
-
return 0;
|
|
88366
89873
|
}
|
|
88367
89874
|
|
|
88368
89875
|
// src/commands/workflow.ts
|
|
@@ -88370,10 +89877,29 @@ init_src3();
|
|
|
88370
89877
|
init_loader();
|
|
88371
89878
|
init_dist7();
|
|
88372
89879
|
init_dist6();
|
|
88373
|
-
|
|
88374
|
-
import { resolve as
|
|
89880
|
+
init_dist5();
|
|
89881
|
+
import { resolve as resolve8 } from "path";
|
|
88375
89882
|
import { createInterface } from "readline";
|
|
88376
89883
|
import { setTimeout as sleep3 } from "timers/promises";
|
|
89884
|
+
function shQuote2(value2) {
|
|
89885
|
+
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
89886
|
+
}
|
|
89887
|
+
async function spawnAsyncWorkflowWorker(spurBin, cmd) {
|
|
89888
|
+
const line = [spurBin, ...cmd].map(shQuote2).join(" ");
|
|
89889
|
+
const env = {};
|
|
89890
|
+
for (const [key, value2] of Object.entries(process.env)) {
|
|
89891
|
+
if (value2 !== undefined)
|
|
89892
|
+
env[key] = value2;
|
|
89893
|
+
}
|
|
89894
|
+
env.SPUR_ASYNC_WORKER = "1";
|
|
89895
|
+
await new NodeProcessExecutor3().run({
|
|
89896
|
+
command: process.platform === "win32" ? "cmd" : "/bin/sh",
|
|
89897
|
+
args: process.platform === "win32" ? ["/c", `start /b "" ${[spurBin, ...cmd].map((c3) => `"${c3.replace(/"/g, '""')}"`).join(" ")}`] : ["-c", `nohup ${line} </dev/null >/dev/null 2>&1 &`],
|
|
89898
|
+
env,
|
|
89899
|
+
forceBuffered: true,
|
|
89900
|
+
rejectOnError: false
|
|
89901
|
+
});
|
|
89902
|
+
}
|
|
88377
89903
|
function parseVars(raw2) {
|
|
88378
89904
|
if (raw2 === undefined) {
|
|
88379
89905
|
return;
|
|
@@ -88410,14 +89936,17 @@ async function resolveWorkflowPaths(cwd) {
|
|
|
88410
89936
|
}
|
|
88411
89937
|
}
|
|
88412
89938
|
function registerWorkflowCommand(program2, context4) {
|
|
88413
|
-
const makeSvc = (json3,
|
|
89939
|
+
const makeSvc = (json3, bus) => new WorkflowAppService({
|
|
88414
89940
|
cwd: context4.cwd,
|
|
88415
89941
|
getDb: () => context4.getDb(),
|
|
88416
89942
|
agentService: () => context4.agentService(),
|
|
88417
89943
|
ruleService: () => context4.ruleService(),
|
|
88418
89944
|
hitlResponder: () => context4.hitlResponder(json3),
|
|
88419
89945
|
embeddedSchemas: () => EMBEDDED_SPUR_SCHEMAS,
|
|
88420
|
-
...
|
|
89946
|
+
...bus ? {
|
|
89947
|
+
observabilityBus: () => bus,
|
|
89948
|
+
events: () => bus
|
|
89949
|
+
} : {}
|
|
88421
89950
|
});
|
|
88422
89951
|
const workflow = program2.command("workflow").summary("validate and execute workflow YAML files");
|
|
88423
89952
|
workflow.command("validate").description("Validate a workflow definition.").argument("<file>", "Workflow YAML file").option("--no-schema", "Skip schema validation").option("--json", "Output machine-readable JSON where supported").action(async (file2, options) => {
|
|
@@ -88475,12 +90004,7 @@ ${result.errors.map((m) => ` - ${m}`).join(`
|
|
|
88475
90004
|
cmd.push("--trace-file");
|
|
88476
90005
|
}
|
|
88477
90006
|
try {
|
|
88478
|
-
|
|
88479
|
-
stdio: "ignore",
|
|
88480
|
-
detached: true,
|
|
88481
|
-
env: { ...process.env, SPUR_ASYNC_WORKER: "1" }
|
|
88482
|
-
});
|
|
88483
|
-
child.unref();
|
|
90007
|
+
await spawnAsyncWorkflowWorker(spurBin, cmd);
|
|
88484
90008
|
} catch {
|
|
88485
90009
|
const result2 = await makeSvc(options.json).run(file2, {
|
|
88486
90010
|
runId: runId2,
|
|
@@ -88508,21 +90032,19 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
88508
90032
|
const vars = { spurBin: resolveSpurBin(), ...parseVars(options.vars) };
|
|
88509
90033
|
const runId = options.runId || crypto.randomUUID();
|
|
88510
90034
|
const humanProgress = !json3 && !quiet && !silent;
|
|
88511
|
-
|
|
90035
|
+
const bus = new EventBus;
|
|
90036
|
+
const ledger = await attachSystemEventLedger(bus, context4);
|
|
88512
90037
|
let traceWriter;
|
|
88513
90038
|
const heartbeats = new Map;
|
|
88514
|
-
if (
|
|
88515
|
-
bus = new EventBus;
|
|
88516
|
-
}
|
|
88517
|
-
if (options.traceFile === true && bus !== undefined) {
|
|
90039
|
+
if (options.traceFile === true) {
|
|
88518
90040
|
traceWriter = new WorkflowTraceWriter(context4.cwd, runId);
|
|
88519
90041
|
traceWriter.attach(bus);
|
|
88520
90042
|
}
|
|
88521
|
-
if (humanProgress
|
|
90043
|
+
if (humanProgress) {
|
|
88522
90044
|
context4.output.write(`Run: ${runId}`);
|
|
88523
90045
|
if (options.plan !== false) {
|
|
88524
90046
|
try {
|
|
88525
|
-
const def = await loadWorkflowDef(
|
|
90047
|
+
const def = await loadWorkflowDef(resolve8(context4.cwd, file2), { validateSchema: false });
|
|
88526
90048
|
context4.output.write(renderRunPlan(def));
|
|
88527
90049
|
} catch {}
|
|
88528
90050
|
}
|
|
@@ -88536,9 +90058,9 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
88536
90058
|
bus.on("workflow.transition", report);
|
|
88537
90059
|
bus.on("workflow.action.started", (event2) => {
|
|
88538
90060
|
report(event2);
|
|
88539
|
-
const
|
|
90061
|
+
const startedAt3 = Date.parse(event2.at);
|
|
88540
90062
|
const timer = setInterval(() => {
|
|
88541
|
-
const line = renderActionHeartbeat(event2, Math.max(0, Date.now() -
|
|
90063
|
+
const line = renderActionHeartbeat(event2, Math.max(0, Date.now() - startedAt3), { detail });
|
|
88542
90064
|
if (line !== null)
|
|
88543
90065
|
context4.output.write(line);
|
|
88544
90066
|
}, 30000);
|
|
@@ -88563,7 +90085,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
88563
90085
|
});
|
|
88564
90086
|
}
|
|
88565
90087
|
const steeringController = options.steer === true ? new WorkflowSteeringController((ack) => {
|
|
88566
|
-
bus
|
|
90088
|
+
bus.emit("workflow.steering", ack);
|
|
88567
90089
|
context4.output.write(`[steer] ${ack.accepted ? "ack" : "nack"} ${ack.operation} \xB7 ${ack.reason ?? `version=${ack.version}`}`);
|
|
88568
90090
|
}, configuredSecretValues(context4.env), new Set(["operator"]), (snapshot) => {
|
|
88569
90091
|
if (snapshot.state === "boundary") {
|
|
@@ -88588,6 +90110,8 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
88588
90110
|
clearInterval(timer);
|
|
88589
90111
|
heartbeats.clear();
|
|
88590
90112
|
await traceWriter?.flush();
|
|
90113
|
+
await ledger.flush();
|
|
90114
|
+
ledger.unsubscribe();
|
|
88591
90115
|
steeringInput?.close();
|
|
88592
90116
|
}
|
|
88593
90117
|
if (json3)
|
|
@@ -88599,7 +90123,9 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
88599
90123
|
});
|
|
88600
90124
|
workflow.command("continue").description("Resume a paused (HITL) workflow run. Omit run-id to resume the most recent paused run.").argument("[run-id]", "Run ID to resume (default: the most recent paused run)").option("--yes", "Skip the CLI resume confirmation (does not set the persisted HITL answer)").option("--json", "Output machine-readable JSON where supported").action(async (runId, options) => {
|
|
88601
90125
|
const json3 = options.json === true;
|
|
88602
|
-
const
|
|
90126
|
+
const bus = new EventBus;
|
|
90127
|
+
const ledger = await attachSystemEventLedger(bus, context4);
|
|
90128
|
+
const svc = makeSvc(json3, bus);
|
|
88603
90129
|
try {
|
|
88604
90130
|
let targetId = runId;
|
|
88605
90131
|
if (targetId === undefined) {
|
|
@@ -88630,6 +90156,9 @@ Monitor with: spur workflow trace ${runId2} --follow`);
|
|
|
88630
90156
|
} catch (err) {
|
|
88631
90157
|
context4.output.error(String(err));
|
|
88632
90158
|
context4.setExitCode(1);
|
|
90159
|
+
} finally {
|
|
90160
|
+
await ledger.flush();
|
|
90161
|
+
ledger.unsubscribe();
|
|
88633
90162
|
}
|
|
88634
90163
|
});
|
|
88635
90164
|
workflow.command("clean").description("Finalize orphaned runs stuck in running/pending past a staleness threshold (mark as failed). " + "To cancel a single live run by id, use `spur workflow cancel <run-id>` instead.").option("--older-than <minutes>", "Staleness threshold in minutes", "30").option("--force", "Clean ALL non-terminal runs regardless of age (overrides --older-than)").option("--dry-run", "List what would be cleaned without writing").option("--json", "Output machine-readable JSON where supported").action(async (options) => {
|
|
@@ -88864,7 +90393,7 @@ init_src3();
|
|
|
88864
90393
|
init_src();
|
|
88865
90394
|
init_src2();
|
|
88866
90395
|
init_dist5();
|
|
88867
|
-
import { dirname as
|
|
90396
|
+
import { dirname as dirname16, join as join26, resolve as resolve9 } from "path";
|
|
88868
90397
|
import { isatty as isatty4 } from "tty";
|
|
88869
90398
|
|
|
88870
90399
|
// ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
|
|
@@ -90247,7 +91776,7 @@ class DefaultHitlResponder {
|
|
|
90247
91776
|
|
|
90248
91777
|
// src/context.ts
|
|
90249
91778
|
function createCliContext(options) {
|
|
90250
|
-
const cwd =
|
|
91779
|
+
const cwd = resolve9(options.cwd ?? process.cwd());
|
|
90251
91780
|
const env = options.env ?? process.env;
|
|
90252
91781
|
const fs3 = createNodeFileSystem3(cwd);
|
|
90253
91782
|
let dbPromise;
|
|
@@ -90265,7 +91794,15 @@ function createCliContext(options) {
|
|
|
90265
91794
|
setExitCode: options.setExitCode ?? noopSetExitCode,
|
|
90266
91795
|
output: options.output,
|
|
90267
91796
|
getDb,
|
|
90268
|
-
|
|
91797
|
+
...options.agentConfig !== undefined ? { agentConfig: options.agentConfig } : {},
|
|
91798
|
+
agentService: (serviceOptions) => new AgentService({
|
|
91799
|
+
cwd,
|
|
91800
|
+
env,
|
|
91801
|
+
output: options.output,
|
|
91802
|
+
agentConfig: options.agentConfig,
|
|
91803
|
+
...serviceOptions?.events !== undefined ? { events: serviceOptions.events } : {},
|
|
91804
|
+
...serviceOptions?.processRegistry !== undefined ? { processRegistry: serviceOptions.processRegistry } : {}
|
|
91805
|
+
}),
|
|
90269
91806
|
ruleService: () => new RuleService({ cwd, env, fs: fs3, output: options.output, getDb }),
|
|
90270
91807
|
hitlResponder: (json3) => {
|
|
90271
91808
|
if (isatty4(1) && json3 !== true)
|
|
@@ -90278,10 +91815,10 @@ function createCliContext(options) {
|
|
|
90278
91815
|
function noopSetExitCode(_code) {}
|
|
90279
91816
|
async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, dbUrl) {
|
|
90280
91817
|
const config4 = buildConfigFromEnv(env);
|
|
90281
|
-
const configuredUrl = env.DATABASE_URL === undefined ?
|
|
91818
|
+
const configuredUrl = env.DATABASE_URL === undefined ? join26(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
|
|
90282
91819
|
const url2 = dbUrl ?? configuredUrl;
|
|
90283
91820
|
if (url2 !== IN_MEMORY_DATABASE_URL) {
|
|
90284
|
-
await createNodeFileSystem3().ensureDir(
|
|
91821
|
+
await createNodeFileSystem3().ensureDir(dirname16(url2));
|
|
90285
91822
|
}
|
|
90286
91823
|
return createMigratedDb({ url: url2 });
|
|
90287
91824
|
}
|
|
@@ -90343,6 +91880,7 @@ async function runCommandDispatch(argv, context4, output2) {
|
|
|
90343
91880
|
registerInitCommand(program2, context4);
|
|
90344
91881
|
registerMessageCommand(program2, context4);
|
|
90345
91882
|
registerMigrateCommand(program2, context4);
|
|
91883
|
+
registerProjectsCommand(program2, context4);
|
|
90346
91884
|
registerRuleCommand(program2, context4);
|
|
90347
91885
|
registerStatusCommand(program2, context4);
|
|
90348
91886
|
registerServeCommand(program2, context4);
|