@gobing-ai/spur 0.3.28 → 0.3.29
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/strict/runtime-boundaries.yaml +1 -0
- package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +57 -0
- package/config/templates/AGENTS.md +3 -0
- package/config/workflows/feature-lifecycle.yaml +9 -7
- package/config/workflows/task-pipeline.yaml +3 -2
- package/config/workflows/wrapup-pipeline.yaml +5 -4
- package/package.json +1 -1
- package/schemas/spur-config.schema.json +1 -1
- package/spur.js +916 -312
package/spur.js
CHANGED
|
@@ -37121,6 +37121,7 @@ var init_finding_codes = __esm(() => {
|
|
|
37121
37121
|
"L3.solution-file-line",
|
|
37122
37122
|
"L3.review-priority-table",
|
|
37123
37123
|
"L3.testing-coverage",
|
|
37124
|
+
"L3.required-section-placeholder",
|
|
37124
37125
|
"L3.plan-format",
|
|
37125
37126
|
"L3.unchecked-checklist",
|
|
37126
37127
|
"L3.ac-checklist-text",
|
|
@@ -37150,8 +37151,9 @@ var init_finding_codes = __esm(() => {
|
|
|
37150
37151
|
"L4.uncovered-feature-scenario",
|
|
37151
37152
|
"L4.verifying-incomplete-tasks",
|
|
37152
37153
|
"L4.dogfood-missing",
|
|
37153
|
-
"L4.
|
|
37154
|
-
"L4.
|
|
37154
|
+
"L4.stale-line-anchor",
|
|
37155
|
+
"L4.malformed-verdict-artifact",
|
|
37156
|
+
"L4.scenario-unverified"
|
|
37155
37157
|
];
|
|
37156
37158
|
FINDING_CODES = {
|
|
37157
37159
|
L1_MARKDOWN_PARSE: "L1.markdown-parse",
|
|
@@ -37165,6 +37167,7 @@ var init_finding_codes = __esm(() => {
|
|
|
37165
37167
|
L3_SOLUTION_FILE_LINE: "L3.solution-file-line",
|
|
37166
37168
|
L3_REVIEW_PRIORITY_TABLE: "L3.review-priority-table",
|
|
37167
37169
|
L3_TESTING_COVERAGE: "L3.testing-coverage",
|
|
37170
|
+
L3_REQUIRED_SECTION_PLACEHOLDER: "L3.required-section-placeholder",
|
|
37168
37171
|
L3_PLAN_FORMAT: "L3.plan-format",
|
|
37169
37172
|
L3_UNCHECKED_CHECKLIST: "L3.unchecked-checklist",
|
|
37170
37173
|
L3_AC_CHECKLIST_TEXT: "L3.ac-checklist-text",
|
|
@@ -37192,10 +37195,11 @@ var init_finding_codes = __esm(() => {
|
|
|
37192
37195
|
L4_ORPHAN_SCENARIOS: "L4.orphan-scenarios",
|
|
37193
37196
|
L4_UNCOVERED_TASK_SCENARIO: "L4.uncovered-task-scenario",
|
|
37194
37197
|
L4_UNCOVERED_FEATURE_SCENARIO: "L4.uncovered-feature-scenario",
|
|
37195
|
-
L4_VERIFYING_INCOMPLETE_TASKS: "L4.verifying-incomplete-tasks",
|
|
37196
37198
|
L4_DOGFOOD_MISSING: "L4.dogfood-missing",
|
|
37197
|
-
|
|
37198
|
-
L4_STALE_LINE_ANCHOR: "L4.stale-line-anchor"
|
|
37199
|
+
L4_VERIFYING_INCOMPLETE_TASKS: "L4.verifying-incomplete-tasks",
|
|
37200
|
+
L4_STALE_LINE_ANCHOR: "L4.stale-line-anchor",
|
|
37201
|
+
L4_MALFORMED_VERDICT_ARTIFACT: "L4.malformed-verdict-artifact",
|
|
37202
|
+
L4_SCENARIO_UNVERIFIED: "L4.scenario-unverified"
|
|
37199
37203
|
};
|
|
37200
37204
|
});
|
|
37201
37205
|
|
|
@@ -37269,7 +37273,7 @@ function buildConfigFromEnv(env = process.env) {
|
|
|
37269
37273
|
}
|
|
37270
37274
|
});
|
|
37271
37275
|
}
|
|
37272
|
-
var SPUR_ENV_VARS, SPUR_LOG_LEVELS, DEFAULT_TASKS_DIR = "docs/tasks", DEFAULT_FEATURES_DIR = "docs/features", DEFAULT_DATABASE_URL = ".spur/spur.db", IN_MEMORY_DATABASE_URL = ":memory:", folderConfigSchema, tasksConfigSchema, featuresConfigSchema, EXECUTOR_CAPABILITY_TIERS, executorCapabilityTierSchema, AgentExecutorConfigSchema, AGENT_ID_REGEX, TeamMemberConfigSchema, TeamConfigSchema, AgentConfigSchema, RulesConfigSchema, WorkflowsConfigSchema, RedactionConfigSchema, spurConfigSchema, configSchema2;
|
|
37276
|
+
var SPUR_ENV_VARS, SPUR_LOG_LEVELS, DEFAULT_TASKS_DIR = "docs/tasks", DEFAULT_FEATURES_DIR = "docs/features", DEFAULT_DATABASE_URL = ".spur/spur.db", IN_MEMORY_DATABASE_URL = ":memory:", folderConfigSchema, tasksConfigSchema, featuresConfigSchema, EXECUTOR_CAPABILITY_TIERS, executorCapabilityTierSchema, AgentExecutorConfigSchema, AGENT_ID_REGEX, TeamMemberConfigSchema, TeamConfigSchema, AgentOutputConfigSchema, AgentConfigSchema, RulesConfigSchema, WorkflowsConfigSchema, RedactionConfigSchema, spurConfigSchema, configSchema2;
|
|
37273
37277
|
var init_src = __esm(() => {
|
|
37274
37278
|
init_zod();
|
|
37275
37279
|
init_finding_codes();
|
|
@@ -37339,11 +37343,16 @@ var init_src = __esm(() => {
|
|
|
37339
37343
|
autostart: exports_external.boolean().optional(),
|
|
37340
37344
|
members: exports_external.array(TeamMemberConfigSchema).min(1)
|
|
37341
37345
|
});
|
|
37346
|
+
AgentOutputConfigSchema = exports_external.object({
|
|
37347
|
+
"max-bytes": exports_external.number().int().positive().optional(),
|
|
37348
|
+
"max-lines": exports_external.number().int().positive().optional()
|
|
37349
|
+
});
|
|
37342
37350
|
AgentConfigSchema = exports_external.object({
|
|
37343
37351
|
default: exports_external.string().optional(),
|
|
37344
37352
|
executors: exports_external.array(AgentExecutorConfigSchema).optional(),
|
|
37345
37353
|
"default-by-phase": exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37346
|
-
team: exports_external.record(exports_external.string(), TeamConfigSchema).optional()
|
|
37354
|
+
team: exports_external.record(exports_external.string(), TeamConfigSchema).optional(),
|
|
37355
|
+
output: AgentOutputConfigSchema.optional()
|
|
37347
37356
|
}).superRefine((value, ctx) => {
|
|
37348
37357
|
const executors = value.executors;
|
|
37349
37358
|
if (executors !== undefined) {
|
|
@@ -37356,6 +37365,13 @@ var init_src = __esm(() => {
|
|
|
37356
37365
|
path: ["executors", index2, "name"]
|
|
37357
37366
|
});
|
|
37358
37367
|
}
|
|
37368
|
+
if (executor.name === "inline" || executor.name === "auto") {
|
|
37369
|
+
ctx.addIssue({
|
|
37370
|
+
code: exports_external.ZodIssueCode.custom,
|
|
37371
|
+
message: `Executor name "${executor.name}" is reserved (the --agent selector value '${executor.name}' has fixed semantics); rename executor at index ${index2}.`,
|
|
37372
|
+
path: ["executors", index2, "name"]
|
|
37373
|
+
});
|
|
37374
|
+
}
|
|
37359
37375
|
seen.add(executor.name);
|
|
37360
37376
|
}
|
|
37361
37377
|
}
|
|
@@ -39182,7 +39198,7 @@ async function loadStructuredSpurConfig(configPath, opts) {
|
|
|
39182
39198
|
function defaultFoldersConfig() {
|
|
39183
39199
|
return {
|
|
39184
39200
|
active_folder: DEFAULT_TASKS_DIR,
|
|
39185
|
-
folders: { [DEFAULT_TASKS_DIR]: {
|
|
39201
|
+
folders: { [DEFAULT_TASKS_DIR]: { baseCounter: 0 } }
|
|
39186
39202
|
};
|
|
39187
39203
|
}
|
|
39188
39204
|
function defaultPlanningFolders() {
|
|
@@ -39219,8 +39235,7 @@ async function resolvePlanningFoldersUncached(fs2) {
|
|
|
39219
39235
|
const tasks = tasksConfigSchema.parse(parsed.tasks);
|
|
39220
39236
|
const folders = {};
|
|
39221
39237
|
for (const [path8, fc] of Object.entries(tasks.folders)) {
|
|
39222
|
-
|
|
39223
|
-
folders[path8] = { base_counter: validated.baseCounter, label: validated.label };
|
|
39238
|
+
folders[path8] = folderConfigSchema.parse(fc);
|
|
39224
39239
|
}
|
|
39225
39240
|
const foldersConfig = {
|
|
39226
39241
|
active_folder: tasks.active,
|
|
@@ -58289,9 +58304,6 @@ function normalizeCapabilityTier(value) {
|
|
|
58289
58304
|
function isTierEligible(candidateTier, minTier) {
|
|
58290
58305
|
return TIER_RANK[candidateTier] >= TIER_RANK[minTier];
|
|
58291
58306
|
}
|
|
58292
|
-
function pickStartingTier(policy) {
|
|
58293
|
-
return policy.min_tier;
|
|
58294
|
-
}
|
|
58295
58307
|
function getNextFallback(policy, signal, currentTier) {
|
|
58296
58308
|
const currentRank = currentTier ? TIER_RANK[currentTier] : 0;
|
|
58297
58309
|
const match = policy.fallback.find((f) => f.trigger === signal && TIER_RANK[f.tier] > currentRank);
|
|
@@ -58357,12 +58369,12 @@ function parseStageRecord(raw, consumerVersion = STAGE_REGISTRY_SCHEMA_VERSION)
|
|
|
58357
58369
|
function getCanonicalStage(idOrAlias) {
|
|
58358
58370
|
return CANONICAL_STAGE_BY_KEY.get(idOrAlias);
|
|
58359
58371
|
}
|
|
58360
|
-
var STAGE_REGISTRY_SCHEMA_VERSION, stageSchemaVersionSchema, AUTHORITY_LANES, MUTATION_CLASSES, EXECUTION_KINDS, inlineExecutionSchema, subprocessExecutionSchema, deterministicExecutionSchema, hitlExecutionSchema, irreversibleExecutionSchema, executionKindSchema, ARTIFACT_DIRECTIONS, stageArtifactSchema, stageGateSchema, stageRetryPolicySchema, CAPABILITY_TIERS, LEGACY_CAPABLE_ALIAS = "capable", capabilityTierSchema, stageModelPolicySchema, TIER_RANK, CONTEXT_LAYER_NAMES, stageContextLayerSchema, stageEventSchema, STAGE_ID_PATTERN, stageRecordSchema, StageRegistryError, REGISTERED_CANONICAL_STAGES, CANONICAL_STAGE_BY_KEY;
|
|
58372
|
+
var STAGE_REGISTRY_SCHEMA_VERSION, stageSchemaVersionSchema, AUTHORITY_LANES, MUTATION_CLASSES, EXECUTION_KINDS, inlineExecutionSchema, subprocessExecutionSchema, deterministicExecutionSchema, hitlExecutionSchema, irreversibleExecutionSchema, executionKindSchema, ARTIFACT_DIRECTIONS, stageArtifactSchema, stageGateSchema, stageRetryPolicySchema, CAPABILITY_TIERS, LEGACY_CAPABLE_ALIAS = "capable", capabilityTierSchema, objectiveEscalationTriggerSchema, stageModelPolicySchema, TIER_RANK, CONTEXT_LAYER_NAMES, stageContextLayerSchema, stageEventSchema, STAGE_ID_PATTERN, stageRecordSchema, StageRegistryError, REGISTERED_CANONICAL_STAGES, CANONICAL_STAGE_BY_KEY;
|
|
58361
58373
|
var init_schema4 = __esm(() => {
|
|
58362
58374
|
init_zod();
|
|
58363
58375
|
STAGE_REGISTRY_SCHEMA_VERSION = {
|
|
58364
58376
|
major: 1,
|
|
58365
|
-
minor:
|
|
58377
|
+
minor: 1
|
|
58366
58378
|
};
|
|
58367
58379
|
stageSchemaVersionSchema = exports_external.object({
|
|
58368
58380
|
major: exports_external.number().int().nonnegative(),
|
|
@@ -58433,11 +58445,18 @@ var init_schema4 = __esm(() => {
|
|
|
58433
58445
|
}).strict();
|
|
58434
58446
|
CAPABILITY_TIERS = ["cheap", "standard", "capable-1", "capable-2", "capable-3"];
|
|
58435
58447
|
capabilityTierSchema = exports_external.preprocess(normalizeCapabilityTier, exports_external.enum(CAPABILITY_TIERS));
|
|
58448
|
+
objectiveEscalationTriggerSchema = exports_external.enum([
|
|
58449
|
+
"gate-fail",
|
|
58450
|
+
"timeout",
|
|
58451
|
+
"insufficient-evidence",
|
|
58452
|
+
"retry-exhausted",
|
|
58453
|
+
"resource-exhaustion"
|
|
58454
|
+
]);
|
|
58436
58455
|
stageModelPolicySchema = exports_external.object({
|
|
58437
58456
|
min_tier: capabilityTierSchema,
|
|
58438
58457
|
fallback: exports_external.array(exports_external.object({
|
|
58439
58458
|
tier: capabilityTierSchema,
|
|
58440
|
-
trigger:
|
|
58459
|
+
trigger: objectiveEscalationTriggerSchema
|
|
58441
58460
|
}).strict()),
|
|
58442
58461
|
override_key: exports_external.string().optional()
|
|
58443
58462
|
}).strict();
|
|
@@ -58508,7 +58527,10 @@ var init_schema4 = __esm(() => {
|
|
|
58508
58527
|
retry: { max_attempts: 3, terminal_stop: "block" },
|
|
58509
58528
|
model_policy: {
|
|
58510
58529
|
min_tier: "standard",
|
|
58511
|
-
fallback: [
|
|
58530
|
+
fallback: [
|
|
58531
|
+
{ tier: "capable-2", trigger: "gate-fail" },
|
|
58532
|
+
{ tier: "capable-2", trigger: "resource-exhaustion" }
|
|
58533
|
+
]
|
|
58512
58534
|
},
|
|
58513
58535
|
context_layers: [],
|
|
58514
58536
|
observability: [],
|
|
@@ -58527,7 +58549,10 @@ var init_schema4 = __esm(() => {
|
|
|
58527
58549
|
retry: { max_attempts: 3, terminal_stop: "block" },
|
|
58528
58550
|
model_policy: {
|
|
58529
58551
|
min_tier: "capable-2",
|
|
58530
|
-
fallback: [
|
|
58552
|
+
fallback: [
|
|
58553
|
+
{ tier: "capable-3", trigger: "gate-fail" },
|
|
58554
|
+
{ tier: "capable-3", trigger: "resource-exhaustion" }
|
|
58555
|
+
]
|
|
58531
58556
|
},
|
|
58532
58557
|
context_layers: [],
|
|
58533
58558
|
observability: [],
|
|
@@ -58548,7 +58573,8 @@ var init_schema4 = __esm(() => {
|
|
|
58548
58573
|
min_tier: "standard",
|
|
58549
58574
|
fallback: [
|
|
58550
58575
|
{ tier: "capable-1", trigger: "gate-fail" },
|
|
58551
|
-
{ tier: "capable-1", trigger: "timeout" }
|
|
58576
|
+
{ tier: "capable-1", trigger: "timeout" },
|
|
58577
|
+
{ tier: "capable-1", trigger: "resource-exhaustion" }
|
|
58552
58578
|
]
|
|
58553
58579
|
},
|
|
58554
58580
|
context_layers: [],
|
|
@@ -58567,8 +58593,11 @@ var init_schema4 = __esm(() => {
|
|
|
58567
58593
|
mutation_class: "tests",
|
|
58568
58594
|
retry: { max_attempts: 3, terminal_stop: "block" },
|
|
58569
58595
|
model_policy: {
|
|
58570
|
-
|
|
58571
|
-
|
|
58596
|
+
fallback: [
|
|
58597
|
+
{ tier: "capable-1", trigger: "gate-fail" },
|
|
58598
|
+
{ tier: "capable-1", trigger: "resource-exhaustion" }
|
|
58599
|
+
],
|
|
58600
|
+
min_tier: "standard"
|
|
58572
58601
|
},
|
|
58573
58602
|
context_layers: [],
|
|
58574
58603
|
observability: [],
|
|
@@ -58606,7 +58635,10 @@ var init_schema4 = __esm(() => {
|
|
|
58606
58635
|
retry: { max_attempts: 2, terminal_stop: "block" },
|
|
58607
58636
|
model_policy: {
|
|
58608
58637
|
min_tier: "standard",
|
|
58609
|
-
fallback: [
|
|
58638
|
+
fallback: [
|
|
58639
|
+
{ tier: "capable-1", trigger: "gate-fail" },
|
|
58640
|
+
{ tier: "capable-1", trigger: "resource-exhaustion" }
|
|
58641
|
+
]
|
|
58610
58642
|
},
|
|
58611
58643
|
context_layers: [],
|
|
58612
58644
|
observability: [],
|
|
@@ -58625,7 +58657,10 @@ var init_schema4 = __esm(() => {
|
|
|
58625
58657
|
retry: { max_attempts: 2, terminal_stop: "block" },
|
|
58626
58658
|
model_policy: {
|
|
58627
58659
|
min_tier: "standard",
|
|
58628
|
-
fallback: [
|
|
58660
|
+
fallback: [
|
|
58661
|
+
{ tier: "capable-1", trigger: "gate-fail" },
|
|
58662
|
+
{ tier: "capable-1", trigger: "resource-exhaustion" }
|
|
58663
|
+
]
|
|
58629
58664
|
},
|
|
58630
58665
|
context_layers: [],
|
|
58631
58666
|
observability: [],
|
|
@@ -60268,13 +60303,13 @@ __export(exports_src, {
|
|
|
60268
60303
|
queryEtlRecords: () => queryEtlRecords,
|
|
60269
60304
|
queryAllEtlRecords: () => queryAllEtlRecords,
|
|
60270
60305
|
projectSnapshotSchema: () => projectSnapshotSchema,
|
|
60271
|
-
pickStartingTier: () => pickStartingTier,
|
|
60272
60306
|
passAllResolver: () => passAllResolver,
|
|
60273
60307
|
parseStageRecord: () => parseStageRecord,
|
|
60274
60308
|
parseHistoryLine: () => parseHistoryLine,
|
|
60275
60309
|
parseFeature: () => parseFeature,
|
|
60276
60310
|
parseExistingAcTags: () => parseExistingAcTags,
|
|
60277
60311
|
parseChecklist: () => parseChecklist,
|
|
60312
|
+
objectiveEscalationTriggerSchema: () => objectiveEscalationTriggerSchema,
|
|
60278
60313
|
normalizeTitle: () => normalizeTitle,
|
|
60279
60314
|
normalizeTaskStatusSafe: () => normalizeTaskStatusSafe,
|
|
60280
60315
|
normalizeTaskStatus: () => normalizeTaskStatus,
|
|
@@ -60588,92 +60623,159 @@ class AgentService {
|
|
|
60588
60623
|
if (!resolved.ok) {
|
|
60589
60624
|
return { ok: false, exitCode: resolved.exitCode, message: resolved.message };
|
|
60590
60625
|
}
|
|
60591
|
-
const
|
|
60592
|
-
|
|
60593
|
-
|
|
60626
|
+
const runFlags = { ...flags };
|
|
60627
|
+
let currentAgent = resolved.agent;
|
|
60628
|
+
let currentModel = resolved.model;
|
|
60629
|
+
let currentSource = resolved.source;
|
|
60630
|
+
let currentStage = resolved.stage;
|
|
60631
|
+
const maxEscalations = currentStage?.policy.fallback.length ?? 0;
|
|
60632
|
+
const attemptedExecutors = new Set(currentStage ? [currentStage.executorName] : []);
|
|
60633
|
+
if (!jsonOutput && TIER2_AGENTS.has(currentAgent)) {
|
|
60634
|
+
this.ctx.output.error(`Warning: ${currentAgent} is a Tier-2 agent (TUI/gateway only)`);
|
|
60594
60635
|
}
|
|
60595
|
-
const
|
|
60596
|
-
const input = translated ?? prompt;
|
|
60636
|
+
const explicitModel = stringFlag(flags, "model", "") || undefined;
|
|
60597
60637
|
const purpose = stringFlag(flags, "purpose", "") || undefined;
|
|
60598
60638
|
const tags = parseTagsFlag(flags);
|
|
60599
60639
|
const systemPrompt = stringFlag(flags, "system-prompt", "") || undefined;
|
|
60600
60640
|
const taskId = stringFlag(flags, "task", "") || undefined;
|
|
60601
|
-
const explicitModel = stringFlag(flags, "model", "") || undefined;
|
|
60602
|
-
const model = explicitModel ?? resolved.model;
|
|
60603
|
-
const promptOptions = {
|
|
60604
|
-
input,
|
|
60605
|
-
continue: continueFlag || undefined,
|
|
60606
|
-
model,
|
|
60607
|
-
mode,
|
|
60608
|
-
...purpose !== undefined ? { purpose } : {},
|
|
60609
|
-
...tags !== undefined ? { tags } : {},
|
|
60610
|
-
...systemPrompt !== undefined ? { systemPrompt } : {},
|
|
60611
|
-
...taskId !== undefined ? { taskId } : {}
|
|
60612
|
-
};
|
|
60613
|
-
let shimCommand;
|
|
60614
|
-
try {
|
|
60615
|
-
shimCommand = typeof runner.buildPromptCommand === "function" ? runner.buildPromptCommand(agent, promptOptions, { cwd: cwd || undefined }) : getAgentShim(agent).getPromptCommand(promptOptions);
|
|
60616
|
-
if (!jsonOutput) {
|
|
60617
|
-
const version3 = (await detector.detectOne(agent)).version;
|
|
60618
|
-
this.ctx.output.error(`\u2699\uFE0F ${agent}${version3 !== null ? ` v${version3}` : ""}
|
|
60619
|
-
${shimCommand.command} ${shimCommand.args.join(" ")}`);
|
|
60620
|
-
}
|
|
60621
|
-
} catch (error51) {
|
|
60622
|
-
return { ok: false, exitCode: 2, message: error51 instanceof Error ? error51.message : String(error51) };
|
|
60623
|
-
}
|
|
60624
|
-
const traceInput = input === undefined ? undefined : traceSafePrompt(input);
|
|
60625
|
-
const invocation = {
|
|
60626
|
-
agent,
|
|
60627
|
-
source: resolved.source,
|
|
60628
|
-
command: shimCommand.command,
|
|
60629
|
-
argv: sanitizeInvocationArgv(shimCommand.args, input, traceInput),
|
|
60630
|
-
...cwd !== "" ? { cwd } : {},
|
|
60631
|
-
mode,
|
|
60632
|
-
outputMode,
|
|
60633
|
-
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
60634
|
-
continue: continueFlag,
|
|
60635
|
-
stdinInteractive: false,
|
|
60636
|
-
...model !== undefined ? { model } : {},
|
|
60637
|
-
...translated !== undefined && prompt !== undefined ? { translatedFrom: traceSafePrompt(prompt) } : {}
|
|
60638
|
-
};
|
|
60639
|
-
let result;
|
|
60640
60641
|
const controller = new AbortController;
|
|
60641
60642
|
const onTerminate = () => controller.abort();
|
|
60642
60643
|
const onExternalAbort = () => controller.abort();
|
|
60643
60644
|
const lifecycle = new AgentExecutionLifecycle(options.execution?.observer, options.execution?.correlation, configuredSecretValues(this.ctx.env), options.execution?.heartbeatMs);
|
|
60644
|
-
|
|
60645
|
-
|
|
60646
|
-
|
|
60647
|
-
|
|
60648
|
-
|
|
60649
|
-
|
|
60650
|
-
|
|
60645
|
+
process.on("SIGTERM", onTerminate);
|
|
60646
|
+
process.on("SIGINT", onTerminate);
|
|
60647
|
+
options.execution?.signal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
60648
|
+
if (options.execution?.signal?.aborted === true)
|
|
60649
|
+
controller.abort();
|
|
60650
|
+
let result;
|
|
60651
|
+
let invocation;
|
|
60652
|
+
let dispatchStartedAt = Date.now();
|
|
60651
60653
|
try {
|
|
60652
|
-
|
|
60653
|
-
|
|
60654
|
-
|
|
60655
|
-
|
|
60656
|
-
|
|
60657
|
-
|
|
60658
|
-
|
|
60659
|
-
|
|
60660
|
-
|
|
60661
|
-
|
|
60662
|
-
|
|
60663
|
-
|
|
60664
|
-
|
|
60665
|
-
|
|
60666
|
-
|
|
60667
|
-
|
|
60668
|
-
|
|
60669
|
-
|
|
60670
|
-
|
|
60671
|
-
|
|
60654
|
+
for (let attempt = 0;; attempt++) {
|
|
60655
|
+
const agent = currentAgent;
|
|
60656
|
+
const model = explicitModel ?? currentModel;
|
|
60657
|
+
const translated = prompt !== undefined && isClaudeStyleSlashCommand(prompt) ? translateSlashCommand(agent, prompt) : undefined;
|
|
60658
|
+
const input = translated ?? prompt;
|
|
60659
|
+
const promptOptions = {
|
|
60660
|
+
input,
|
|
60661
|
+
continue: continueFlag || undefined,
|
|
60662
|
+
model,
|
|
60663
|
+
mode,
|
|
60664
|
+
...purpose !== undefined ? { purpose } : {},
|
|
60665
|
+
...tags !== undefined ? { tags } : {},
|
|
60666
|
+
...systemPrompt !== undefined ? { systemPrompt } : {},
|
|
60667
|
+
...taskId !== undefined ? { taskId } : {}
|
|
60668
|
+
};
|
|
60669
|
+
let shimCommand;
|
|
60670
|
+
try {
|
|
60671
|
+
shimCommand = typeof runner.buildPromptCommand === "function" ? runner.buildPromptCommand(agent, promptOptions, { cwd: cwd || undefined }) : getAgentShim(agent).getPromptCommand(promptOptions);
|
|
60672
|
+
if (!jsonOutput) {
|
|
60673
|
+
const version3 = (await detector.detectOne(agent)).version;
|
|
60674
|
+
this.ctx.output.error(`\u2699\uFE0F ${agent}${version3 !== null ? ` v${version3}` : ""}
|
|
60675
|
+
${shimCommand.command} ${shimCommand.args.join(" ")}`);
|
|
60676
|
+
}
|
|
60677
|
+
} catch (error51) {
|
|
60678
|
+
if (attempt === 0) {
|
|
60679
|
+
return {
|
|
60680
|
+
ok: false,
|
|
60681
|
+
exitCode: 2,
|
|
60682
|
+
message: error51 instanceof Error ? error51.message : String(error51)
|
|
60683
|
+
};
|
|
60684
|
+
}
|
|
60685
|
+
if (!jsonOutput) {
|
|
60686
|
+
this.ctx.output.error(`Escalation aborted: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
60687
|
+
}
|
|
60688
|
+
break;
|
|
60689
|
+
}
|
|
60690
|
+
const traceInput = input === undefined ? undefined : traceSafePrompt(input);
|
|
60691
|
+
const attemptInvocation = {
|
|
60692
|
+
agent,
|
|
60693
|
+
source: currentSource,
|
|
60694
|
+
command: shimCommand.command,
|
|
60695
|
+
argv: sanitizeInvocationArgv(shimCommand.args, input, traceInput),
|
|
60696
|
+
...cwd !== "" ? { cwd } : {},
|
|
60697
|
+
mode,
|
|
60698
|
+
outputMode,
|
|
60699
|
+
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
60700
|
+
continue: continueFlag,
|
|
60701
|
+
stdinInteractive: false,
|
|
60702
|
+
...model !== undefined ? { model } : {},
|
|
60703
|
+
...translated !== undefined && prompt !== undefined ? { translatedFrom: traceSafePrompt(prompt) } : {}
|
|
60704
|
+
};
|
|
60705
|
+
if (attempt === 0) {
|
|
60706
|
+
lifecycle.start({
|
|
60707
|
+
agent,
|
|
60708
|
+
...model !== undefined ? { model } : {},
|
|
60709
|
+
invocation: `${attemptInvocation.command} ${attemptInvocation.argv.join(" ")}`,
|
|
60710
|
+
...timeoutMs !== undefined ? { timeoutMs } : {}
|
|
60711
|
+
});
|
|
60712
|
+
dispatchStartedAt = Date.now();
|
|
60713
|
+
}
|
|
60714
|
+
try {
|
|
60715
|
+
result = await runner.runPromptCommand(agent, promptOptions, {
|
|
60716
|
+
cwd: cwd || undefined,
|
|
60717
|
+
...timeoutMs !== undefined ? { timeout: timeoutMs } : {},
|
|
60718
|
+
signal: controller.signal,
|
|
60719
|
+
correlation: lifecycle.identity,
|
|
60720
|
+
onOutput: (output2) => lifecycle.observe(output2)
|
|
60721
|
+
});
|
|
60722
|
+
} catch (error51) {
|
|
60723
|
+
if (attempt === 0) {
|
|
60724
|
+
lifecycle.finish({
|
|
60725
|
+
exitCode: null,
|
|
60726
|
+
durationMs: Date.now() - dispatchStartedAt,
|
|
60727
|
+
...controller.signal.aborted ? { reason: "cancelled" } : {},
|
|
60728
|
+
...!controller.signal.aborted ? { reason: error51 instanceof Error ? error51.message : String(error51) } : {}
|
|
60729
|
+
});
|
|
60730
|
+
return {
|
|
60731
|
+
ok: false,
|
|
60732
|
+
exitCode: 2,
|
|
60733
|
+
message: error51 instanceof Error ? error51.message : String(error51)
|
|
60734
|
+
};
|
|
60735
|
+
}
|
|
60736
|
+
if (!jsonOutput) {
|
|
60737
|
+
this.ctx.output.error(`Escalation dispatch failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
60738
|
+
}
|
|
60739
|
+
break;
|
|
60740
|
+
}
|
|
60741
|
+
invocation = attemptInvocation;
|
|
60742
|
+
if (result.exitCode === 0)
|
|
60743
|
+
break;
|
|
60744
|
+
const escalationSignal = classifyObjectiveFailure(result);
|
|
60745
|
+
if (escalationSignal === undefined || currentStage === undefined || attempt >= maxEscalations) {
|
|
60746
|
+
break;
|
|
60747
|
+
}
|
|
60748
|
+
runFlags.signal = escalationSignal;
|
|
60749
|
+
runFlags["from-executor"] = currentStage.executorName;
|
|
60750
|
+
const nextResolved = await this.resolveAgent(prompt, runFlags, doctorRunner);
|
|
60751
|
+
if (!nextResolved.ok || nextResolved.stage === undefined || attemptedExecutors.has(nextResolved.stage.executorName)) {
|
|
60752
|
+
if (!jsonOutput) {
|
|
60753
|
+
this.ctx.output.error(`Escalation chain exhausted after ${attempt + 1} attempt(s); executors tried: ${[...attemptedExecutors].join(", ")}`);
|
|
60754
|
+
}
|
|
60755
|
+
break;
|
|
60756
|
+
}
|
|
60757
|
+
if (!jsonOutput) {
|
|
60758
|
+
this.ctx.output.error(`Escalating: ${currentStage.executorName} (tier ${currentStage.executorTier}) failed with ${escalationSignal}; retrying on ${nextResolved.stage.executorName} (tier ${nextResolved.stage.executorTier})`);
|
|
60759
|
+
}
|
|
60760
|
+
currentAgent = nextResolved.agent;
|
|
60761
|
+
currentModel = nextResolved.model;
|
|
60762
|
+
currentSource = nextResolved.source;
|
|
60763
|
+
currentStage = nextResolved.stage;
|
|
60764
|
+
attemptedExecutors.add(nextResolved.stage.executorName);
|
|
60765
|
+
}
|
|
60672
60766
|
} finally {
|
|
60673
60767
|
process.off("SIGTERM", onTerminate);
|
|
60674
60768
|
process.off("SIGINT", onTerminate);
|
|
60675
60769
|
options.execution?.signal?.removeEventListener("abort", onExternalAbort);
|
|
60676
60770
|
}
|
|
60771
|
+
if (result === undefined || invocation === undefined) {
|
|
60772
|
+
lifecycle.finish({
|
|
60773
|
+
exitCode: null,
|
|
60774
|
+
durationMs: Date.now() - dispatchStartedAt,
|
|
60775
|
+
reason: "no dispatch attempted"
|
|
60776
|
+
});
|
|
60777
|
+
return { ok: false, exitCode: 2, message: "No dispatch attempted" };
|
|
60778
|
+
}
|
|
60677
60779
|
lifecycle.finish({
|
|
60678
60780
|
exitCode: result.exitCode,
|
|
60679
60781
|
durationMs: result.durationMs,
|
|
@@ -60703,6 +60805,13 @@ class AgentService {
|
|
|
60703
60805
|
const raw = stringFlag(flags, "agent", "auto");
|
|
60704
60806
|
if (raw === "auto")
|
|
60705
60807
|
return this.resolveAgentAuto(prompt, flags, doctorRunner);
|
|
60808
|
+
if (raw === "inline") {
|
|
60809
|
+
return {
|
|
60810
|
+
ok: false,
|
|
60811
|
+
exitCode: 2,
|
|
60812
|
+
message: "'inline' selects in-session execution and cannot be passed to 'spur agent run', which always starts a subprocess. Run the backing skill directly in the current session, or use '--agent auto' for subprocess dispatch with a tier-resolved executor."
|
|
60813
|
+
};
|
|
60814
|
+
}
|
|
60706
60815
|
return this.resolveExecutorSelector(raw, doctorRunner, "explicit");
|
|
60707
60816
|
}
|
|
60708
60817
|
async resolveAgentAuto(prompt, flags, doctorRunner) {
|
|
@@ -60773,7 +60882,13 @@ class AgentService {
|
|
|
60773
60882
|
ok: true,
|
|
60774
60883
|
agent: canonical,
|
|
60775
60884
|
model: executor.model,
|
|
60776
|
-
source: "stage"
|
|
60885
|
+
source: "stage",
|
|
60886
|
+
stage: {
|
|
60887
|
+
stageId: stageRecord.id,
|
|
60888
|
+
policy,
|
|
60889
|
+
executorName: executor.name,
|
|
60890
|
+
executorTier: getExecutorTier(executor)
|
|
60891
|
+
}
|
|
60777
60892
|
};
|
|
60778
60893
|
}
|
|
60779
60894
|
}
|
|
@@ -60821,8 +60936,15 @@ class AgentService {
|
|
|
60821
60936
|
};
|
|
60822
60937
|
}
|
|
60823
60938
|
const canonical = resolveAgentName(selector);
|
|
60824
|
-
if (canonical === undefined)
|
|
60825
|
-
|
|
60939
|
+
if (canonical === undefined) {
|
|
60940
|
+
const names = this.ctx.agentConfig?.executors?.map((e) => e.name) ?? [];
|
|
60941
|
+
const available = names.length > 0 ? names.join(", ") : "(no executors configured; use a canonical agent name)";
|
|
60942
|
+
return {
|
|
60943
|
+
ok: false,
|
|
60944
|
+
exitCode: 2,
|
|
60945
|
+
message: `Unknown agent: '${selector}'. Available executors: ${available}.`
|
|
60946
|
+
};
|
|
60947
|
+
}
|
|
60826
60948
|
const usable = await this.checkUsable(canonical, doctorRunner);
|
|
60827
60949
|
if (!usable.ok)
|
|
60828
60950
|
return usable.result;
|
|
@@ -61008,6 +61130,17 @@ function getExecutorTier(executor) {
|
|
|
61008
61130
|
return "capable-1";
|
|
61009
61131
|
return "standard";
|
|
61010
61132
|
}
|
|
61133
|
+
function classifyObjectiveFailure(result) {
|
|
61134
|
+
if (result.signal !== undefined)
|
|
61135
|
+
return "timeout";
|
|
61136
|
+
if (result.exitCode === 0)
|
|
61137
|
+
return;
|
|
61138
|
+
const text4 = `${result.stderr} ${result.stdout}`.toLowerCase();
|
|
61139
|
+
if (/\b(rate[\s-]?limit|429|too many requests|quota|token limit|token budget|context length|maximum context|context window)\b/.test(text4)) {
|
|
61140
|
+
return "resource-exhaustion";
|
|
61141
|
+
}
|
|
61142
|
+
return;
|
|
61143
|
+
}
|
|
61011
61144
|
var TRACE_SAFE_SLASH_COMMAND, TRACE_SAFE_SLASH_TOKEN, SENSITIVE_FLAG, SENSITIVE_INLINE_FLAG;
|
|
61012
61145
|
var init_agent_service = __esm(() => {
|
|
61013
61146
|
init_src2();
|
|
@@ -61861,11 +61994,106 @@ var init_planning_check_base = __esm(() => {
|
|
|
61861
61994
|
// ../../packages/app/src/services/feature-check.ts
|
|
61862
61995
|
var exports_feature_check = {};
|
|
61863
61996
|
__export(exports_feature_check, {
|
|
61997
|
+
findOtherP0InStatus: () => findOtherP0InStatus,
|
|
61864
61998
|
defaultVerdictRunDir: () => defaultVerdictRunDir,
|
|
61865
61999
|
FeatureCheckService: () => FeatureCheckService,
|
|
61866
62000
|
DEFAULT_FEATURE_MATRIX: () => DEFAULT_FEATURE_MATRIX
|
|
61867
62001
|
});
|
|
61868
62002
|
import { dirname as dirname8, join as join8 } from "path";
|
|
62003
|
+
function isGroupFeature(fm) {
|
|
62004
|
+
const tags = fm.tags;
|
|
62005
|
+
return Array.isArray(tags) && tags.includes("group") ? "group" : "standard";
|
|
62006
|
+
}
|
|
62007
|
+
async function findOtherP0InStatus(fs3, featuresDir, currentId, statuses) {
|
|
62008
|
+
try {
|
|
62009
|
+
const entries = await fs3.readDir(featuresDir);
|
|
62010
|
+
for (const entry of entries) {
|
|
62011
|
+
if (!entry.endsWith(".md"))
|
|
62012
|
+
continue;
|
|
62013
|
+
const match = /^([A-Z][1-9]*)_/.exec(entry);
|
|
62014
|
+
if (match === null)
|
|
62015
|
+
continue;
|
|
62016
|
+
const otherId = match[1];
|
|
62017
|
+
if (otherId === undefined)
|
|
62018
|
+
continue;
|
|
62019
|
+
if (otherId === currentId)
|
|
62020
|
+
continue;
|
|
62021
|
+
try {
|
|
62022
|
+
const raw = await fs3.readFile(`${featuresDir}/${entry}`);
|
|
62023
|
+
const doc2 = MarkdownDocument.parse(raw, "feature");
|
|
62024
|
+
const otherFm = doc2.frontmatterData ?? {};
|
|
62025
|
+
const otherPriority = otherFm.priority;
|
|
62026
|
+
const otherStatus = otherFm.status ?? "backlog";
|
|
62027
|
+
if (otherPriority === "P0" && statuses.includes(otherStatus)) {
|
|
62028
|
+
return { id: otherId, status: otherStatus };
|
|
62029
|
+
}
|
|
62030
|
+
} catch {}
|
|
62031
|
+
}
|
|
62032
|
+
} catch {}
|
|
62033
|
+
return null;
|
|
62034
|
+
}
|
|
62035
|
+
function decodeVerdictRows(source, sectionName, required2) {
|
|
62036
|
+
if (source === undefined) {
|
|
62037
|
+
return {
|
|
62038
|
+
rows: [],
|
|
62039
|
+
rejected: 0,
|
|
62040
|
+
invalidFields: required2 ? [`${sectionName} (missing array)`] : [],
|
|
62041
|
+
state: "absent"
|
|
62042
|
+
};
|
|
62043
|
+
}
|
|
62044
|
+
if (!Array.isArray(source)) {
|
|
62045
|
+
return {
|
|
62046
|
+
rows: [],
|
|
62047
|
+
rejected: 0,
|
|
62048
|
+
invalidFields: [`${sectionName} (expected array)`],
|
|
62049
|
+
state: "invalid"
|
|
62050
|
+
};
|
|
62051
|
+
}
|
|
62052
|
+
const rows = [];
|
|
62053
|
+
const invalidFields = new Set;
|
|
62054
|
+
let rejected = 0;
|
|
62055
|
+
for (const r of source) {
|
|
62056
|
+
if (typeof r !== "object" || r === null) {
|
|
62057
|
+
rejected++;
|
|
62058
|
+
invalidFields.add(`${sectionName}[non-object]`);
|
|
62059
|
+
continue;
|
|
62060
|
+
}
|
|
62061
|
+
const row = r;
|
|
62062
|
+
const hasId = Object.hasOwn(row, "id");
|
|
62063
|
+
const hasScenario = Object.hasOwn(row, "scenario");
|
|
62064
|
+
const idStr = typeof row.id === "string";
|
|
62065
|
+
const scStr = typeof row.scenario === "string";
|
|
62066
|
+
const statusOk = typeof row.status === "string";
|
|
62067
|
+
if (!statusOk) {
|
|
62068
|
+
rejected++;
|
|
62069
|
+
invalidFields.add(`${sectionName}.status`);
|
|
62070
|
+
continue;
|
|
62071
|
+
}
|
|
62072
|
+
if (hasId && hasScenario) {
|
|
62073
|
+
if (idStr && scStr && row.id === row.scenario) {
|
|
62074
|
+
rows.push({ id: row.id, status: row.status });
|
|
62075
|
+
} else {
|
|
62076
|
+
rejected++;
|
|
62077
|
+
invalidFields.add(`${sectionName}.id/scenario conflict`);
|
|
62078
|
+
}
|
|
62079
|
+
continue;
|
|
62080
|
+
}
|
|
62081
|
+
if (hasId && idStr) {
|
|
62082
|
+
rows.push({ id: row.id, status: row.status });
|
|
62083
|
+
} else if (hasScenario && scStr) {
|
|
62084
|
+
rows.push({ id: row.scenario, status: row.status });
|
|
62085
|
+
} else {
|
|
62086
|
+
rejected++;
|
|
62087
|
+
invalidFields.add(`${sectionName}.id/scenario missing`);
|
|
62088
|
+
}
|
|
62089
|
+
}
|
|
62090
|
+
return {
|
|
62091
|
+
rows,
|
|
62092
|
+
rejected,
|
|
62093
|
+
invalidFields: [...invalidFields],
|
|
62094
|
+
state: source.length === 0 ? "empty" : "populated"
|
|
62095
|
+
};
|
|
62096
|
+
}
|
|
61869
62097
|
function defaultVerdictRunDir(tasksDir) {
|
|
61870
62098
|
const norm = tasksDir.replace(/\\/g, "/");
|
|
61871
62099
|
if (/\/docs\/tasks\d*$/.test(norm) || /\/docs\/tasks$/.test(norm)) {
|
|
@@ -61911,6 +62139,35 @@ var init_feature_check = __esm(() => {
|
|
|
61911
62139
|
required: ["Notes"],
|
|
61912
62140
|
optional: ["Goal", "Scope", "Acceptance Criteria", "Tasks", "History"]
|
|
61913
62141
|
}
|
|
62142
|
+
},
|
|
62143
|
+
group: {
|
|
62144
|
+
backlog: {
|
|
62145
|
+
required: [],
|
|
62146
|
+
optional: ["Goal", "Scope", "Acceptance Criteria", "Tasks", "Notes", "History"]
|
|
62147
|
+
},
|
|
62148
|
+
active: {
|
|
62149
|
+
required: ["Goal", "Scope"],
|
|
62150
|
+
optional: ["Acceptance Criteria", "Tasks", "Notes", "History"],
|
|
62151
|
+
gate: true
|
|
62152
|
+
},
|
|
62153
|
+
verifying: {
|
|
62154
|
+
required: ["Goal", "Scope"],
|
|
62155
|
+
optional: ["Acceptance Criteria", "Tasks", "Notes", "History"],
|
|
62156
|
+
gate: true
|
|
62157
|
+
},
|
|
62158
|
+
blocked: {
|
|
62159
|
+
required: ["Goal", "Notes"],
|
|
62160
|
+
optional: ["Scope", "Acceptance Criteria", "Tasks", "History"]
|
|
62161
|
+
},
|
|
62162
|
+
done: {
|
|
62163
|
+
required: ["Goal", "Scope", "Tasks"],
|
|
62164
|
+
optional: ["Acceptance Criteria", "Notes", "History"],
|
|
62165
|
+
gate: true
|
|
62166
|
+
},
|
|
62167
|
+
cancelled: {
|
|
62168
|
+
required: ["Notes"],
|
|
62169
|
+
optional: ["Goal", "Scope", "Acceptance Criteria", "Tasks", "History"]
|
|
62170
|
+
}
|
|
61914
62171
|
}
|
|
61915
62172
|
}
|
|
61916
62173
|
};
|
|
@@ -61934,11 +62191,11 @@ var init_feature_check = __esm(() => {
|
|
|
61934
62191
|
}
|
|
61935
62192
|
const fm = doc2.frontmatterData ?? {};
|
|
61936
62193
|
const status = fm.status ?? "backlog";
|
|
61937
|
-
const entry = this.resolveMatrixEntry(
|
|
62194
|
+
const entry = this.resolveMatrixEntry(isGroupFeature(fm), status);
|
|
61938
62195
|
this.runL2(doc2, entry, findings);
|
|
61939
62196
|
this.runL3(doc2, findings);
|
|
61940
62197
|
if (options?.featuresDir) {
|
|
61941
|
-
await this.checkOneActiveGoal(fm, featureId2, options.featuresDir, findings);
|
|
62198
|
+
await this.checkOneActiveGoal(fm, featureId2, options.featuresDir, findings, options.asStatus);
|
|
61942
62199
|
await this.checkChildrenLimit(featureId2, options.featuresDir, findings);
|
|
61943
62200
|
}
|
|
61944
62201
|
const dogfoodDir = options?.dogfoodDir ?? (options?.featuresDir ? join8(dirname8(options.featuresDir), "dogfood") : undefined);
|
|
@@ -62011,44 +62268,24 @@ var init_feature_check = __esm(() => {
|
|
|
62011
62268
|
}
|
|
62012
62269
|
}
|
|
62013
62270
|
}
|
|
62014
|
-
async checkOneActiveGoal(fm, currentId, featuresDir, findings) {
|
|
62271
|
+
async checkOneActiveGoal(fm, currentId, featuresDir, findings, asStatus) {
|
|
62015
62272
|
const priority = fm.priority;
|
|
62016
|
-
const status = fm.status;
|
|
62273
|
+
const status = fm.status ?? "backlog";
|
|
62274
|
+
const effectiveStatus = asStatus ?? status;
|
|
62017
62275
|
if (priority !== "P0")
|
|
62018
62276
|
return;
|
|
62019
|
-
if (
|
|
62277
|
+
if (effectiveStatus !== "active")
|
|
62020
62278
|
return;
|
|
62021
|
-
|
|
62022
|
-
|
|
62023
|
-
|
|
62024
|
-
|
|
62025
|
-
|
|
62026
|
-
|
|
62027
|
-
|
|
62028
|
-
|
|
62029
|
-
|
|
62030
|
-
|
|
62031
|
-
if (otherId === currentId)
|
|
62032
|
-
continue;
|
|
62033
|
-
try {
|
|
62034
|
-
const raw = await this.fs.readFile(otherPath);
|
|
62035
|
-
const doc2 = MarkdownDocument.parse(raw, "feature");
|
|
62036
|
-
const otherFm = doc2.frontmatterData ?? {};
|
|
62037
|
-
const otherPriority = otherFm.priority;
|
|
62038
|
-
const otherStatus = otherFm.status;
|
|
62039
|
-
if (otherPriority === "P0" && (otherStatus === "active" || otherStatus === "verifying")) {
|
|
62040
|
-
findings.push({
|
|
62041
|
-
layer: "L3",
|
|
62042
|
-
code: FINDING_CODES.L3_ONE_ACTIVE_GOAL,
|
|
62043
|
-
severity: "error",
|
|
62044
|
-
section: "",
|
|
62045
|
-
message: `One-active-goal violated: P0 feature "${otherId}" is already ${otherStatus}`
|
|
62046
|
-
});
|
|
62047
|
-
return;
|
|
62048
|
-
}
|
|
62049
|
-
} catch {}
|
|
62050
|
-
}
|
|
62051
|
-
} catch {}
|
|
62279
|
+
const conflict = await findOtherP0InStatus(this.fs, featuresDir, currentId, ["active"]);
|
|
62280
|
+
if (conflict !== null) {
|
|
62281
|
+
findings.push({
|
|
62282
|
+
layer: "L3",
|
|
62283
|
+
code: FINDING_CODES.L3_ONE_ACTIVE_GOAL,
|
|
62284
|
+
severity: "error",
|
|
62285
|
+
section: "",
|
|
62286
|
+
message: `One-active-goal violated: P0 feature "${conflict.id}" is already ${conflict.status}`
|
|
62287
|
+
});
|
|
62288
|
+
}
|
|
62052
62289
|
}
|
|
62053
62290
|
async checkChildrenLimit(featureId2, featuresDir, findings) {
|
|
62054
62291
|
let children = 0;
|
|
@@ -62209,11 +62446,42 @@ var init_feature_check = __esm(() => {
|
|
|
62209
62446
|
}
|
|
62210
62447
|
covers[sc.title] = linked;
|
|
62211
62448
|
}
|
|
62449
|
+
const doneWbs = new Set;
|
|
62450
|
+
for (const sc of scenarioAliases) {
|
|
62451
|
+
for (const task of covers[sc.title] ?? []) {
|
|
62452
|
+
if (task.status === "done")
|
|
62453
|
+
doneWbs.add(task.wbs);
|
|
62454
|
+
}
|
|
62455
|
+
}
|
|
62456
|
+
const artifacts2 = new Map;
|
|
62457
|
+
for (const wbs of doneWbs) {
|
|
62458
|
+
const artifact = await this.readVerdictArtifact(runDir, wbs);
|
|
62459
|
+
artifacts2.set(wbs, artifact);
|
|
62460
|
+
const diagnosticParts = [];
|
|
62461
|
+
if (artifact.diagnostics.artifactError !== undefined) {
|
|
62462
|
+
diagnosticParts.push(artifact.diagnostics.artifactError);
|
|
62463
|
+
}
|
|
62464
|
+
if (artifact.diagnostics.rejectedRowCount > 0) {
|
|
62465
|
+
diagnosticParts.push(`${artifact.diagnostics.rejectedRowCount} rejected coverage row(s)`);
|
|
62466
|
+
}
|
|
62467
|
+
if (artifact.diagnostics.invalidFields.length > 0) {
|
|
62468
|
+
diagnosticParts.push(`invalid fields: ${artifact.diagnostics.invalidFields.join(", ")}`);
|
|
62469
|
+
}
|
|
62470
|
+
if (diagnosticParts.length > 0) {
|
|
62471
|
+
findings.push({
|
|
62472
|
+
layer: "L4",
|
|
62473
|
+
code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
|
|
62474
|
+
severity: "warning",
|
|
62475
|
+
section: "Acceptance Criteria",
|
|
62476
|
+
message: `Task ${wbs} verdict artifact (${artifact.path}) is invalid: ${diagnosticParts.join("; ")}. ` + "Rows were not silently dropped \u2014 verify the artifact uses canonical `id` " + "(or `scenario` alias) and `status` fields."
|
|
62477
|
+
});
|
|
62478
|
+
}
|
|
62479
|
+
}
|
|
62212
62480
|
for (const sc of scenarioAliases) {
|
|
62213
62481
|
const linked = covers[sc.title] ?? [];
|
|
62214
62482
|
if (linked.length === 0)
|
|
62215
62483
|
continue;
|
|
62216
|
-
const verified =
|
|
62484
|
+
const verified = this.isScenarioVerified(sc, linked, artifacts2);
|
|
62217
62485
|
if (!verified) {
|
|
62218
62486
|
findings.push({
|
|
62219
62487
|
layer: "L4",
|
|
@@ -62225,12 +62493,12 @@ var init_feature_check = __esm(() => {
|
|
|
62225
62493
|
}
|
|
62226
62494
|
}
|
|
62227
62495
|
}
|
|
62228
|
-
|
|
62496
|
+
isScenarioVerified(sc, linked, artifacts2) {
|
|
62229
62497
|
for (const task of linked) {
|
|
62230
62498
|
if (task.status !== "done")
|
|
62231
62499
|
continue;
|
|
62232
|
-
const artifact =
|
|
62233
|
-
if (artifact ===
|
|
62500
|
+
const artifact = artifacts2.get(task.wbs);
|
|
62501
|
+
if (artifact === undefined)
|
|
62234
62502
|
continue;
|
|
62235
62503
|
if (artifact.verdict !== "PASS")
|
|
62236
62504
|
continue;
|
|
@@ -62242,20 +62510,35 @@ var init_feature_check = __esm(() => {
|
|
|
62242
62510
|
return false;
|
|
62243
62511
|
}
|
|
62244
62512
|
async readVerdictArtifact(runDir, wbs) {
|
|
62245
|
-
|
|
62246
|
-
|
|
62247
|
-
|
|
62248
|
-
|
|
62249
|
-
|
|
62250
|
-
|
|
62251
|
-
|
|
62252
|
-
|
|
62253
|
-
|
|
62254
|
-
|
|
62255
|
-
|
|
62256
|
-
|
|
62257
|
-
|
|
62513
|
+
const loaded = await readVerdictArtifact(this.fs, runDir, wbs);
|
|
62514
|
+
if (loaded.artifact === undefined) {
|
|
62515
|
+
return {
|
|
62516
|
+
path: loaded.path,
|
|
62517
|
+
requirements: [],
|
|
62518
|
+
acceptanceCriteria: [],
|
|
62519
|
+
diagnostics: {
|
|
62520
|
+
artifactError: loaded.readError ?? "artifact is missing",
|
|
62521
|
+
rejectedRowCount: 0,
|
|
62522
|
+
invalidFields: [],
|
|
62523
|
+
arrayStates: { requirements: "unavailable", acceptanceCriteria: "unavailable" }
|
|
62524
|
+
}
|
|
62525
|
+
};
|
|
62258
62526
|
}
|
|
62527
|
+
const parsed = loaded.artifact;
|
|
62528
|
+
const req = decodeVerdictRows(parsed.requirements, "requirements", true);
|
|
62529
|
+
const ac = decodeVerdictRows(parsed.acceptanceCriteria, "acceptanceCriteria", false);
|
|
62530
|
+
return {
|
|
62531
|
+
path: loaded.path,
|
|
62532
|
+
verdict: typeof parsed.verdict === "string" ? parsed.verdict : undefined,
|
|
62533
|
+
requirements: req.rows,
|
|
62534
|
+
acceptanceCriteria: ac.rows,
|
|
62535
|
+
diagnostics: {
|
|
62536
|
+
artifactError: typeof parsed.verdict === "string" ? undefined : "invalid `verdict` field",
|
|
62537
|
+
rejectedRowCount: req.rejected + ac.rejected,
|
|
62538
|
+
invalidFields: [...new Set([...req.invalidFields, ...ac.invalidFields])],
|
|
62539
|
+
arrayStates: { requirements: req.state, acceptanceCriteria: ac.state }
|
|
62540
|
+
}
|
|
62541
|
+
};
|
|
62259
62542
|
}
|
|
62260
62543
|
};
|
|
62261
62544
|
});
|
|
@@ -62323,6 +62606,25 @@ class TaskLocator {
|
|
|
62323
62606
|
}
|
|
62324
62607
|
return null;
|
|
62325
62608
|
}
|
|
62609
|
+
async findDuplicateWbs() {
|
|
62610
|
+
const wbsMap = new Map;
|
|
62611
|
+
for (const dir of this.folderDirs()) {
|
|
62612
|
+
for (const name of await readDirIfPresent(this.fs, dir)) {
|
|
62613
|
+
const captured = TASK_FILENAME_RE.exec(name);
|
|
62614
|
+
if (captured === null)
|
|
62615
|
+
continue;
|
|
62616
|
+
const wbs = captured[1];
|
|
62617
|
+
const hit = { wbs, name, filePath: `${dir}/${name}` };
|
|
62618
|
+
const existing = wbsMap.get(wbs);
|
|
62619
|
+
if (existing) {
|
|
62620
|
+
existing.push(hit);
|
|
62621
|
+
} else {
|
|
62622
|
+
wbsMap.set(wbs, [hit]);
|
|
62623
|
+
}
|
|
62624
|
+
}
|
|
62625
|
+
}
|
|
62626
|
+
return [...wbsMap.values()].filter((hits) => hits.length > 1);
|
|
62627
|
+
}
|
|
62326
62628
|
}
|
|
62327
62629
|
var TASK_FILENAME_RE;
|
|
62328
62630
|
var init_task_locator = __esm(() => {
|
|
@@ -62609,11 +62911,32 @@ class FeatureService {
|
|
|
62609
62911
|
}
|
|
62610
62912
|
const appliedHops = [];
|
|
62611
62913
|
for (const hop of hops) {
|
|
62914
|
+
if (hop === "active") {
|
|
62915
|
+
const conflict = await this.findOneActiveGoalConflict(featureId2);
|
|
62916
|
+
if (conflict !== null) {
|
|
62917
|
+
return {
|
|
62918
|
+
proposal: {
|
|
62919
|
+
...proposal,
|
|
62920
|
+
reason: `Activation blocked by one-active-goal: P0 feature "${conflict.featureId}" is already ${conflict.status}`
|
|
62921
|
+
},
|
|
62922
|
+
applied: false,
|
|
62923
|
+
appliedHops,
|
|
62924
|
+
goalConflict: conflict
|
|
62925
|
+
};
|
|
62926
|
+
}
|
|
62927
|
+
}
|
|
62612
62928
|
await this.transition(featureId2, hop);
|
|
62613
62929
|
appliedHops.push(hop);
|
|
62614
62930
|
}
|
|
62615
62931
|
return { proposal, applied: true, appliedHops };
|
|
62616
62932
|
}
|
|
62933
|
+
async findOneActiveGoalConflict(featureId2) {
|
|
62934
|
+
const feature = await this.show(featureId2);
|
|
62935
|
+
if (feature === null || feature.frontmatter.priority !== "P0")
|
|
62936
|
+
return null;
|
|
62937
|
+
const conflict = await findOtherP0InStatus(this.ctx.fs, this.ctx.featuresDir, featureId2, ["active"]);
|
|
62938
|
+
return conflict === null ? null : { featureId: conflict.id, status: conflict.status };
|
|
62939
|
+
}
|
|
62617
62940
|
async syncAllFeatures(options) {
|
|
62618
62941
|
const allFeatures = await this.list();
|
|
62619
62942
|
const tasksByFeature = await this.collectTasksByFeature();
|
|
@@ -65347,6 +65670,21 @@ var init_task_check = __esm(() => {
|
|
|
65347
65670
|
});
|
|
65348
65671
|
}
|
|
65349
65672
|
}
|
|
65673
|
+
const requiredSections = new Set(entry?.required ?? []);
|
|
65674
|
+
for (const sectionName of ["Testing", "Solution"]) {
|
|
65675
|
+
if (!requiredSections.has(sectionName))
|
|
65676
|
+
continue;
|
|
65677
|
+
const body = doc2.getSection(sectionName);
|
|
65678
|
+
if (body === null || !isPlaceholderBody(body))
|
|
65679
|
+
continue;
|
|
65680
|
+
findings.push({
|
|
65681
|
+
layer: "L3",
|
|
65682
|
+
code: FINDING_CODES.L3_REQUIRED_SECTION_PLACEHOLDER,
|
|
65683
|
+
severity: "error",
|
|
65684
|
+
section: sectionName,
|
|
65685
|
+
message: `${sectionName} is required at status '${status}' but is still placeholder-only \u2014 run \`spur task record <wbs>\` to fill it from the verdict artifact, or author it directly`
|
|
65686
|
+
});
|
|
65687
|
+
}
|
|
65350
65688
|
const testBody = doc2.getSection("Testing");
|
|
65351
65689
|
if (testBody !== null && !isPlaceholderBody(testBody)) {
|
|
65352
65690
|
const hasCoverage = /coverage|\u2265\d+%|\d+\.\d+%|N\/A/i.test(testBody);
|
|
@@ -66219,15 +66557,15 @@ class TaskService {
|
|
|
66219
66557
|
const variant = params.template ?? (params.featureId !== undefined ? "feature-impl" : DEFAULT_TASK_VARIANT);
|
|
66220
66558
|
const status = params.status ?? (params.featureId !== undefined ? "todo" : "backlog");
|
|
66221
66559
|
return this.writeService.createAllocated(folder, async () => {
|
|
66222
|
-
|
|
66223
|
-
|
|
66560
|
+
const dedupeWithinSec = params.dedupeWithinSec === null ? undefined : params.dedupeWithinSec ?? 300;
|
|
66561
|
+
if (dedupeWithinSec !== undefined && params.featureId !== undefined) {
|
|
66562
|
+
const collision = await this.findDuplicateFollowUp(params.featureId, params.title, dedupeWithinSec);
|
|
66224
66563
|
if (collision !== null) {
|
|
66225
66564
|
throw new DuplicateFollowUpError(collision.wbs, collision.name, params.title);
|
|
66226
66565
|
}
|
|
66227
66566
|
}
|
|
66228
|
-
const wbs = await this.allocateWbs();
|
|
66229
66567
|
const slug = this.slugify(params.title);
|
|
66230
|
-
const filePath = this.
|
|
66568
|
+
const { wbs, filePath } = await this.allocateWbsChecked(slug);
|
|
66231
66569
|
const now = new Date().toISOString();
|
|
66232
66570
|
const rawTemplate = this.ctx.resolveTemplate?.(variant);
|
|
66233
66571
|
if (rawTemplate !== undefined) {
|
|
@@ -66630,9 +66968,8 @@ ${issues}`);
|
|
|
66630
66968
|
const status = hasSpec ? "todo" : "backlog";
|
|
66631
66969
|
const variant = item.template ?? (item.feature_id !== undefined && item.feature_id !== null ? "feature-impl" : DEFAULT_TASK_VARIANT);
|
|
66632
66970
|
return this.writeService.createAllocated(folder, async () => {
|
|
66633
|
-
const wbs = await this.allocateWbs();
|
|
66634
66971
|
const slug = this.slugify(item.name);
|
|
66635
|
-
const filePath = this.
|
|
66972
|
+
const { wbs, filePath } = await this.allocateWbsChecked(slug);
|
|
66636
66973
|
const now = new Date().toISOString();
|
|
66637
66974
|
const rawTemplate = this.ctx.resolveTemplate?.(variant);
|
|
66638
66975
|
if (rawTemplate !== undefined) {
|
|
@@ -66785,9 +67122,15 @@ ${block}` : block);
|
|
|
66785
67122
|
}
|
|
66786
67123
|
async allocateWbs() {
|
|
66787
67124
|
let max = 0;
|
|
66788
|
-
const dirs = this.
|
|
67125
|
+
const dirs = this.allFolderDirs();
|
|
67126
|
+
const folderFloors = new Map;
|
|
67127
|
+
if (this.ctx.foldersConfig) {
|
|
67128
|
+
for (const [key, entry] of Object.entries(this.ctx.foldersConfig.folders)) {
|
|
67129
|
+
folderFloors.set(this.ctx.fs.resolve(key), entry.baseCounter);
|
|
67130
|
+
}
|
|
67131
|
+
}
|
|
66789
67132
|
for (const dir of dirs) {
|
|
66790
|
-
const baseCounter =
|
|
67133
|
+
const baseCounter = folderFloors.get(dir) ?? 0;
|
|
66791
67134
|
if (baseCounter > max)
|
|
66792
67135
|
max = baseCounter;
|
|
66793
67136
|
try {
|
|
@@ -66804,6 +67147,14 @@ ${block}` : block);
|
|
|
66804
67147
|
}
|
|
66805
67148
|
return String(max + 1).padStart(4, "0");
|
|
66806
67149
|
}
|
|
67150
|
+
async allocateWbsChecked(slug) {
|
|
67151
|
+
const wbs = await this.allocateWbs();
|
|
67152
|
+
const existing = await this.locator.findByWbs(wbs);
|
|
67153
|
+
if (existing !== null) {
|
|
67154
|
+
throw new WbsCollisionError(wbs, existing.filePath, this.resolveTaskPath(wbs, slug));
|
|
67155
|
+
}
|
|
67156
|
+
return { wbs, filePath: this.resolveTaskPath(wbs, slug) };
|
|
67157
|
+
}
|
|
66807
67158
|
async getFilePath(wbs) {
|
|
66808
67159
|
const result = await this.findTaskFileName(wbs);
|
|
66809
67160
|
if (!result)
|
|
@@ -66865,7 +67216,7 @@ ${block}` : block);
|
|
|
66865
67216
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
66866
67217
|
}
|
|
66867
67218
|
}
|
|
66868
|
-
var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS, DEFAULT_CREATION_SECTIONS;
|
|
67219
|
+
var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, WbsCollisionError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS, DEFAULT_CREATION_SECTIONS;
|
|
66869
67220
|
var init_task_service = __esm(() => {
|
|
66870
67221
|
init_src2();
|
|
66871
67222
|
init_task_locator();
|
|
@@ -66898,6 +67249,18 @@ var init_task_service = __esm(() => {
|
|
|
66898
67249
|
this.attemptedName = attemptedName;
|
|
66899
67250
|
}
|
|
66900
67251
|
};
|
|
67252
|
+
WbsCollisionError = class WbsCollisionError extends Error {
|
|
67253
|
+
wbs;
|
|
67254
|
+
existingPath;
|
|
67255
|
+
attemptedPath;
|
|
67256
|
+
constructor(wbs, existingPath, attemptedPath) {
|
|
67257
|
+
super(`wbs-collision: WBS ${wbs} already exists at "${existingPath}". ` + `Refusing to overwrite with "${attemptedPath}". ` + `Re-run create; if the collision persists, inspect the corpus with: spur task check.`);
|
|
67258
|
+
this.name = "WbsCollisionError";
|
|
67259
|
+
this.wbs = wbs;
|
|
67260
|
+
this.existingPath = existingPath;
|
|
67261
|
+
this.attemptedPath = attemptedPath;
|
|
67262
|
+
}
|
|
67263
|
+
};
|
|
66901
67264
|
ROSTER_REGION_RE = /<!--[ \t]*AUTO-GENERATED[^>]*-->[\s\S]*?<!--[ \t]*END[ \t]*AUTO-GENERATED[^>]*-->/g;
|
|
66902
67265
|
TASK_ACTION_COMMANDS = {
|
|
66903
67266
|
refine: (wbs) => `/sp:dev-refine ${wbs} --auto`,
|
|
@@ -67884,6 +68247,110 @@ var init_token_ledger_watcher = __esm(() => {
|
|
|
67884
68247
|
init_token_ledger_service();
|
|
67885
68248
|
});
|
|
67886
68249
|
|
|
68250
|
+
// ../../packages/app/src/observability/run-output-sink.ts
|
|
68251
|
+
import { closeSync as closeSync4, mkdirSync as mkdirSync6, openSync as openSync4, writeSync as writeSync2 } from "fs";
|
|
68252
|
+
import { join as join14 } from "path";
|
|
68253
|
+
|
|
68254
|
+
class RunOutputSink {
|
|
68255
|
+
filePath;
|
|
68256
|
+
maxBytes;
|
|
68257
|
+
maxLines;
|
|
68258
|
+
fd;
|
|
68259
|
+
bytes = 0;
|
|
68260
|
+
lines = 0;
|
|
68261
|
+
truncated = false;
|
|
68262
|
+
closed = false;
|
|
68263
|
+
constructor(options) {
|
|
68264
|
+
this.filePath = join14(options.dir, `${options.runId}-output.log`);
|
|
68265
|
+
this.maxBytes = options.maxBytes ?? DEFAULT_OUTPUT_MAX_BYTES;
|
|
68266
|
+
this.maxLines = options.maxLines;
|
|
68267
|
+
try {
|
|
68268
|
+
mkdirSync6(options.dir, { recursive: true });
|
|
68269
|
+
this.fd = openSync4(this.filePath, "a");
|
|
68270
|
+
} catch {
|
|
68271
|
+
this.fd = undefined;
|
|
68272
|
+
}
|
|
68273
|
+
}
|
|
68274
|
+
get isTruncated() {
|
|
68275
|
+
return this.truncated;
|
|
68276
|
+
}
|
|
68277
|
+
observe(event2) {
|
|
68278
|
+
if (this.fd === undefined || this.closed)
|
|
68279
|
+
return;
|
|
68280
|
+
switch (event2.kind) {
|
|
68281
|
+
case "output":
|
|
68282
|
+
this.appendChunk(event2.at, event2.stream, event2.chunk);
|
|
68283
|
+
break;
|
|
68284
|
+
case "started":
|
|
68285
|
+
this.append(`# agent output \u2014 run ${event2.runId} \u2014 ${event2.agent} \u2014 ${event2.at}
|
|
68286
|
+
`);
|
|
68287
|
+
this.append(`# invocation: ${event2.invocation}
|
|
68288
|
+
`);
|
|
68289
|
+
break;
|
|
68290
|
+
case "dropped":
|
|
68291
|
+
this.append(`
|
|
68292
|
+
=== [dropped] ${event2.chunks} chunk(s) discarded by the lifecycle relay under backpressure ===
|
|
68293
|
+
`);
|
|
68294
|
+
break;
|
|
68295
|
+
case "finished":
|
|
68296
|
+
this.append(`
|
|
68297
|
+
=== run ${event2.outcome} (exit ${event2.exitCode}) after ${event2.durationMs}ms ===
|
|
68298
|
+
`);
|
|
68299
|
+
break;
|
|
68300
|
+
default:
|
|
68301
|
+
break;
|
|
68302
|
+
}
|
|
68303
|
+
}
|
|
68304
|
+
close() {
|
|
68305
|
+
if (this.closed)
|
|
68306
|
+
return;
|
|
68307
|
+
this.closed = true;
|
|
68308
|
+
if (this.fd !== undefined) {
|
|
68309
|
+
try {
|
|
68310
|
+
closeSync4(this.fd);
|
|
68311
|
+
} catch {}
|
|
68312
|
+
this.fd = undefined;
|
|
68313
|
+
}
|
|
68314
|
+
}
|
|
68315
|
+
appendChunk(at, stream, chunk) {
|
|
68316
|
+
if (this.truncated)
|
|
68317
|
+
return;
|
|
68318
|
+
const text4 = `[${at}] ${stream}: ${chunk}
|
|
68319
|
+
`;
|
|
68320
|
+
const chunkBytes = Buffer.byteLength(text4);
|
|
68321
|
+
const chunkLines = countNewlines(text4);
|
|
68322
|
+
if (this.bytes + chunkBytes > this.maxBytes || this.maxLines !== undefined && this.lines + chunkLines > this.maxLines) {
|
|
68323
|
+
this.truncated = true;
|
|
68324
|
+
this.append(TRUNCATION_MARKER);
|
|
68325
|
+
return;
|
|
68326
|
+
}
|
|
68327
|
+
this.append(text4);
|
|
68328
|
+
}
|
|
68329
|
+
append(text4) {
|
|
68330
|
+
if (this.fd === undefined || this.closed)
|
|
68331
|
+
return;
|
|
68332
|
+
try {
|
|
68333
|
+
writeSync2(this.fd, text4);
|
|
68334
|
+
this.bytes += Buffer.byteLength(text4);
|
|
68335
|
+
this.lines += countNewlines(text4);
|
|
68336
|
+
} catch {}
|
|
68337
|
+
}
|
|
68338
|
+
}
|
|
68339
|
+
function countNewlines(text4) {
|
|
68340
|
+
let count3 = 0;
|
|
68341
|
+
for (let i2 = 0;i2 < text4.length; i2 += 1) {
|
|
68342
|
+
if (text4.charCodeAt(i2) === 10)
|
|
68343
|
+
count3 += 1;
|
|
68344
|
+
}
|
|
68345
|
+
return count3;
|
|
68346
|
+
}
|
|
68347
|
+
var DEFAULT_OUTPUT_MAX_BYTES, TRUNCATION_MARKER = `
|
|
68348
|
+
=== [truncated] agent output capture reached its configured bound; further chunks were not written ===
|
|
68349
|
+
`;
|
|
68350
|
+
var init_run_output_sink = __esm(() => {
|
|
68351
|
+
DEFAULT_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
68352
|
+
});
|
|
68353
|
+
|
|
67887
68354
|
// ../../packages/app/src/workflow/steering.ts
|
|
67888
68355
|
class WorkflowSteeringController {
|
|
67889
68356
|
onAck;
|
|
@@ -68126,16 +68593,18 @@ var init_steering = __esm(() => {
|
|
|
68126
68593
|
});
|
|
68127
68594
|
|
|
68128
68595
|
// ../../packages/app/src/workflow/actions/agent-run.ts
|
|
68129
|
-
import { dirname as dirname12, isAbsolute as isAbsolute2, join as
|
|
68596
|
+
import { dirname as dirname12, isAbsolute as isAbsolute2, join as join15 } from "path";
|
|
68130
68597
|
|
|
68131
68598
|
class AgentRunActionRunner {
|
|
68132
68599
|
observabilityBus;
|
|
68133
68600
|
steeringController;
|
|
68601
|
+
outputLog;
|
|
68134
68602
|
kind = KIND;
|
|
68135
68603
|
agentService;
|
|
68136
|
-
constructor(agentService, observabilityBus, steeringController) {
|
|
68604
|
+
constructor(agentService, observabilityBus, steeringController, outputLog) {
|
|
68137
68605
|
this.observabilityBus = observabilityBus;
|
|
68138
68606
|
this.steeringController = steeringController;
|
|
68607
|
+
this.outputLog = outputLog;
|
|
68139
68608
|
this.agentService = agentService;
|
|
68140
68609
|
}
|
|
68141
68610
|
async execute(options, context4) {
|
|
@@ -68146,7 +68615,8 @@ class AgentRunActionRunner {
|
|
|
68146
68615
|
const cwd = asOptionalString(options.cwd) ?? context4.workdir ?? ".";
|
|
68147
68616
|
let continueFlag = asOptionalBoolean(options.continue);
|
|
68148
68617
|
const latch = context4.vars.__agentSession;
|
|
68149
|
-
|
|
68618
|
+
const latchAutoContinued = continueFlag === undefined && latch === "open";
|
|
68619
|
+
if (latchAutoContinued) {
|
|
68150
68620
|
continueFlag = true;
|
|
68151
68621
|
}
|
|
68152
68622
|
if (input === undefined && !continueFlag) {
|
|
@@ -68178,78 +68648,100 @@ class AgentRunActionRunner {
|
|
|
68178
68648
|
const expectFile = asOptionalString(options.expectFile);
|
|
68179
68649
|
const capture = asOptionalBoolean(options.capture) || answerFile !== undefined;
|
|
68180
68650
|
const agentLabel = agent ?? "<default>";
|
|
68181
|
-
const
|
|
68651
|
+
const outputLog = this.outputLog;
|
|
68652
|
+
const sink = outputLog === undefined ? undefined : new RunOutputSink({
|
|
68653
|
+
dir: join15(cwd, ".spur", "run"),
|
|
68654
|
+
runId: context4.runId,
|
|
68655
|
+
...outputLog.maxBytes !== undefined ? { maxBytes: outputLog.maxBytes } : {},
|
|
68656
|
+
...outputLog.maxLines !== undefined ? { maxLines: outputLog.maxLines } : {}
|
|
68657
|
+
});
|
|
68658
|
+
const observer = this.observabilityBus === undefined && sink === undefined ? undefined : (event2) => {
|
|
68659
|
+
sink?.observe(event2);
|
|
68182
68660
|
this.observabilityBus?.emit("workflow.agent", event2);
|
|
68183
68661
|
};
|
|
68184
68662
|
const actionId = context4.actionId ?? `${context4.runId}:${context4.stateOrNodeId}`;
|
|
68185
68663
|
const steeringPolicy = parseSteeringPolicy(options);
|
|
68186
|
-
let
|
|
68664
|
+
let resumeRetried = false;
|
|
68187
68665
|
let steeringNote;
|
|
68188
68666
|
let traced;
|
|
68189
|
-
|
|
68190
|
-
|
|
68191
|
-
|
|
68192
|
-
|
|
68193
|
-
|
|
68194
|
-
|
|
68195
|
-
|
|
68196
|
-
|
|
68197
|
-
|
|
68198
|
-
|
|
68199
|
-
|
|
68667
|
+
try {
|
|
68668
|
+
for (;; ) {
|
|
68669
|
+
steeringNote = undefined;
|
|
68670
|
+
let steeringSignal = this.steeringController?.begin(context4.runId, actionId, steeringPolicy);
|
|
68671
|
+
while (true) {
|
|
68672
|
+
traced = await this.agentService.runTraced(input, flags, undefined, {
|
|
68673
|
+
correlation: {
|
|
68674
|
+
runId: context4.runId,
|
|
68675
|
+
executionId: crypto.randomUUID(),
|
|
68676
|
+
actionId
|
|
68677
|
+
},
|
|
68678
|
+
...observer !== undefined ? { observer } : {},
|
|
68679
|
+
...steeringSignal !== undefined ? { signal: steeringSignal } : {}
|
|
68680
|
+
});
|
|
68681
|
+
if (this.steeringController === undefined)
|
|
68682
|
+
break;
|
|
68683
|
+
const decision = await this.steeringController.boundary(traced.exitCode === 0);
|
|
68684
|
+
if (decision.operation === "retry") {
|
|
68685
|
+
steeringSignal = this.steeringController.nextAttempt();
|
|
68686
|
+
continue;
|
|
68687
|
+
}
|
|
68688
|
+
if (decision.operation === "note")
|
|
68689
|
+
steeringNote = decision.note;
|
|
68690
|
+
if (decision.operation === "abort" && traced.exitCode === 0) {
|
|
68691
|
+
traced = {
|
|
68692
|
+
...traced,
|
|
68693
|
+
exitCode: 3,
|
|
68694
|
+
signal: "STEERING_ABORT",
|
|
68695
|
+
message: "aborted at steering boundary"
|
|
68696
|
+
};
|
|
68697
|
+
}
|
|
68698
|
+
break;
|
|
68699
|
+
}
|
|
68700
|
+
this.steeringController?.complete();
|
|
68701
|
+
if (!resumeRetried && latchAutoContinued && traced.exitCode === 2) {
|
|
68702
|
+
resumeRetried = true;
|
|
68703
|
+
delete flags.continue;
|
|
68704
|
+
continue;
|
|
68705
|
+
}
|
|
68200
68706
|
break;
|
|
68201
|
-
const decision = await this.steeringController.boundary(traced.exitCode === 0);
|
|
68202
|
-
if (decision.operation === "retry") {
|
|
68203
|
-
steeringSignal = this.steeringController.nextAttempt();
|
|
68204
|
-
continue;
|
|
68205
68707
|
}
|
|
68206
|
-
|
|
68207
|
-
|
|
68208
|
-
|
|
68209
|
-
|
|
68210
|
-
|
|
68211
|
-
|
|
68212
|
-
|
|
68213
|
-
|
|
68214
|
-
|
|
68708
|
+
const { exitCode, stdout: answer } = traced;
|
|
68709
|
+
const ok = exitCode === 0;
|
|
68710
|
+
const invocation = traced.invocation;
|
|
68711
|
+
if (capture && answerFile !== undefined) {
|
|
68712
|
+
const target = isAbsolute2(answerFile) ? answerFile : join15(cwd, answerFile);
|
|
68713
|
+
const fs3 = createNodeFileSystem3(cwd);
|
|
68714
|
+
await fs3.ensureDir(dirname12(target));
|
|
68715
|
+
await fs3.writeFile(target, answer);
|
|
68716
|
+
}
|
|
68717
|
+
if (ok && expectFile !== undefined) {
|
|
68718
|
+
const target = isAbsolute2(expectFile) ? expectFile : join15(cwd, expectFile);
|
|
68719
|
+
const fs3 = createNodeFileSystem3(cwd);
|
|
68720
|
+
if (!await fs3.exists(target)) {
|
|
68721
|
+
return {
|
|
68722
|
+
ok: false,
|
|
68723
|
+
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
68724
|
+
error: `agent.run (${agentLabel}) exited 0 but expected file is absent: ${expectFile}`
|
|
68725
|
+
};
|
|
68726
|
+
}
|
|
68215
68727
|
}
|
|
68216
|
-
|
|
68217
|
-
|
|
68218
|
-
this.steeringController?.complete();
|
|
68219
|
-
const { exitCode, stdout: answer } = traced;
|
|
68220
|
-
const ok = exitCode === 0;
|
|
68221
|
-
const invocation = traced.invocation;
|
|
68222
|
-
if (capture && answerFile !== undefined) {
|
|
68223
|
-
const target = isAbsolute2(answerFile) ? answerFile : join14(cwd, answerFile);
|
|
68224
|
-
const fs3 = createNodeFileSystem3(cwd);
|
|
68225
|
-
await fs3.ensureDir(dirname12(target));
|
|
68226
|
-
await fs3.writeFile(target, answer);
|
|
68227
|
-
}
|
|
68228
|
-
if (ok && expectFile !== undefined) {
|
|
68229
|
-
const target = isAbsolute2(expectFile) ? expectFile : join14(cwd, expectFile);
|
|
68230
|
-
const fs3 = createNodeFileSystem3(cwd);
|
|
68231
|
-
if (!await fs3.exists(target)) {
|
|
68232
|
-
return {
|
|
68233
|
-
ok: false,
|
|
68234
|
-
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
68235
|
-
error: `agent.run (${agentLabel}) exited 0 but expected file is absent: ${expectFile}`
|
|
68236
|
-
};
|
|
68728
|
+
if (!ok) {
|
|
68729
|
+
await writePartialWorkArtifact(context4, agentLabel, model, traced, cwd);
|
|
68237
68730
|
}
|
|
68731
|
+
const stepLabel = context4.stateOrNodeId;
|
|
68732
|
+
const error51 = ok ? undefined : traced.signal !== undefined ? timeoutMs !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) terminated by signal ${traced.signal} (configured timeout: ${timeoutMs}ms; timeout or cancellation); see partial-work artifact` : `agent.run '${stepLabel}' (${agentLabel}) was cancelled by signal ${traced.signal}; see partial-work artifact` : traced.message !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) dispatch failed: ${traced.message}` : `agent.run '${stepLabel}' (${agentLabel}) exited with code ${exitCode}`;
|
|
68733
|
+
return {
|
|
68734
|
+
ok,
|
|
68735
|
+
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
68736
|
+
error: error51,
|
|
68737
|
+
setVars: ok ? {
|
|
68738
|
+
__agentSession: resumeRetried ? "no-resume" : "open",
|
|
68739
|
+
...steeringNote !== undefined ? { __steeringNote: steeringNote } : {}
|
|
68740
|
+
} : undefined
|
|
68741
|
+
};
|
|
68742
|
+
} finally {
|
|
68743
|
+
sink?.close();
|
|
68238
68744
|
}
|
|
68239
|
-
if (!ok) {
|
|
68240
|
-
await writePartialWorkArtifact(context4, agentLabel, model, traced, cwd);
|
|
68241
|
-
}
|
|
68242
|
-
const stepLabel = context4.stateOrNodeId;
|
|
68243
|
-
const error51 = ok ? undefined : traced.signal !== undefined ? timeoutMs !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) terminated by signal ${traced.signal} (configured timeout: ${timeoutMs}ms; timeout or cancellation); see partial-work artifact` : `agent.run '${stepLabel}' (${agentLabel}) was cancelled by signal ${traced.signal}; see partial-work artifact` : traced.message !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) dispatch failed: ${traced.message}` : `agent.run '${stepLabel}' (${agentLabel}) exited with code ${exitCode}`;
|
|
68244
|
-
return {
|
|
68245
|
-
ok,
|
|
68246
|
-
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
68247
|
-
error: error51,
|
|
68248
|
-
setVars: ok ? {
|
|
68249
|
-
__agentSession: "open",
|
|
68250
|
-
...steeringNote !== undefined ? { __steeringNote: steeringNote } : {}
|
|
68251
|
-
} : undefined
|
|
68252
|
-
};
|
|
68253
68745
|
}
|
|
68254
68746
|
}
|
|
68255
68747
|
function buildResultData(exitCode, agentLabel, capture, answer, invocation) {
|
|
@@ -68335,7 +68827,7 @@ async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd
|
|
|
68335
68827
|
""
|
|
68336
68828
|
].join(`
|
|
68337
68829
|
`);
|
|
68338
|
-
const target =
|
|
68830
|
+
const target = join15(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
|
|
68339
68831
|
const fs3 = createNodeFileSystem3(cwd);
|
|
68340
68832
|
await fs3.ensureDir(dirname12(target));
|
|
68341
68833
|
await fs3.writeFile(target, body);
|
|
@@ -68365,6 +68857,7 @@ ${text4.slice(text4.length - maxChars)}`;
|
|
|
68365
68857
|
var PARTIAL_ARTIFACT_TAIL_CHARS = 4000, KIND = "agent.run";
|
|
68366
68858
|
var init_agent_run = __esm(() => {
|
|
68367
68859
|
init_dist5();
|
|
68860
|
+
init_run_output_sink();
|
|
68368
68861
|
init_steering();
|
|
68369
68862
|
});
|
|
68370
68863
|
|
|
@@ -68893,7 +69386,7 @@ var init_rule_check = __esm(() => {
|
|
|
68893
69386
|
// ../../packages/app/src/workflow/builtins.ts
|
|
68894
69387
|
function registerSpurBuiltins(host, options) {
|
|
68895
69388
|
const fileSystem = options.fileSystem ?? createNodeFileSystem3();
|
|
68896
|
-
host.registerAction(new AgentRunActionRunner(options.agentService, options.observabilityBus, options.steeringController), "builtin");
|
|
69389
|
+
host.registerAction(new AgentRunActionRunner(options.agentService, options.observabilityBus, options.steeringController, options.outputLog), "builtin");
|
|
68897
69390
|
host.registerAction(new RuleCheckActionRunner(options.ruleService), "builtin");
|
|
68898
69391
|
host.registerAction(new FileExistsActionRunner(fileSystem), "builtin");
|
|
68899
69392
|
host.registerAction(new FileReadActionRunner(fileSystem), "builtin");
|
|
@@ -69073,7 +69566,7 @@ var init_observability = __esm(() => {
|
|
|
69073
69566
|
|
|
69074
69567
|
// ../../packages/app/src/services/workflow-service.ts
|
|
69075
69568
|
import { homedir as homedir5 } from "os";
|
|
69076
|
-
import { join as
|
|
69569
|
+
import { join as join16, resolve as resolve4 } from "path";
|
|
69077
69570
|
function signalSubprocess(pid) {
|
|
69078
69571
|
if (!Number.isInteger(pid) || pid <= 1)
|
|
69079
69572
|
return false;
|
|
@@ -69296,7 +69789,7 @@ class WorkflowAppService {
|
|
|
69296
69789
|
}
|
|
69297
69790
|
async list(workflowPaths = [".spur/workflows/"]) {
|
|
69298
69791
|
const projectRoot = this.ctx.cwd;
|
|
69299
|
-
const globalRoot =
|
|
69792
|
+
const globalRoot = join16(homedir5(), ".config", "spur");
|
|
69300
69793
|
const layers = [];
|
|
69301
69794
|
const entries = [];
|
|
69302
69795
|
const scannedPaths = new Set;
|
|
@@ -69400,7 +69893,12 @@ class WorkflowAppService {
|
|
|
69400
69893
|
});
|
|
69401
69894
|
}
|
|
69402
69895
|
}
|
|
69403
|
-
|
|
69896
|
+
const outputArtifact = await outputArtifactForRun(this.ctx.cwd, runId);
|
|
69897
|
+
return {
|
|
69898
|
+
run,
|
|
69899
|
+
events: events2,
|
|
69900
|
+
...outputArtifact !== undefined ? { outputArtifact } : {}
|
|
69901
|
+
};
|
|
69404
69902
|
}
|
|
69405
69903
|
async createEngineService(opts = {}) {
|
|
69406
69904
|
const processExec = this.ctx.processExecutor?.();
|
|
@@ -69413,7 +69911,8 @@ class WorkflowAppService {
|
|
|
69413
69911
|
httpRequester: this.ctx.httpRequester?.(),
|
|
69414
69912
|
hostAllowlist: this.ctx.hostAllowlist?.(),
|
|
69415
69913
|
...bus !== undefined ? { observabilityBus: bus } : {},
|
|
69416
|
-
...opts.steeringController !== undefined ? { steeringController: opts.steeringController } : {}
|
|
69914
|
+
...opts.steeringController !== undefined ? { steeringController: opts.steeringController } : {},
|
|
69915
|
+
outputLog: await resolveOutputLogConfig(this.ctx.cwd)
|
|
69417
69916
|
});
|
|
69418
69917
|
const db2 = await this.ctx.getDb();
|
|
69419
69918
|
let persistence2 = new DbWorkflowPersistenceAdapter(db2);
|
|
@@ -69428,6 +69927,23 @@ async function fileExists(path9) {
|
|
|
69428
69927
|
const fs3 = createNodeFileSystem3();
|
|
69429
69928
|
return await fs3.exists(path9);
|
|
69430
69929
|
}
|
|
69930
|
+
async function resolveOutputLogConfig(cwd) {
|
|
69931
|
+
try {
|
|
69932
|
+
const output2 = (await loadSpurConfig(cwd)).agent?.output;
|
|
69933
|
+
if (output2 === undefined)
|
|
69934
|
+
return {};
|
|
69935
|
+
return {
|
|
69936
|
+
...output2["max-bytes"] !== undefined ? { maxBytes: output2["max-bytes"] } : {},
|
|
69937
|
+
...output2["max-lines"] !== undefined ? { maxLines: output2["max-lines"] } : {}
|
|
69938
|
+
};
|
|
69939
|
+
} catch {
|
|
69940
|
+
return {};
|
|
69941
|
+
}
|
|
69942
|
+
}
|
|
69943
|
+
async function outputArtifactForRun(cwd, runId) {
|
|
69944
|
+
const relative4 = join16(".spur", "run", `${runId}-output.log`);
|
|
69945
|
+
return await fileExists(join16(cwd, relative4)) ? relative4 : undefined;
|
|
69946
|
+
}
|
|
69431
69947
|
async function scanWorkflowFiles(rootPath, source) {
|
|
69432
69948
|
const entries = [];
|
|
69433
69949
|
const fs3 = createNodeFileSystem3();
|
|
@@ -69507,6 +70023,7 @@ function rowToTraceEntry(row) {
|
|
|
69507
70023
|
}
|
|
69508
70024
|
var TASK_PIPELINE_WORKFLOW = "task-pipeline", EMBEDDED_SCHEMA_PREFIX = "\x00embedded-spur", SPUR_SCHEMA_MANIFEST = "@gobing-ai/spur/package.json", PIPELINE_LINK_KIND = "pipeline";
|
|
69509
70025
|
var init_workflow_service = __esm(() => {
|
|
70026
|
+
init_loader();
|
|
69510
70027
|
init_src2();
|
|
69511
70028
|
init_dist7();
|
|
69512
70029
|
init_dist5();
|
|
@@ -69740,7 +70257,7 @@ function formatDuration(ms) {
|
|
|
69740
70257
|
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);
|
|
69741
70258
|
|
|
69742
70259
|
// ../../packages/app/src/workflow/trace-writer.ts
|
|
69743
|
-
import { dirname as dirname13, join as
|
|
70260
|
+
import { dirname as dirname13, join as join17 } from "path";
|
|
69744
70261
|
|
|
69745
70262
|
class WorkflowTraceWriter {
|
|
69746
70263
|
path;
|
|
@@ -69748,7 +70265,7 @@ class WorkflowTraceWriter {
|
|
|
69748
70265
|
pending = Promise.resolve();
|
|
69749
70266
|
constructor(cwd, runId) {
|
|
69750
70267
|
const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
69751
|
-
this.path =
|
|
70268
|
+
this.path = join17(cwd, ".spur", "runs", "workflow", `${safeRunId}.jsonl`);
|
|
69752
70269
|
}
|
|
69753
70270
|
attach(bus) {
|
|
69754
70271
|
bus.on("workflow.run.started", (event2) => this.enqueue("workflow.run.started", event2));
|
|
@@ -69840,6 +70357,7 @@ __export(exports_src2, {
|
|
|
69840
70357
|
WorkflowTraceWriter: () => WorkflowTraceWriter,
|
|
69841
70358
|
WorkflowSteeringController: () => WorkflowSteeringController,
|
|
69842
70359
|
WorkflowAppService: () => WorkflowAppService,
|
|
70360
|
+
WbsCollisionError: () => WbsCollisionError,
|
|
69843
70361
|
UnsupportedProcessPlatformError: () => UnsupportedProcessPlatformError,
|
|
69844
70362
|
TokenLedgerWatcher: () => TokenLedgerWatcher,
|
|
69845
70363
|
TokenLedgerService: () => TokenLedgerService,
|
|
@@ -77571,7 +78089,7 @@ init_loader();
|
|
|
77571
78089
|
init_src2();
|
|
77572
78090
|
import { existsSync as existsSync9 } from "fs";
|
|
77573
78091
|
import { homedir as homedir6 } from "os";
|
|
77574
|
-
import { join as
|
|
78092
|
+
import { join as join18, resolve as resolve5 } from "path";
|
|
77575
78093
|
|
|
77576
78094
|
// src/workflow/resolve-spur-bin.ts
|
|
77577
78095
|
import { basename as basename3 } from "path";
|
|
@@ -77589,22 +78107,22 @@ function resolveSpurBin() {
|
|
|
77589
78107
|
}
|
|
77590
78108
|
|
|
77591
78109
|
// src/workflow/make-lifecycle-adapter.ts
|
|
77592
|
-
var GLOBAL_CONFIG_DIR =
|
|
78110
|
+
var GLOBAL_CONFIG_DIR = join18(".config", "spur");
|
|
77593
78111
|
function globalConfigRoot(context4) {
|
|
77594
78112
|
const override = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
77595
|
-
return override !== undefined && override.length > 0 ? resolve5(context4.cwd, override) :
|
|
78113
|
+
return override !== undefined && override.length > 0 ? resolve5(context4.cwd, override) : join18(homedir6(), GLOBAL_CONFIG_DIR);
|
|
77596
78114
|
}
|
|
77597
78115
|
function resolveWorkflowPath(context4, profile) {
|
|
77598
78116
|
const bundledRoot = bundledConfigRoot();
|
|
77599
78117
|
if (bundledRoot !== null) {
|
|
77600
|
-
const bundledPath =
|
|
78118
|
+
const bundledPath = join18(bundledRoot, "workflows", `${profile.workflowName}.yaml`);
|
|
77601
78119
|
if (existsSync9(bundledPath))
|
|
77602
78120
|
return bundledPath;
|
|
77603
78121
|
}
|
|
77604
|
-
const projectPath =
|
|
78122
|
+
const projectPath = join18(context4.cwd, ".spur", "workflows", `${profile.workflowName}.yaml`);
|
|
77605
78123
|
if (existsSync9(projectPath))
|
|
77606
78124
|
return projectPath;
|
|
77607
|
-
const globalPath =
|
|
78125
|
+
const globalPath = join18(globalConfigRoot(context4), "workflows", `${profile.workflowName}.yaml`);
|
|
77608
78126
|
if (existsSync9(globalPath))
|
|
77609
78127
|
return globalPath;
|
|
77610
78128
|
return null;
|
|
@@ -77745,9 +78263,9 @@ function registerFeatureCommand(program2, context4) {
|
|
|
77745
78263
|
let next = forwardPath[current];
|
|
77746
78264
|
while (next !== undefined) {
|
|
77747
78265
|
if (current === "active") {
|
|
77748
|
-
await assertFeatureCheckPass(context4, id, options.folder, false);
|
|
78266
|
+
await assertFeatureCheckPass(context4, id, options.folder, false, "verifying");
|
|
77749
78267
|
} else if (current === "verifying") {
|
|
77750
|
-
await assertFeatureCheckPass(context4, id, options.folder, true);
|
|
78268
|
+
await assertFeatureCheckPass(context4, id, options.folder, true, "done");
|
|
77751
78269
|
}
|
|
77752
78270
|
const result = await svc.transition(id, next);
|
|
77753
78271
|
history.push({ from: result.fromStatus ?? current, to: result.toStatus ?? next });
|
|
@@ -77837,7 +78355,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
77837
78355
|
context4.setExitCode(1);
|
|
77838
78356
|
}
|
|
77839
78357
|
});
|
|
77840
|
-
feature.command("check").summary("Validate feature file(s) through the four-layer check (design \xA73).").argument("[id]", "Feature ID (validates all features in the folder when omitted)").option("--strict", "Elevate warnings to failures").option("--folder <path>", "Custom features folder").option("--json", "Output machine-readable JSON").action(async (id, options) => {
|
|
78358
|
+
feature.command("check").summary("Validate feature file(s) through the four-layer check (design \xA73).").argument("[id]", "Feature ID (validates all features in the folder when omitted)").option("--strict", "Elevate warnings to failures").option("--as <status>", "Evaluate the one-active-goal rule as if the feature were in <status> (0418: lifecycle FSM guards pass the transition target)").option("--folder <path>", "Custom features folder").option("--json", "Output machine-readable JSON").action(async (id, options) => {
|
|
77841
78359
|
const resolved = await resolvePlanningFolders(context4.fs);
|
|
77842
78360
|
const featuresDir = options.folder ?? context4.fs.resolve(resolved.featuresDir);
|
|
77843
78361
|
const tasksDir = context4.fs.resolve(resolved.tasksDir);
|
|
@@ -77860,7 +78378,8 @@ function registerFeatureCommand(program2, context4) {
|
|
|
77860
78378
|
featuresDir,
|
|
77861
78379
|
tasksDir,
|
|
77862
78380
|
runDir: context4.fs.resolve(".spur/run"),
|
|
77863
|
-
severityOverrides: resolved.severityOverrides
|
|
78381
|
+
severityOverrides: resolved.severityOverrides,
|
|
78382
|
+
asStatus: options.as
|
|
77864
78383
|
});
|
|
77865
78384
|
results.push(result);
|
|
77866
78385
|
if (!json3) {
|
|
@@ -77900,7 +78419,7 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
77900
78419
|
} else {
|
|
77901
78420
|
context4.output.write(`Evaluated ${result.evaluated}/${result.totalFeatures} features; updated ${result.updatedCount} feature(s).`);
|
|
77902
78421
|
for (const res of result.results) {
|
|
77903
|
-
const tag = res.applied ? "UPDATED" : res.proposal.from === res.proposal.to ? "NOOP" : "SKIPPED";
|
|
78422
|
+
const tag = res.applied ? "UPDATED" : res.goalConflict !== undefined ? "GOAL-CONFLICT" : res.proposal.from === res.proposal.to ? "NOOP" : "SKIPPED";
|
|
77904
78423
|
context4.output.write(` [${tag}] ${res.proposal.featureId}: ${res.proposal.from} -> ${res.proposal.to} (${res.proposal.reason})`);
|
|
77905
78424
|
}
|
|
77906
78425
|
}
|
|
@@ -77912,7 +78431,7 @@ ${result.id} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
77912
78431
|
if (options.json) {
|
|
77913
78432
|
context4.output.write(toJson2(result));
|
|
77914
78433
|
} else {
|
|
77915
|
-
const tag = result.applied ? "UPDATED" : result.proposal.from === result.proposal.to ? "NOOP" : "SKIPPED";
|
|
78434
|
+
const tag = result.applied ? "UPDATED" : result.goalConflict !== undefined ? "GOAL-CONFLICT" : result.proposal.from === result.proposal.to ? "NOOP" : "SKIPPED";
|
|
77916
78435
|
context4.output.write(`Feature ${id}: [${tag}] ${result.proposal.from} -> ${result.proposal.to} (${result.proposal.reason})`);
|
|
77917
78436
|
}
|
|
77918
78437
|
}
|
|
@@ -77940,7 +78459,7 @@ async function makeService(context4, folderOverride) {
|
|
|
77940
78459
|
foldersConfig: resolved.foldersConfig
|
|
77941
78460
|
});
|
|
77942
78461
|
}
|
|
77943
|
-
async function assertFeatureCheckPass(context4, id, folderOverride, strict) {
|
|
78462
|
+
async function assertFeatureCheckPass(context4, id, folderOverride, strict, asStatus) {
|
|
77944
78463
|
const resolved = await resolvePlanningFolders(context4.fs);
|
|
77945
78464
|
const featuresDir = folderOverride ?? context4.fs.resolve(resolved.featuresDir);
|
|
77946
78465
|
const tasksDir = context4.fs.resolve(resolved.tasksDir);
|
|
@@ -77952,7 +78471,8 @@ async function assertFeatureCheckPass(context4, id, folderOverride, strict) {
|
|
|
77952
78471
|
const result = await new FeatureCheckService(context4.fs).check(`${featuresDir}/${fileName}`, id, {
|
|
77953
78472
|
strict,
|
|
77954
78473
|
featuresDir,
|
|
77955
|
-
tasksDir
|
|
78474
|
+
tasksDir,
|
|
78475
|
+
asStatus
|
|
77956
78476
|
});
|
|
77957
78477
|
if (!result.pass) {
|
|
77958
78478
|
const details = result.findings.map((f) => `${f.layer} ${f.section}: ${f.message}`).join("; ");
|
|
@@ -78006,13 +78526,13 @@ init_loader();
|
|
|
78006
78526
|
init_src2();
|
|
78007
78527
|
init_dist10();
|
|
78008
78528
|
import { homedir as homedir7 } from "os";
|
|
78009
|
-
import { join as
|
|
78529
|
+
import { join as join19, resolve as resolve6 } from "path";
|
|
78010
78530
|
|
|
78011
78531
|
// src/config.ts
|
|
78012
78532
|
var CLI_CONFIG = {
|
|
78013
78533
|
binaryName: "spur",
|
|
78014
78534
|
binaryLabel: "spur",
|
|
78015
|
-
binaryVersion: "0.3.
|
|
78535
|
+
binaryVersion: "0.3.29",
|
|
78016
78536
|
configDir: ".spur",
|
|
78017
78537
|
configFile: ".spur/config.yaml",
|
|
78018
78538
|
databaseFile: ".spur/spur.db"
|
|
@@ -78063,8 +78583,8 @@ var SCAFFOLD_MANIFEST = [
|
|
|
78063
78583
|
];
|
|
78064
78584
|
|
|
78065
78585
|
// src/commands/init.ts
|
|
78066
|
-
var GLOBAL_CONFIG_DIR2 =
|
|
78067
|
-
var GLOBAL_RULES_DIR2 =
|
|
78586
|
+
var GLOBAL_CONFIG_DIR2 = join19(".config", "spur");
|
|
78587
|
+
var GLOBAL_RULES_DIR2 = join19(GLOBAL_CONFIG_DIR2, "rules");
|
|
78068
78588
|
var GLOBAL_CONFIG_EXAMPLE = "config.example.yaml";
|
|
78069
78589
|
var GLOBAL_CONFIG_FILE2 = "config.yaml";
|
|
78070
78590
|
var INDEXED_CONTEXT_MARKER = "## Indexed context";
|
|
@@ -78095,7 +78615,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
|
|
|
78095
78615
|
`;
|
|
78096
78616
|
function globalRulesRoot(context4) {
|
|
78097
78617
|
const override = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
78098
|
-
return override !== undefined && override.length > 0 ? resolve6(context4.cwd, override) :
|
|
78618
|
+
return override !== undefined && override.length > 0 ? resolve6(context4.cwd, override) : join19(homedir7(), GLOBAL_RULES_DIR2);
|
|
78099
78619
|
}
|
|
78100
78620
|
async function writeIfNew(context4, path9, content, force, result) {
|
|
78101
78621
|
if (!force && await context4.fs.exists(path9)) {
|
|
@@ -78112,11 +78632,11 @@ async function seedGlobalRules(context4) {
|
|
|
78112
78632
|
const target = globalRulesRoot(context4);
|
|
78113
78633
|
let written = 0;
|
|
78114
78634
|
for (const relPath of await listBundledRuleFiles()) {
|
|
78115
|
-
const destination =
|
|
78635
|
+
const destination = join19(target, relPath);
|
|
78116
78636
|
if (await context4.fs.exists(destination))
|
|
78117
78637
|
continue;
|
|
78118
|
-
await context4.fs.ensureDir(
|
|
78119
|
-
await context4.fs.writeFile(destination, await context4.fs.readFile(
|
|
78638
|
+
await context4.fs.ensureDir(join19(target, ...relPath.split("/").slice(0, -1)));
|
|
78639
|
+
await context4.fs.writeFile(destination, await context4.fs.readFile(join19(source, relPath)));
|
|
78120
78640
|
written += 1;
|
|
78121
78641
|
}
|
|
78122
78642
|
return written;
|
|
@@ -78126,20 +78646,20 @@ async function seedGlobalConfig(context4) {
|
|
|
78126
78646
|
if (source === null)
|
|
78127
78647
|
return 0;
|
|
78128
78648
|
const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
|
|
78129
|
-
const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve6(context4.cwd, globalOverride) :
|
|
78649
|
+
const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve6(context4.cwd, globalOverride) : join19(homedir7(), GLOBAL_CONFIG_DIR2);
|
|
78130
78650
|
let written = 0;
|
|
78131
78651
|
for (const relPath of listBundledConfigFiles()) {
|
|
78132
78652
|
if (relPath === GLOBAL_CONFIG_EXAMPLE)
|
|
78133
78653
|
continue;
|
|
78134
|
-
const destination =
|
|
78654
|
+
const destination = join19(target, relPath);
|
|
78135
78655
|
if (await context4.fs.exists(destination))
|
|
78136
78656
|
continue;
|
|
78137
|
-
await context4.fs.ensureDir(
|
|
78138
|
-
await context4.fs.writeFile(destination, await context4.fs.readFile(
|
|
78657
|
+
await context4.fs.ensureDir(join19(target, ...relPath.split("/").slice(0, -1)));
|
|
78658
|
+
await context4.fs.writeFile(destination, await context4.fs.readFile(join19(source, relPath)));
|
|
78139
78659
|
written += 1;
|
|
78140
78660
|
}
|
|
78141
|
-
const examplePath =
|
|
78142
|
-
const globalConfigPath =
|
|
78661
|
+
const examplePath = join19(source, GLOBAL_CONFIG_EXAMPLE);
|
|
78662
|
+
const globalConfigPath = join19(target, GLOBAL_CONFIG_FILE2);
|
|
78143
78663
|
if (await context4.fs.exists(examplePath) && !await context4.fs.exists(globalConfigPath)) {
|
|
78144
78664
|
await context4.fs.ensureDir(target);
|
|
78145
78665
|
await context4.fs.writeFile(globalConfigPath, await context4.fs.readFile(examplePath));
|
|
@@ -78153,7 +78673,7 @@ function registerInitCommand(program2, context4) {
|
|
|
78153
78673
|
const force = options.force === true;
|
|
78154
78674
|
const minimal = options.minimal === true;
|
|
78155
78675
|
const projectName = options.name ?? "default";
|
|
78156
|
-
const configPath =
|
|
78676
|
+
const configPath = join19(context4.cwd, CLI_CONFIG.configFile);
|
|
78157
78677
|
if (!force && await context4.fs.exists(configPath)) {
|
|
78158
78678
|
const message = `Already initialized: ${CLI_CONFIG.configFile}. Use --force to overwrite.`;
|
|
78159
78679
|
context4.output.write(json3 ? toJson2({ ok: false, reason: "already-initialized", config: CLI_CONFIG.configFile }) : message);
|
|
@@ -78188,13 +78708,13 @@ function registerInitCommand(program2, context4) {
|
|
|
78188
78708
|
].join(`
|
|
78189
78709
|
`)}
|
|
78190
78710
|
`;
|
|
78191
|
-
await context4.fs.ensureDir(
|
|
78711
|
+
await context4.fs.ensureDir(join19(context4.cwd, CLI_CONFIG.configDir));
|
|
78192
78712
|
await context4.fs.writeFile(configPath, configYaml);
|
|
78193
78713
|
result.created.push(configPath);
|
|
78194
|
-
const agentsDir =
|
|
78714
|
+
const agentsDir = join19(context4.cwd, CLI_CONFIG.configDir, "agents");
|
|
78195
78715
|
await context4.fs.ensureDir(agentsDir);
|
|
78196
|
-
await writeIfNew(context4,
|
|
78197
|
-
const gitignorePath =
|
|
78716
|
+
await writeIfNew(context4, join19(agentsDir, ".gitkeep"), "", force, result);
|
|
78717
|
+
const gitignorePath = join19(context4.cwd, ".gitignore");
|
|
78198
78718
|
const contextEntry = ".spur/context/";
|
|
78199
78719
|
if (await context4.fs.exists(gitignorePath)) {
|
|
78200
78720
|
const existing = await context4.fs.readFile(gitignorePath);
|
|
@@ -78216,20 +78736,20 @@ ${contextEntry}
|
|
|
78216
78736
|
for (const relPath of listBundledProjectSeedFiles()) {
|
|
78217
78737
|
if (relPath.startsWith("templates/docs/"))
|
|
78218
78738
|
continue;
|
|
78219
|
-
const sourcePath =
|
|
78739
|
+
const sourcePath = join19(configRoot, relPath);
|
|
78220
78740
|
if (!await context4.fs.exists(sourcePath))
|
|
78221
78741
|
continue;
|
|
78222
|
-
const targetPath =
|
|
78223
|
-
await context4.fs.ensureDir(
|
|
78742
|
+
const targetPath = join19(context4.cwd, CLI_CONFIG.configDir, relPath);
|
|
78743
|
+
await context4.fs.ensureDir(join19(targetPath, ".."));
|
|
78224
78744
|
await writeIfNew(context4, targetPath, await context4.fs.readFile(sourcePath), force, result);
|
|
78225
78745
|
}
|
|
78226
78746
|
for (const entry of SCAFFOLD_MANIFEST) {
|
|
78227
|
-
const sourcePath =
|
|
78747
|
+
const sourcePath = join19(configRoot, entry.source);
|
|
78228
78748
|
if (!await context4.fs.exists(sourcePath))
|
|
78229
78749
|
continue;
|
|
78230
|
-
const baseDir = entry.root === true ? context4.cwd :
|
|
78231
|
-
const targetPath =
|
|
78232
|
-
await context4.fs.ensureDir(
|
|
78750
|
+
const baseDir = entry.root === true ? context4.cwd : join19(context4.cwd, CLI_CONFIG.configDir);
|
|
78751
|
+
const targetPath = join19(baseDir, entry.target);
|
|
78752
|
+
await context4.fs.ensureDir(join19(targetPath, ".."));
|
|
78233
78753
|
const entryForce = entry.preserve === true ? false : force;
|
|
78234
78754
|
let body = await context4.fs.readFile(sourcePath);
|
|
78235
78755
|
if (entry.target === "AGENTS.md") {
|
|
@@ -78242,7 +78762,7 @@ ${contextEntry}
|
|
|
78242
78762
|
}
|
|
78243
78763
|
}
|
|
78244
78764
|
}
|
|
78245
|
-
const agentsMdPath =
|
|
78765
|
+
const agentsMdPath = join19(context4.cwd, "AGENTS.md");
|
|
78246
78766
|
if (await context4.fs.exists(agentsMdPath)) {
|
|
78247
78767
|
const existing = await context4.fs.readFile(agentsMdPath);
|
|
78248
78768
|
if (!existing.includes(INDEXED_CONTEXT_MARKER)) {
|
|
@@ -78420,10 +78940,10 @@ function parseInterval2(raw) {
|
|
|
78420
78940
|
|
|
78421
78941
|
// src/commands/migrate.ts
|
|
78422
78942
|
init_src2();
|
|
78423
|
-
import { join as
|
|
78943
|
+
import { join as join20 } from "path";
|
|
78424
78944
|
function registerMigrateCommand(program2, context4) {
|
|
78425
78945
|
program2.command("migrate").summary("apply CLI-owned schema migrations").option("--json", "Output machine-readable JSON").action(async (options) => {
|
|
78426
|
-
const migrations = await loadSqlMigrations(
|
|
78946
|
+
const migrations = await loadSqlMigrations(join20(context4.cwd, "drizzle")).catch(() => {
|
|
78427
78947
|
return;
|
|
78428
78948
|
});
|
|
78429
78949
|
const applied = await applyCliMigrations(await context4.getDb(), migrations);
|
|
@@ -78839,7 +79359,7 @@ function formatTraceDetail(detail) {
|
|
|
78839
79359
|
|
|
78840
79360
|
// src/commands/serve.ts
|
|
78841
79361
|
init_src();
|
|
78842
|
-
import { join as
|
|
79362
|
+
import { join as join24 } from "path";
|
|
78843
79363
|
|
|
78844
79364
|
// ../server/src/index.ts
|
|
78845
79365
|
init_src();
|
|
@@ -78849,11 +79369,11 @@ init_src3();
|
|
|
78849
79369
|
init_src();
|
|
78850
79370
|
init_loader();
|
|
78851
79371
|
init_src2();
|
|
78852
|
-
import { basename as basename6, dirname as dirname15, isAbsolute as isAbsolute3, join as
|
|
79372
|
+
import { basename as basename6, dirname as dirname15, isAbsolute as isAbsolute3, join as join23 } from "path";
|
|
78853
79373
|
init_dist5();
|
|
78854
79374
|
|
|
78855
79375
|
// ../server/src/bootstrap.ts
|
|
78856
|
-
import { join as
|
|
79376
|
+
import { join as join21 } from "path";
|
|
78857
79377
|
|
|
78858
79378
|
// ../../node_modules/.bun/radash@12.1.1/node_modules/radash/dist/esm/async.mjs
|
|
78859
79379
|
var guard = (func, shouldGuard) => {
|
|
@@ -84088,6 +84608,9 @@ var contextInjector = (appRt) => async function contextInjectorMiddleware(c3, ne
|
|
|
84088
84608
|
await next();
|
|
84089
84609
|
};
|
|
84090
84610
|
|
|
84611
|
+
// ../server/src/middleware/error-handler.ts
|
|
84612
|
+
init_src3();
|
|
84613
|
+
|
|
84091
84614
|
// ../server/src/errors.ts
|
|
84092
84615
|
init_errors5();
|
|
84093
84616
|
|
|
@@ -84150,6 +84673,14 @@ function resolveError(err, requestId, isProd) {
|
|
|
84150
84673
|
details: { requestId }
|
|
84151
84674
|
};
|
|
84152
84675
|
}
|
|
84676
|
+
if (err instanceof WbsCollisionError) {
|
|
84677
|
+
return {
|
|
84678
|
+
status: 409,
|
|
84679
|
+
apiCode: "WBS_COLLISION",
|
|
84680
|
+
message: isProd ? "WBS collision - task already exists" : message,
|
|
84681
|
+
details: { requestId, wbs: err.wbs, existingPath: err.existingPath, attemptedPath: err.attemptedPath }
|
|
84682
|
+
};
|
|
84683
|
+
}
|
|
84153
84684
|
if (err instanceof LockTimeoutError) {
|
|
84154
84685
|
return {
|
|
84155
84686
|
status: 503,
|
|
@@ -87737,7 +88268,7 @@ function createApp(appRt, opts) {
|
|
|
87737
88268
|
app.use("*", async (c3, next) => {
|
|
87738
88269
|
const pathname = c3.req.path === "/" ? "/index.html" : c3.req.path;
|
|
87739
88270
|
try {
|
|
87740
|
-
const file2 = Bun.file(
|
|
88271
|
+
const file2 = Bun.file(join21(webDistPath, pathname));
|
|
87741
88272
|
if (await file2.exists()) {
|
|
87742
88273
|
const headers = new Headers({ "content-type": file2.type });
|
|
87743
88274
|
return new Response(file2.stream(), { headers });
|
|
@@ -87750,7 +88281,7 @@ function createApp(appRt, opts) {
|
|
|
87750
88281
|
return c3.json({ error: "Not Found" }, 404);
|
|
87751
88282
|
}
|
|
87752
88283
|
try {
|
|
87753
|
-
const indexFile = Bun.file(
|
|
88284
|
+
const indexFile = Bun.file(join21(webDistPath, "index.html"));
|
|
87754
88285
|
if (await indexFile.exists()) {
|
|
87755
88286
|
return new Response(indexFile.stream(), {
|
|
87756
88287
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -87771,7 +88302,7 @@ init_src3();
|
|
|
87771
88302
|
init_src();
|
|
87772
88303
|
init_src2();
|
|
87773
88304
|
init_dist5();
|
|
87774
|
-
import { dirname as dirname14, join as
|
|
88305
|
+
import { dirname as dirname14, join as join22 } from "path";
|
|
87775
88306
|
var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
|
|
87776
88307
|
|
|
87777
88308
|
class LazyPlanningEventEmitter {
|
|
@@ -87794,7 +88325,7 @@ class LazyPlanningEventEmitter {
|
|
|
87794
88325
|
var DEFAULT_PLANNING_FOLDERS = {
|
|
87795
88326
|
tasksDir: DEFAULT_TASKS_DIR,
|
|
87796
88327
|
featuresDir: DEFAULT_FEATURES_DIR,
|
|
87797
|
-
foldersConfig: { active_folder: DEFAULT_TASKS_DIR, folders: { [DEFAULT_TASKS_DIR]: {
|
|
88328
|
+
foldersConfig: { active_folder: DEFAULT_TASKS_DIR, folders: { [DEFAULT_TASKS_DIR]: { baseCounter: 0 } } }
|
|
87798
88329
|
};
|
|
87799
88330
|
|
|
87800
88331
|
class NotConfiguredError extends Error {
|
|
@@ -87806,7 +88337,7 @@ class NotConfiguredError extends Error {
|
|
|
87806
88337
|
function createServerContext(appRt, options) {
|
|
87807
88338
|
const cwd = options.cwd;
|
|
87808
88339
|
const fs3 = options.fs;
|
|
87809
|
-
const dbUrl = options.dbUrl ??
|
|
88340
|
+
const dbUrl = options.dbUrl ?? join22(cwd, DEFAULT_DATABASE_URL);
|
|
87810
88341
|
const eventsBus = options.eventsBus ?? appRt.events;
|
|
87811
88342
|
const jobQueueEnabled = options.jobQueueEnabled ?? false;
|
|
87812
88343
|
const folders = options.folders ?? DEFAULT_PLANNING_FOLDERS;
|
|
@@ -88166,15 +88697,15 @@ async function handleFeatureActionJob(ctx, env, payload) {
|
|
|
88166
88697
|
await runFeatureActionJob(ctx, env, payload);
|
|
88167
88698
|
}
|
|
88168
88699
|
async function resolveWebDistPath(configuredPath) {
|
|
88169
|
-
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath :
|
|
88170
|
-
|
|
88171
|
-
|
|
88172
|
-
|
|
88173
|
-
|
|
88174
|
-
|
|
88700
|
+
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join23(process.cwd(), configuredPath)] : [
|
|
88701
|
+
join23(process.cwd(), "dist/web"),
|
|
88702
|
+
join23(import.meta.dir, "web"),
|
|
88703
|
+
join23(dirname15(process.execPath), "web"),
|
|
88704
|
+
join23(dirname15(process.execPath), "../web"),
|
|
88705
|
+
join23(import.meta.dir, "../../../dist/web")
|
|
88175
88706
|
];
|
|
88176
88707
|
for (const candidate of candidates) {
|
|
88177
|
-
if (await Bun.file(
|
|
88708
|
+
if (await Bun.file(join23(candidate, "index.html")).exists()) {
|
|
88178
88709
|
return candidate;
|
|
88179
88710
|
}
|
|
88180
88711
|
}
|
|
@@ -88333,7 +88864,7 @@ if (false) {}
|
|
|
88333
88864
|
|
|
88334
88865
|
// src/commands/serve.ts
|
|
88335
88866
|
function resolveServeDbUrl(cwd, env, configuredUrl) {
|
|
88336
|
-
return env.DATABASE_URL === undefined ?
|
|
88867
|
+
return env.DATABASE_URL === undefined ? join24(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
|
|
88337
88868
|
}
|
|
88338
88869
|
function registerServeCommand(program2, context4) {
|
|
88339
88870
|
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) => {
|
|
@@ -88372,7 +88903,7 @@ function registerServeCommand(program2, context4) {
|
|
|
88372
88903
|
}
|
|
88373
88904
|
|
|
88374
88905
|
// src/commands/status.ts
|
|
88375
|
-
import { join as
|
|
88906
|
+
import { join as join25 } from "path";
|
|
88376
88907
|
|
|
88377
88908
|
// src/errors.ts
|
|
88378
88909
|
class CommandError extends Error {
|
|
@@ -88443,8 +88974,8 @@ function registerStatusCommand(program2, context4) {
|
|
|
88443
88974
|
}
|
|
88444
88975
|
async function runStatusCore(path9, options, context4) {
|
|
88445
88976
|
const [packageJsonExists, spurConfigExists, git, agentSpecs] = await Promise.all([
|
|
88446
|
-
context4.fs.exists(
|
|
88447
|
-
context4.fs.exists(
|
|
88977
|
+
context4.fs.exists(join25(context4.cwd, "package.json")),
|
|
88978
|
+
context4.fs.exists(join25(context4.cwd, ".spur", "config.yaml")),
|
|
88448
88979
|
gitContext(context4.cwd),
|
|
88449
88980
|
listAgentSpecIds(context4)
|
|
88450
88981
|
]);
|
|
@@ -88472,14 +89003,14 @@ async function runStatusCore(path9, options, context4) {
|
|
|
88472
89003
|
return status.ok ? 0 : 1;
|
|
88473
89004
|
}
|
|
88474
89005
|
async function listAgentSpecIds(context4) {
|
|
88475
|
-
const dir =
|
|
89006
|
+
const dir = join25(context4.cwd, ".spur", "agents");
|
|
88476
89007
|
if (!await context4.fs.exists(dir))
|
|
88477
89008
|
return [];
|
|
88478
89009
|
const entries = await context4.fs.readDir(dir);
|
|
88479
89010
|
return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
|
|
88480
89011
|
}
|
|
88481
89012
|
async function readTargetStatus(context4, targetPath) {
|
|
88482
|
-
const resolved =
|
|
89013
|
+
const resolved = join25(context4.cwd, targetPath);
|
|
88483
89014
|
const stat = await context4.fs.stat(resolved);
|
|
88484
89015
|
if (stat === null)
|
|
88485
89016
|
throw new CommandError(`status failed: path does not exist at ${resolved}`);
|
|
@@ -88492,7 +89023,7 @@ init_loader();
|
|
|
88492
89023
|
init_src2();
|
|
88493
89024
|
init_dist5();
|
|
88494
89025
|
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
88495
|
-
import { join as
|
|
89026
|
+
import { join as join26 } from "path";
|
|
88496
89027
|
// schemas/section-matrix.schema.json
|
|
88497
89028
|
var section_matrix_schema_default = {
|
|
88498
89029
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -88804,7 +89335,7 @@ var spur_config_schema_default = {
|
|
|
88804
89335
|
},
|
|
88805
89336
|
tasks: {
|
|
88806
89337
|
type: "object",
|
|
88807
|
-
description: "Task-folder registration (design \xA79). Absorbs the legacy docs/.tasks/config.json folders +
|
|
89338
|
+
description: "Task-folder registration (design \xA79). Absorbs the legacy docs/.tasks/config.json folders + baseCounter concepts. Mirrors @gobing-ai/spur-config tasksConfigSchema (Zod is SSOT).",
|
|
88808
89339
|
properties: {
|
|
88809
89340
|
folders: {
|
|
88810
89341
|
type: "object",
|
|
@@ -89083,12 +89614,17 @@ function renderMigrationReport(report, dryRun, corpusDir) {
|
|
|
89083
89614
|
}
|
|
89084
89615
|
function registerTaskCommand(program2, context4) {
|
|
89085
89616
|
const task = program2.command("task").summary("manage tasks");
|
|
89086
|
-
task.command("create").summary("Create a new task with race-safe WBS allocation.").argument("<title>", "Task title").option("--feature <id>", "Feature ID for traceability and Goal\u2192Background derivation").option("--parent <wbs>", "Parent WBS for sub-task grouping").option("--template <variant>", `Template variant (${TASK_VARIANTS.join("|")})`).option("--folder <path>", "Custom tasks folder").option("--dedupe-within <seconds>", "
|
|
89617
|
+
task.command("create").summary("Create a new task with race-safe WBS allocation.").argument("<title>", "Task title").option("--feature <id>", "Feature ID for traceability and Goal\u2192Background derivation").option("--parent <wbs>", "Parent WBS for sub-task grouping").option("--template <variant>", `Template variant (${TASK_VARIANTS.join("|")})`).option("--folder <path>", "Custom tasks folder").option("--dedupe-within <seconds>", "Override the default dedup window (seconds). Guard is on (300s) by default when --feature is set.", Number).option("--allow-duplicate-name", "Disable the dedup guard entirely (creates anyway)").option("--json", "Output machine-readable JSON").action(async (title2, options) => {
|
|
89087
89618
|
if (options.template !== undefined && !TASK_VARIANTS.includes(options.template)) {
|
|
89088
89619
|
context4.output.error(`Unknown template variant "${options.template}". Valid: ${TASK_VARIANTS.join(", ")}`);
|
|
89089
89620
|
context4.setExitCode(2);
|
|
89090
89621
|
return;
|
|
89091
89622
|
}
|
|
89623
|
+
if (options.dedupeWithin !== undefined && (!Number.isInteger(options.dedupeWithin) || options.dedupeWithin <= 0)) {
|
|
89624
|
+
context4.output.error("--dedupe-within must be a positive integer");
|
|
89625
|
+
context4.setExitCode(2);
|
|
89626
|
+
return;
|
|
89627
|
+
}
|
|
89092
89628
|
const svc = await makeService2(context4, options.folder);
|
|
89093
89629
|
try {
|
|
89094
89630
|
const result = await svc.create({
|
|
@@ -89096,7 +89632,7 @@ function registerTaskCommand(program2, context4) {
|
|
|
89096
89632
|
featureId: options.feature,
|
|
89097
89633
|
parentWbs: options.parent,
|
|
89098
89634
|
template: options.template,
|
|
89099
|
-
dedupeWithinSec: options.allowDuplicateName ?
|
|
89635
|
+
dedupeWithinSec: options.allowDuplicateName ? null : options.dedupeWithin
|
|
89100
89636
|
});
|
|
89101
89637
|
if (options.json) {
|
|
89102
89638
|
context4.output.write(toJson2(result));
|
|
@@ -89104,8 +89640,37 @@ function registerTaskCommand(program2, context4) {
|
|
|
89104
89640
|
context4.output.write(`Created task ${result.ref.id}: ${result.ref.filePath}`);
|
|
89105
89641
|
}
|
|
89106
89642
|
} catch (err) {
|
|
89107
|
-
if (err instanceof
|
|
89108
|
-
|
|
89643
|
+
if (err instanceof WbsCollisionError) {
|
|
89644
|
+
if (options.json) {
|
|
89645
|
+
context4.output.write(toJson2({
|
|
89646
|
+
ok: false,
|
|
89647
|
+
error: {
|
|
89648
|
+
code: "wbs-collision",
|
|
89649
|
+
message: err.message,
|
|
89650
|
+
wbs: err.wbs,
|
|
89651
|
+
existingPath: err.existingPath,
|
|
89652
|
+
attemptedPath: err.attemptedPath
|
|
89653
|
+
}
|
|
89654
|
+
}));
|
|
89655
|
+
} else {
|
|
89656
|
+
context4.output.error(err.message);
|
|
89657
|
+
}
|
|
89658
|
+
context4.setExitCode(3);
|
|
89659
|
+
} else if (err instanceof DuplicateFollowUpError) {
|
|
89660
|
+
if (options.json) {
|
|
89661
|
+
context4.output.write(toJson2({
|
|
89662
|
+
ok: false,
|
|
89663
|
+
error: {
|
|
89664
|
+
code: "duplicate-follow-up",
|
|
89665
|
+
message: err.message,
|
|
89666
|
+
existingWbs: err.existingWbs,
|
|
89667
|
+
existingName: err.existingName,
|
|
89668
|
+
attemptedName: err.attemptedName
|
|
89669
|
+
}
|
|
89670
|
+
}));
|
|
89671
|
+
} else {
|
|
89672
|
+
context4.output.error(err.message);
|
|
89673
|
+
}
|
|
89109
89674
|
context4.setExitCode(3);
|
|
89110
89675
|
} else {
|
|
89111
89676
|
context4.output.error(String(err));
|
|
@@ -89176,7 +89741,7 @@ ${result.content}`);
|
|
|
89176
89741
|
let forcedDoneVerdict;
|
|
89177
89742
|
if (status === "done") {
|
|
89178
89743
|
const current = await svc.show(wbs);
|
|
89179
|
-
const verdictDir = options.verdictDir ??
|
|
89744
|
+
const verdictDir = options.verdictDir ?? join26(context4.cwd, ".spur", "run");
|
|
89180
89745
|
const loaded = await readVerdictArtifact(context4.fs, verdictDir, wbs);
|
|
89181
89746
|
const guardOutcome = evaluateDoneTransition({
|
|
89182
89747
|
wbs,
|
|
@@ -89435,8 +90000,26 @@ ${result.content}`);
|
|
|
89435
90000
|
}
|
|
89436
90001
|
}
|
|
89437
90002
|
} catch (err) {
|
|
89438
|
-
|
|
89439
|
-
|
|
90003
|
+
if (err instanceof WbsCollisionError) {
|
|
90004
|
+
if (options.json) {
|
|
90005
|
+
context4.output.write(toJson2({
|
|
90006
|
+
ok: false,
|
|
90007
|
+
error: {
|
|
90008
|
+
code: "wbs-collision",
|
|
90009
|
+
message: err.message,
|
|
90010
|
+
wbs: err.wbs,
|
|
90011
|
+
existingPath: err.existingPath,
|
|
90012
|
+
attemptedPath: err.attemptedPath
|
|
90013
|
+
}
|
|
90014
|
+
}));
|
|
90015
|
+
} else {
|
|
90016
|
+
context4.output.error(err.message);
|
|
90017
|
+
}
|
|
90018
|
+
context4.setExitCode(3);
|
|
90019
|
+
} else {
|
|
90020
|
+
context4.output.error(String(err));
|
|
90021
|
+
context4.setExitCode(1);
|
|
90022
|
+
}
|
|
89440
90023
|
}
|
|
89441
90024
|
});
|
|
89442
90025
|
task.command("record").summary("Record pipeline results into the task file \u2014 Testing, Review, and optional Solution backfill.").argument("<wbs>", "Task WBS number").option("--verdict-file <path>", "Path to verdict JSON (default: .spur/run/<wbs>-verdict.json)").option("--solution-from-diff", "Backfill Solution from git diff when bare").option("--transition <status>", "Optional lifecycle transition (e.g. testing)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
|
|
@@ -89574,6 +90157,23 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
89574
90157
|
}
|
|
89575
90158
|
}
|
|
89576
90159
|
}
|
|
90160
|
+
if (!wbs) {
|
|
90161
|
+
const locator = await makeTaskLocator(context4);
|
|
90162
|
+
const duplicates2 = await locator.findDuplicateWbs();
|
|
90163
|
+
for (const dup of duplicates2) {
|
|
90164
|
+
const [first] = dup;
|
|
90165
|
+
if (first === undefined)
|
|
90166
|
+
continue;
|
|
90167
|
+
const msg = `Duplicate WBS ${first.wbs} found in ${dup.length} files:
|
|
90168
|
+
` + dup.map((h2) => ` ${h2.filePath}`).join(`
|
|
90169
|
+
`);
|
|
90170
|
+
if (json3) {
|
|
90171
|
+
results.push({ wbs: first.wbs, pass: false, status: "duplicate", findings: [msg] });
|
|
90172
|
+
} else {
|
|
90173
|
+
context4.output.error(msg);
|
|
90174
|
+
}
|
|
90175
|
+
}
|
|
90176
|
+
}
|
|
89577
90177
|
if (json3) {
|
|
89578
90178
|
context4.output.write(toJson2(results));
|
|
89579
90179
|
}
|
|
@@ -89716,7 +90316,7 @@ function loadTemplateContent(projectRoot, variant) {
|
|
|
89716
90316
|
return templateContentCache.get(variant);
|
|
89717
90317
|
if (templateMissSet.has(variant))
|
|
89718
90318
|
return;
|
|
89719
|
-
const localPath =
|
|
90319
|
+
const localPath = join26(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
|
|
89720
90320
|
if (existsSync10(localPath)) {
|
|
89721
90321
|
const content = readFileSync9(localPath, "utf8");
|
|
89722
90322
|
templateContentCache.set(variant, content);
|
|
@@ -89724,7 +90324,7 @@ function loadTemplateContent(projectRoot, variant) {
|
|
|
89724
90324
|
}
|
|
89725
90325
|
const root = bundledConfigRoot();
|
|
89726
90326
|
if (root !== null) {
|
|
89727
|
-
const templatePath =
|
|
90327
|
+
const templatePath = join26(root, "templates", "task", `${variant}.md`);
|
|
89728
90328
|
if (existsSync10(templatePath)) {
|
|
89729
90329
|
const content = readFileSync9(templatePath, "utf8");
|
|
89730
90330
|
templateContentCache.set(variant, content);
|
|
@@ -89740,13 +90340,13 @@ function loadTemplateBodies(projectRoot, variant) {
|
|
|
89740
90340
|
if (cached2 !== undefined)
|
|
89741
90341
|
return cached2;
|
|
89742
90342
|
let bodies = {};
|
|
89743
|
-
const localPath =
|
|
90343
|
+
const localPath = join26(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
|
|
89744
90344
|
if (existsSync10(localPath)) {
|
|
89745
90345
|
bodies = extractTemplateBodies(readFileSync9(localPath, "utf8"));
|
|
89746
90346
|
} else {
|
|
89747
90347
|
const root = bundledConfigRoot();
|
|
89748
90348
|
if (root !== null) {
|
|
89749
|
-
const templatePath =
|
|
90349
|
+
const templatePath = join26(root, "templates", "task", `${variant}.md`);
|
|
89750
90350
|
if (existsSync10(templatePath)) {
|
|
89751
90351
|
bodies = extractTemplateBodies(readFileSync9(templatePath, "utf8"));
|
|
89752
90352
|
}
|
|
@@ -89799,7 +90399,7 @@ async function loadSectionMatrixUncached(projectRoot) {
|
|
|
89799
90399
|
}
|
|
89800
90400
|
const root = bundledConfigRoot();
|
|
89801
90401
|
if (root !== null) {
|
|
89802
|
-
const matrixPath =
|
|
90402
|
+
const matrixPath = join26(root, "tasks", "section-matrix.yaml");
|
|
89803
90403
|
if (await fs3.exists(matrixPath)) {
|
|
89804
90404
|
const data = await loadStructuredSpurConfig(matrixPath, {
|
|
89805
90405
|
validateJsonSchema: true,
|
|
@@ -90518,6 +91118,10 @@ function formatTraceTimeline(result) {
|
|
|
90518
91118
|
`Started: ${run.startedAt} Completed: ${run.completedAt ?? "-"} Events: ${events2.length}`,
|
|
90519
91119
|
""
|
|
90520
91120
|
];
|
|
91121
|
+
if (result.outputArtifact !== undefined) {
|
|
91122
|
+
lines.push(`Agent output: ${result.outputArtifact} (tail -f for live view)`);
|
|
91123
|
+
lines.push("");
|
|
91124
|
+
}
|
|
90521
91125
|
for (const event2 of events2) {
|
|
90522
91126
|
lines.push(formatTimelineEvent(event2));
|
|
90523
91127
|
}
|
|
@@ -90598,7 +91202,7 @@ init_src3();
|
|
|
90598
91202
|
init_src();
|
|
90599
91203
|
init_src2();
|
|
90600
91204
|
init_dist5();
|
|
90601
|
-
import { dirname as dirname16, join as
|
|
91205
|
+
import { dirname as dirname16, join as join27, resolve as resolve9 } from "path";
|
|
90602
91206
|
import { isatty as isatty4 } from "tty";
|
|
90603
91207
|
|
|
90604
91208
|
// ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
|
|
@@ -92020,7 +92624,7 @@ function createCliContext(options) {
|
|
|
92020
92624
|
function noopSetExitCode(_code) {}
|
|
92021
92625
|
async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, dbUrl) {
|
|
92022
92626
|
const config4 = buildConfigFromEnv(env);
|
|
92023
|
-
const configuredUrl = env.DATABASE_URL === undefined ?
|
|
92627
|
+
const configuredUrl = env.DATABASE_URL === undefined ? join27(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
|
|
92024
92628
|
const url2 = dbUrl ?? configuredUrl;
|
|
92025
92629
|
if (url2 !== IN_MEMORY_DATABASE_URL) {
|
|
92026
92630
|
await createNodeFileSystem3().ensureDir(dirname16(url2));
|