@gobing-ai/spur 0.3.33 → 0.3.34
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/config.example.yaml +1 -1
- package/config/workflows/feature-lifecycle.yaml +10 -0
- package/config/workflows/task-pipeline.yaml +15 -3
- package/package.json +2 -2
- package/schemas/spur-config.schema.json +37 -23
- package/spur.js +252 -132
- package/web/index.html +1 -1
|
@@ -74,7 +74,7 @@ agent:
|
|
|
74
74
|
# agent: pi
|
|
75
75
|
# tier: capable-1
|
|
76
76
|
|
|
77
|
-
# `default-by-phase` is a
|
|
77
|
+
# `default-by-phase` is a REMOVED (0452). Prefer
|
|
78
78
|
# executor `tier` + stage-registry model_policy. When present, a matching
|
|
79
79
|
# phase mapping is still authoritative (fail-fast; no fall back to default)
|
|
80
80
|
# and shadows stage routing for that phase.
|
|
@@ -74,6 +74,16 @@ transitions:
|
|
|
74
74
|
guard:
|
|
75
75
|
kind: always
|
|
76
76
|
|
|
77
|
+
# Reopen: done → active (design §7.5 — done is re-enterable; cancelled is not)
|
|
78
|
+
- from: done
|
|
79
|
+
to: active
|
|
80
|
+
description: >
|
|
81
|
+
Reopen completed feature for follow-up work (mandatory History entry).
|
|
82
|
+
Mirrors task-lifecycle done→wip. Prefer spur feature sync --force when
|
|
83
|
+
non-terminal tasks are already linked.
|
|
84
|
+
guard:
|
|
85
|
+
kind: always
|
|
86
|
+
|
|
77
87
|
# Blocked ↔ active — bidirectional
|
|
78
88
|
- from: active
|
|
79
89
|
to: blocked
|
|
@@ -113,6 +113,21 @@ states:
|
|
|
113
113
|
# hook at run start — see the 0062 follow-up note; there is no link-writing
|
|
114
114
|
# CLI verb to call from a shell step, so it cannot live in pure YAML.
|
|
115
115
|
message: "Pipeline start for task ${vars.wbs}."
|
|
116
|
+
# R1 (0453): auto-profile precheck reopens a done feature before task check.
|
|
117
|
+
# Under profile=auto, resolve feature_id, sync (preferred) or update to active.
|
|
118
|
+
# Under non-auto, leave R4 message to guide the operator.
|
|
119
|
+
- kind: shell
|
|
120
|
+
options:
|
|
121
|
+
command: >-
|
|
122
|
+
if [ "$profile" = auto ]; then
|
|
123
|
+
FID=$($spurBin task show $wbs --json 2>/dev/null |
|
|
124
|
+
jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);
|
|
125
|
+
if [ -n "$FID" ]; then
|
|
126
|
+
$spurBin feature sync "$FID" --force --json 2>/dev/null ||
|
|
127
|
+
$spurBin feature update "$FID" active 2>/dev/null || true;
|
|
128
|
+
fi;
|
|
129
|
+
fi;
|
|
130
|
+
exit 0
|
|
116
131
|
|
|
117
132
|
- id: implement
|
|
118
133
|
description: >
|
|
@@ -181,8 +196,6 @@ states:
|
|
|
181
196
|
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
182
197
|
echo 0 > "$ATTEMPT_FILE" &&
|
|
183
198
|
set +e &&
|
|
184
|
-
# sh -c re-parses multi-command gates (&&, ;); do NOT use bare `$qualityGateCmd`
|
|
185
|
-
# word-split (breaks on &&). qualityGateCmd is pipeline-owned trusted config only.
|
|
186
199
|
( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
|
|
187
200
|
if [ "$gate_rc" -eq 0 ]; then
|
|
188
201
|
printf 'PASS\n' > "$STATUS_FILE";
|
|
@@ -223,7 +236,6 @@ states:
|
|
|
223
236
|
mkdir -p .spur/run &&
|
|
224
237
|
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
225
238
|
set +e &&
|
|
226
|
-
# See test probe: sh -c for multi-command; qualityGateCmd is trusted config only.
|
|
227
239
|
( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
|
|
228
240
|
if [ "$gate_rc" -eq 0 ]; then
|
|
229
241
|
printf 'PASS\n' > "$STATUS_FILE";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Spur CLI
|
|
3
|
+
"version": "0.3.34",
|
|
4
|
+
"description": "Spur CLI \u2014 local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
|
7
7
|
"cli",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/gobing-ai/spur/schemas/spur-config.schema.json",
|
|
4
4
|
"title": "Spur Project Configuration",
|
|
5
|
-
"description": "Schema for .spur/config.yaml
|
|
5
|
+
"description": "Schema for .spur/config.yaml \u2014 the single project configuration surface consumed by spur and (future) spur-server.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["version", "name"],
|
|
8
8
|
"properties": {
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
},
|
|
108
108
|
"executors": {
|
|
109
109
|
"type": "array",
|
|
110
|
-
"description": "Named executor profiles: each pairs a canonical agent with an optional model override. Referenced by name from `default` and
|
|
110
|
+
"description": "Named executor profiles: each pairs a canonical agent with an optional model override. Referenced by name from `agent.default` and stage model_policy (default-by-phase removed 0452).",
|
|
111
111
|
"items": {
|
|
112
112
|
"type": "object",
|
|
113
113
|
"required": ["name", "agent"],
|
|
@@ -130,18 +130,11 @@
|
|
|
130
130
|
"tier": {
|
|
131
131
|
"type": "string",
|
|
132
132
|
"enum": ["cheap", "standard", "capable-1", "capable-2", "capable-3"],
|
|
133
|
-
"description": "Capability tier for stage-registry adaptive model routing (ADR-033, 0343). Live values: cheap | standard | capable-1 | capable-2 | capable-3 (1=low output quality, 3=high within the capable band). A stage starts on the cheapest eligible executor meeting its model_policy min_tier. Bare legacy `capable` is accepted at runtime (zod preprocess
|
|
133
|
+
"description": "Capability tier for stage-registry adaptive model routing (ADR-033, 0343). Live values: cheap | standard | capable-1 | capable-2 | capable-3 (1=low output quality, 3=high within the capable band). A stage starts on the cheapest eligible executor meeting its model_policy min_tier. Bare legacy `capable` is accepted at runtime (zod preprocess \u2192 capable-1) during the deprecation window but is not part of this editor enum."
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
},
|
|
138
|
-
"default-by-phase": {
|
|
139
|
-
"type": "object",
|
|
140
|
-
"description": "DEPRECATED (ADR-033). Backward-compat map of dev phase (e.g. dev-run, dev-review) to executor selector. Prefer executor `tier` + stage-registry model_policy. When present, a matching phase mapping is still authoritative (fail-fast; no fall back to `default`) and shadows stage routing for that phase.",
|
|
141
|
-
"additionalProperties": {
|
|
142
|
-
"type": "string"
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
138
|
"team": {
|
|
146
139
|
"type": "object",
|
|
147
140
|
"description": "Declarative agent teams keyed by team id (the map key is the teamId). Each team materializes a roster of agent specs (feature M). Mirrors @gobing-ai/spur-config TeamConfigSchema (Zod is SSOT).",
|
|
@@ -169,7 +162,10 @@
|
|
|
169
162
|
"description": "Roster of member references (>= 1). A bare string is shorthand for { executor: <string> }; the composed agent id is <teamId>-<id ?? executor>.",
|
|
170
163
|
"items": {
|
|
171
164
|
"oneOf": [
|
|
172
|
-
{
|
|
165
|
+
{
|
|
166
|
+
"type": "string",
|
|
167
|
+
"minLength": 1
|
|
168
|
+
},
|
|
173
169
|
{
|
|
174
170
|
"type": "object",
|
|
175
171
|
"required": ["executor"],
|
|
@@ -177,27 +173,41 @@
|
|
|
177
173
|
"executor": {
|
|
178
174
|
"type": "string",
|
|
179
175
|
"minLength": 1,
|
|
180
|
-
"description": "Executor name
|
|
176
|
+
"description": "Executor name \u2014 resolved against agent.executors first, else a raw canonical agent type."
|
|
181
177
|
},
|
|
182
178
|
"id": {
|
|
183
179
|
"type": "string",
|
|
184
180
|
"minLength": 1,
|
|
185
181
|
"description": "Local member id; composed id is <teamId>-<id ?? executor> (must match ^[a-z][a-z0-9_-]{1,63}$)."
|
|
186
182
|
},
|
|
187
|
-
"purpose": {
|
|
183
|
+
"purpose": {
|
|
184
|
+
"type": "string"
|
|
185
|
+
},
|
|
188
186
|
"workspace": {
|
|
189
187
|
"type": "string",
|
|
190
188
|
"minLength": 1,
|
|
191
189
|
"description": "Per-member workspace override. A leading `~` is tilde-expanded at load."
|
|
192
190
|
},
|
|
193
|
-
"model": {
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
"model": {
|
|
192
|
+
"type": "string",
|
|
193
|
+
"minLength": 1
|
|
194
|
+
},
|
|
195
|
+
"autonomy": {
|
|
196
|
+
"type": "string"
|
|
197
|
+
},
|
|
198
|
+
"systemPrompt": {
|
|
199
|
+
"type": "string"
|
|
200
|
+
},
|
|
196
201
|
"command": {
|
|
197
202
|
"type": "array",
|
|
198
|
-
"items": {
|
|
203
|
+
"items": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"minLength": 1
|
|
206
|
+
}
|
|
199
207
|
},
|
|
200
|
-
"autostart": {
|
|
208
|
+
"autostart": {
|
|
209
|
+
"type": "boolean"
|
|
210
|
+
}
|
|
201
211
|
}
|
|
202
212
|
}
|
|
203
213
|
]
|
|
@@ -213,7 +223,9 @@
|
|
|
213
223
|
"properties": {
|
|
214
224
|
"paths": {
|
|
215
225
|
"type": "array",
|
|
216
|
-
"items": {
|
|
226
|
+
"items": {
|
|
227
|
+
"type": "string"
|
|
228
|
+
},
|
|
217
229
|
"description": "Glob paths to rule files."
|
|
218
230
|
}
|
|
219
231
|
}
|
|
@@ -223,7 +235,9 @@
|
|
|
223
235
|
"properties": {
|
|
224
236
|
"paths": {
|
|
225
237
|
"type": "array",
|
|
226
|
-
"items": {
|
|
238
|
+
"items": {
|
|
239
|
+
"type": "string"
|
|
240
|
+
},
|
|
227
241
|
"description": "Glob paths to workflow directories."
|
|
228
242
|
}
|
|
229
243
|
}
|
|
@@ -239,11 +253,11 @@
|
|
|
239
253
|
},
|
|
240
254
|
"tasks": {
|
|
241
255
|
"type": "object",
|
|
242
|
-
"description": "Task-folder registration (design
|
|
256
|
+
"description": "Task-folder registration (design \u00a79). Absorbs the legacy docs/.tasks/config.json folders + baseCounter concepts. Mirrors @gobing-ai/spur-config tasksConfigSchema (Zod is SSOT).",
|
|
243
257
|
"properties": {
|
|
244
258
|
"folders": {
|
|
245
259
|
"type": "object",
|
|
246
|
-
"description": "Map of task-folder path
|
|
260
|
+
"description": "Map of task-folder path \u2192 folder config.",
|
|
247
261
|
"additionalProperties": {
|
|
248
262
|
"type": "object",
|
|
249
263
|
"properties": {
|
|
@@ -275,7 +289,7 @@
|
|
|
275
289
|
},
|
|
276
290
|
"features": {
|
|
277
291
|
"type": "object",
|
|
278
|
-
"description": "Feature directory location (design
|
|
292
|
+
"description": "Feature directory location (design \u00a79). Mirrors @gobing-ai/spur-config featuresConfigSchema (Zod is SSOT).",
|
|
279
293
|
"properties": {
|
|
280
294
|
"dir": {
|
|
281
295
|
"type": "string",
|
package/spur.js
CHANGED
|
@@ -22521,7 +22521,7 @@ var init_figures = __esm(() => {
|
|
|
22521
22521
|
replacements = Object.entries(specialMainSymbols);
|
|
22522
22522
|
});
|
|
22523
22523
|
|
|
22524
|
-
// ../../node_modules/.bun/yoctocolors@2.2
|
|
22524
|
+
// ../../node_modules/.bun/yoctocolors@2.1.2/node_modules/yoctocolors/base.js
|
|
22525
22525
|
import tty from "tty";
|
|
22526
22526
|
var hasColors, format = (open, close) => {
|
|
22527
22527
|
if (!hasColors) {
|
|
@@ -22547,7 +22547,7 @@ var hasColors, format = (open, close) => {
|
|
|
22547
22547
|
result += string4.slice(lastIndex) + closeCode;
|
|
22548
22548
|
return result;
|
|
22549
22549
|
};
|
|
22550
|
-
}, reset, bold, dim, italic, underline,
|
|
22550
|
+
}, reset, bold, dim, italic, underline, overline, inverse, hidden, strikethrough, black, red, green, yellow, blue, magenta, cyan, white, gray, bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite, bgGray, redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, whiteBright, bgRedBright, bgGreenBright, bgYellowBright, bgBlueBright, bgMagentaBright, bgCyanBright, bgWhiteBright;
|
|
22551
22551
|
var init_base = __esm(() => {
|
|
22552
22552
|
hasColors = tty?.WriteStream?.prototype?.hasColors?.() ?? false;
|
|
22553
22553
|
reset = format(0, 0);
|
|
@@ -22555,10 +22555,6 @@ var init_base = __esm(() => {
|
|
|
22555
22555
|
dim = format(2, 22);
|
|
22556
22556
|
italic = format(3, 23);
|
|
22557
22557
|
underline = format(4, 24);
|
|
22558
|
-
underlineDouble = format("4:2", 24);
|
|
22559
|
-
underlineCurly = format("4:3", 24);
|
|
22560
|
-
underlineDotted = format("4:4", 24);
|
|
22561
|
-
underlineDashed = format("4:5", 24);
|
|
22562
22558
|
overline = format(53, 55);
|
|
22563
22559
|
inverse = format(7, 27);
|
|
22564
22560
|
hidden = format(8, 28);
|
|
@@ -22595,25 +22591,9 @@ var init_base = __esm(() => {
|
|
|
22595
22591
|
bgMagentaBright = format(105, 49);
|
|
22596
22592
|
bgCyanBright = format(106, 49);
|
|
22597
22593
|
bgWhiteBright = format(107, 49);
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
underlineYellow = format("58;5;3", 59);
|
|
22602
|
-
underlineBlue = format("58;5;4", 59);
|
|
22603
|
-
underlineMagenta = format("58;5;5", 59);
|
|
22604
|
-
underlineCyan = format("58;5;6", 59);
|
|
22605
|
-
underlineWhite = format("58;5;7", 59);
|
|
22606
|
-
underlineGray = format("58;5;8", 59);
|
|
22607
|
-
underlineRedBright = format("58;5;9", 59);
|
|
22608
|
-
underlineGreenBright = format("58;5;10", 59);
|
|
22609
|
-
underlineYellowBright = format("58;5;11", 59);
|
|
22610
|
-
underlineBlueBright = format("58;5;12", 59);
|
|
22611
|
-
underlineMagentaBright = format("58;5;13", 59);
|
|
22612
|
-
underlineCyanBright = format("58;5;14", 59);
|
|
22613
|
-
underlineWhiteBright = format("58;5;15", 59);
|
|
22614
|
-
});
|
|
22615
|
-
|
|
22616
|
-
// ../../node_modules/.bun/yoctocolors@2.2.0/node_modules/yoctocolors/index.js
|
|
22594
|
+
});
|
|
22595
|
+
|
|
22596
|
+
// ../../node_modules/.bun/yoctocolors@2.1.2/node_modules/yoctocolors/index.js
|
|
22617
22597
|
var init_yoctocolors = __esm(() => {
|
|
22618
22598
|
init_base();
|
|
22619
22599
|
});
|
|
@@ -37381,7 +37361,6 @@ var init_src = __esm(() => {
|
|
|
37381
37361
|
AgentConfigSchema = exports_external.object({
|
|
37382
37362
|
default: exports_external.string().optional(),
|
|
37383
37363
|
executors: exports_external.array(AgentExecutorConfigSchema).optional(),
|
|
37384
|
-
"default-by-phase": exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37385
37364
|
team: exports_external.record(exports_external.string(), TeamConfigSchema).optional(),
|
|
37386
37365
|
output: AgentOutputConfigSchema.optional(),
|
|
37387
37366
|
sessionAffinity: exports_external.boolean().optional()
|
|
@@ -61342,13 +61321,6 @@ class AgentService {
|
|
|
61342
61321
|
...observer !== undefined ? { observer } : {}
|
|
61343
61322
|
};
|
|
61344
61323
|
}
|
|
61345
|
-
hasWarnedDeprecation = false;
|
|
61346
|
-
warnDeprecationOnce(message) {
|
|
61347
|
-
if (!this.hasWarnedDeprecation) {
|
|
61348
|
-
this.ctx.output.error(`Warning: ${message}`);
|
|
61349
|
-
this.hasWarnedDeprecation = true;
|
|
61350
|
-
}
|
|
61351
|
-
}
|
|
61352
61324
|
async resolveAgent(prompt, flags, doctorRunner) {
|
|
61353
61325
|
const raw = stringFlag(flags, "agent", "auto");
|
|
61354
61326
|
if (raw === "auto")
|
|
@@ -61360,12 +61332,6 @@ class AgentService {
|
|
|
61360
61332
|
async resolveAgentAuto(prompt, flags, doctorRunner) {
|
|
61361
61333
|
const config4 = this.ctx.agentConfig;
|
|
61362
61334
|
const phase = extractPhase(prompt);
|
|
61363
|
-
const phaseMap = config4?.["default-by-phase"];
|
|
61364
|
-
const phaseSelector = phase !== undefined ? phaseMap?.[phase] : undefined;
|
|
61365
|
-
if (phaseSelector !== undefined && phase !== undefined) {
|
|
61366
|
-
this.warnDeprecationOnce(`default-by-phase is deprecated; use stage model_policy instead. Phase '${phase}' mapped to '${phaseSelector}'.`);
|
|
61367
|
-
return this.resolveExecutorSelector(phaseSelector, doctorRunner, "phase", phase);
|
|
61368
|
-
}
|
|
61369
61335
|
const stageFlag = stringFlag(flags, "stage", "");
|
|
61370
61336
|
const targetStageId = stageFlag !== "" ? stageFlag : phase !== undefined ? phase : undefined;
|
|
61371
61337
|
if (targetStageId !== undefined) {
|
|
@@ -62758,8 +62724,10 @@ var init_feature_check = __esm(() => {
|
|
|
62758
62724
|
await this.checkChildrenLimit(featureId2, options.featuresDir, findings);
|
|
62759
62725
|
}
|
|
62760
62726
|
const dogfoodDir = options?.dogfoodDir ?? (options?.featuresDir ? join9(dirname8(options.featuresDir), "dogfood") : undefined);
|
|
62761
|
-
const
|
|
62762
|
-
|
|
62727
|
+
const primaryTasksDir = options?.tasksDir ?? options?.tasksDirs?.[0];
|
|
62728
|
+
const runDir = options?.runDir ?? (primaryTasksDir ? defaultVerdictRunDir(primaryTasksDir) : undefined);
|
|
62729
|
+
const taskScanDirs = options?.tasksDirs && options.tasksDirs.length > 0 ? options.tasksDirs : options?.tasksDir ? [options.tasksDir] : [];
|
|
62730
|
+
await this.runL4(doc2, featureId2, status, taskScanDirs, dogfoodDir, runDir, findings);
|
|
62763
62731
|
return { id: featureId2, ...this.summarizeWithStatus(status, findings, strict, options?.severityOverrides) };
|
|
62764
62732
|
}
|
|
62765
62733
|
runL3(doc2, findings) {
|
|
@@ -62871,49 +62839,49 @@ var init_feature_check = __esm(() => {
|
|
|
62871
62839
|
});
|
|
62872
62840
|
}
|
|
62873
62841
|
}
|
|
62874
|
-
async runL4(doc2, featureId2, status,
|
|
62875
|
-
if (
|
|
62842
|
+
async runL4(doc2, featureId2, status, tasksDirs, dogfoodDir, runDir, findings) {
|
|
62843
|
+
if (tasksDirs.length === 0)
|
|
62876
62844
|
return;
|
|
62877
62845
|
let linkedTasks = 0;
|
|
62878
62846
|
const incompleteTasks = [];
|
|
62879
62847
|
const linkedTaskAc = [];
|
|
62880
62848
|
const linkedTaskSolutions = [];
|
|
62881
62849
|
const linkedTaskRecords = [];
|
|
62882
|
-
|
|
62883
|
-
|
|
62884
|
-
|
|
62885
|
-
|
|
62886
|
-
|
|
62887
|
-
try {
|
|
62888
|
-
const raw = await this.fs.readFile(`${tasksDir}/${entry}`);
|
|
62889
|
-
const taskDoc = MarkdownDocument.parse(raw, "task");
|
|
62890
|
-
const tfm = taskDoc.frontmatterData ?? {};
|
|
62891
|
-
const tfid = tfm.feature_id ?? tfm["feature-id"];
|
|
62892
|
-
if (tfid !== featureId2)
|
|
62850
|
+
for (const tasksDir of tasksDirs) {
|
|
62851
|
+
try {
|
|
62852
|
+
const entries = await this.fs.readDir(tasksDir);
|
|
62853
|
+
for (const entry of entries) {
|
|
62854
|
+
if (!/^\d{4}_.+\.md$/.test(entry))
|
|
62893
62855
|
continue;
|
|
62894
|
-
|
|
62895
|
-
|
|
62896
|
-
|
|
62897
|
-
|
|
62898
|
-
|
|
62856
|
+
try {
|
|
62857
|
+
const raw = await this.fs.readFile(`${tasksDir}/${entry}`);
|
|
62858
|
+
const taskDoc = MarkdownDocument.parse(raw, "task");
|
|
62859
|
+
const tfm = taskDoc.frontmatterData ?? {};
|
|
62860
|
+
const tfid = tfm.feature_id ?? tfm["feature-id"];
|
|
62861
|
+
if (tfid !== featureId2)
|
|
62862
|
+
continue;
|
|
62863
|
+
linkedTasks += 1;
|
|
62864
|
+
const tStatus = tfm.status ?? "backlog";
|
|
62865
|
+
const wbs = entry.match(/^(\d{4})_/)?.[1] ?? entry;
|
|
62866
|
+
if (tStatus !== "done" && tStatus !== "cancelled") {
|
|
62867
|
+
incompleteTasks.push(wbs);
|
|
62868
|
+
}
|
|
62869
|
+
const tac = stripAcFence(taskDoc.getSection("Acceptance Criteria") ?? "");
|
|
62870
|
+
if (tac.trim().length > 0)
|
|
62871
|
+
linkedTaskAc.push(tac);
|
|
62872
|
+
linkedTaskSolutions.push(taskDoc.getSection("Solution") ?? "");
|
|
62873
|
+
linkedTaskRecords.push({ wbs, status: tStatus, ac: tac });
|
|
62874
|
+
} catch {
|
|
62875
|
+
findings.push({
|
|
62876
|
+
layer: "L4",
|
|
62877
|
+
code: FINDING_CODES.L4_LINKED_TASK_PARSE_FAILED,
|
|
62878
|
+
severity: "warning",
|
|
62879
|
+
section: "",
|
|
62880
|
+
message: `Linked task file "${entry}" failed to parse \u2014 dangling feature_id edge`
|
|
62881
|
+
});
|
|
62899
62882
|
}
|
|
62900
|
-
const tac = stripAcFence(taskDoc.getSection("Acceptance Criteria") ?? "");
|
|
62901
|
-
if (tac.trim().length > 0)
|
|
62902
|
-
linkedTaskAc.push(tac);
|
|
62903
|
-
linkedTaskSolutions.push(taskDoc.getSection("Solution") ?? "");
|
|
62904
|
-
linkedTaskRecords.push({ wbs, status: tStatus, ac: tac });
|
|
62905
|
-
} catch {
|
|
62906
|
-
findings.push({
|
|
62907
|
-
layer: "L4",
|
|
62908
|
-
code: FINDING_CODES.L4_LINKED_TASK_PARSE_FAILED,
|
|
62909
|
-
severity: "warning",
|
|
62910
|
-
section: "",
|
|
62911
|
-
message: `Linked task file "${entry}" failed to parse \u2014 dangling feature_id edge`
|
|
62912
|
-
});
|
|
62913
62883
|
}
|
|
62914
|
-
}
|
|
62915
|
-
} catch {
|
|
62916
|
-
return;
|
|
62884
|
+
} catch {}
|
|
62917
62885
|
}
|
|
62918
62886
|
const acBody = stripAcFence(doc2.getSection("Acceptance Criteria") ?? "");
|
|
62919
62887
|
const hasScenarios = /^\s*Scenario:/m.test(acBody);
|
|
@@ -63018,7 +62986,9 @@ var init_feature_check = __esm(() => {
|
|
|
63018
62986
|
artifacts2.set(wbs, artifact);
|
|
63019
62987
|
const diagnosticParts = [];
|
|
63020
62988
|
if (artifact.diagnostics.artifactError !== undefined) {
|
|
63021
|
-
|
|
62989
|
+
if (artifact.diagnostics.artifactError !== "artifact is missing") {
|
|
62990
|
+
diagnosticParts.push(artifact.diagnostics.artifactError);
|
|
62991
|
+
}
|
|
63022
62992
|
}
|
|
63023
62993
|
if (artifact.diagnostics.rejectedRowCount > 0) {
|
|
63024
62994
|
diagnosticParts.push(`${artifact.diagnostics.rejectedRowCount} rejected coverage row(s)`);
|
|
@@ -63381,9 +63351,13 @@ class FeatureService {
|
|
|
63381
63351
|
}
|
|
63382
63352
|
const checkL4Gate = async () => {
|
|
63383
63353
|
const checkSvc = new FeatureCheckService(this.ctx.fs);
|
|
63354
|
+
const tasksDirs = this.ctx.foldersConfig ? Object.keys(this.ctx.foldersConfig.folders).map((p) => this.ctx.fs.resolve(p)) : [this.ctx.tasksDir];
|
|
63355
|
+
if (!tasksDirs.includes(this.ctx.tasksDir))
|
|
63356
|
+
tasksDirs.unshift(this.ctx.tasksDir);
|
|
63384
63357
|
const res = await checkSvc.check(feature.filePath, featureId2, {
|
|
63385
63358
|
featuresDir: this.ctx.featuresDir,
|
|
63386
63359
|
tasksDir: this.ctx.tasksDir,
|
|
63360
|
+
tasksDirs,
|
|
63387
63361
|
runDir: defaultVerdictRunDir(this.ctx.tasksDir)
|
|
63388
63362
|
});
|
|
63389
63363
|
const l4Errors = res.findings.filter((f) => f.layer === "L4" && f.severity === "error");
|
|
@@ -66349,12 +66323,13 @@ var init_task_check = __esm(() => {
|
|
|
66349
66323
|
const featureTerminal = featureStatus === "done" || featureStatus === "cancelled";
|
|
66350
66324
|
const taskTerminal = status === "done" || status === "cancelled";
|
|
66351
66325
|
if (featureTerminal && !taskTerminal) {
|
|
66326
|
+
const reopenGuide = featureStatus === "done" ? `Reopen: \`spur feature update ${featureId2} active\` (or \`spur feature sync ${featureId2} --force\` when non-terminal tasks are already linked). Alternatively re-parent: \`spur task update <wbs> --feature <otherId>\`.` : `Feature is cancelled \u2014 re-parent or unlink this live task (\`spur task update <wbs> --feature <otherId>\`).`;
|
|
66352
66327
|
findings.push({
|
|
66353
66328
|
layer: "L4",
|
|
66354
66329
|
code: FINDING_CODES.L4_FEATURE_TERMINAL,
|
|
66355
66330
|
severity: "error",
|
|
66356
66331
|
section: "",
|
|
66357
|
-
message: `Feature "${featureId2}" is ${featureStatus} \u2014
|
|
66332
|
+
message: `Feature "${featureId2}" is ${featureStatus} \u2014 live tasks cannot stay linked without action. ${reopenGuide}`
|
|
66358
66333
|
});
|
|
66359
66334
|
}
|
|
66360
66335
|
await this.checkAcCoverage(doc2, featurePath, featureId2, findings);
|
|
@@ -67937,31 +67912,45 @@ function extractRequirements(text4) {
|
|
|
67937
67912
|
const lines = text4.split(`
|
|
67938
67913
|
`);
|
|
67939
67914
|
let inTable = false;
|
|
67915
|
+
let colMap = null;
|
|
67940
67916
|
for (const line of lines) {
|
|
67941
67917
|
const trimmed = line.trim();
|
|
67942
67918
|
if (!trimmed.startsWith("|"))
|
|
67943
67919
|
continue;
|
|
67944
67920
|
const cells = trimmed.split("|").map((c3) => c3.trim()).filter(Boolean);
|
|
67945
67921
|
if (!inTable && cells.length >= 2) {
|
|
67946
|
-
const h0 = (cells[0] ?? "").toLowerCase();
|
|
67947
|
-
const
|
|
67948
|
-
if (
|
|
67949
|
-
|
|
67950
|
-
|
|
67922
|
+
const h0 = (cells[0] ?? "").toLowerCase().trim();
|
|
67923
|
+
const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
67924
|
+
if (h0IsId) {
|
|
67925
|
+
const statusIdx = cells.findIndex((c3) => {
|
|
67926
|
+
const x = c3.toLowerCase().trim();
|
|
67927
|
+
return x.includes("status") || x === "verdict";
|
|
67928
|
+
});
|
|
67929
|
+
if (statusIdx >= 0) {
|
|
67930
|
+
const evidenceIdx = cells.findIndex((c3) => c3.toLowerCase().includes("evidence"));
|
|
67931
|
+
colMap = {
|
|
67932
|
+
id: 0,
|
|
67933
|
+
status: statusIdx,
|
|
67934
|
+
evidence: evidenceIdx >= 0 ? evidenceIdx : undefined
|
|
67935
|
+
};
|
|
67936
|
+
inTable = true;
|
|
67937
|
+
continue;
|
|
67938
|
+
}
|
|
67951
67939
|
}
|
|
67952
67940
|
}
|
|
67953
67941
|
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
67954
67942
|
continue;
|
|
67955
|
-
if (inTable && cells.length >= 2) {
|
|
67943
|
+
if (inTable && colMap && cells.length >= 2) {
|
|
67956
67944
|
const first = (cells[0] ?? "").toLowerCase();
|
|
67957
67945
|
const second = (cells[1] ?? "").toLowerCase();
|
|
67958
67946
|
if ((first === "ac" || first.includes("acceptance") || first === "check" || first === "name") && (second.includes("status") || second === "verdict")) {
|
|
67959
67947
|
inTable = false;
|
|
67948
|
+
colMap = null;
|
|
67960
67949
|
continue;
|
|
67961
67950
|
}
|
|
67962
|
-
const id = cells[
|
|
67963
|
-
const statusRaw = (cells[
|
|
67964
|
-
const evidence =
|
|
67951
|
+
const id = cells[colMap.id] ?? "";
|
|
67952
|
+
const statusRaw = (cells[colMap.status] ?? "").toUpperCase();
|
|
67953
|
+
const evidence = colMap.evidence !== undefined ? cells[colMap.evidence] ?? "" : "";
|
|
67965
67954
|
const status = normalizeStatus(statusRaw);
|
|
67966
67955
|
if (status !== null && id.length > 0) {
|
|
67967
67956
|
reqs.push({ id, status, evidence });
|
|
@@ -69126,42 +69115,45 @@ import { dirname as dirname12, isAbsolute as isAbsolute2, join as join15 } from
|
|
|
69126
69115
|
class AgentRunActionRunner {
|
|
69127
69116
|
observabilityBus;
|
|
69128
69117
|
steeringController;
|
|
69118
|
+
agentConfig;
|
|
69129
69119
|
kind = KIND;
|
|
69130
69120
|
agentService;
|
|
69131
|
-
constructor(agentService, observabilityBus, steeringController) {
|
|
69121
|
+
constructor(agentService, observabilityBus, steeringController, agentConfig = {}) {
|
|
69132
69122
|
this.observabilityBus = observabilityBus;
|
|
69133
69123
|
this.steeringController = steeringController;
|
|
69124
|
+
this.agentConfig = agentConfig;
|
|
69134
69125
|
this.agentService = agentService;
|
|
69135
69126
|
}
|
|
69136
69127
|
async execute(options, context4) {
|
|
69137
69128
|
const input = asOptionalString(options.input);
|
|
69138
69129
|
const agent = asOptionalString(options.agent);
|
|
69139
|
-
const
|
|
69140
|
-
const dispatchAgent = agent === "inline" ? cfg?.agent?.default ?? undefined : agent;
|
|
69130
|
+
const dispatchAgent = agent === "inline" ? this.agentConfig.default ?? undefined : agent;
|
|
69141
69131
|
const model = asOptionalString(options.model);
|
|
69142
69132
|
const mode = asOptionalString(options.mode) ?? "text";
|
|
69143
69133
|
const cwd = asOptionalString(options.cwd) ?? context4.workdir ?? ".";
|
|
69144
69134
|
const sessionAffinityVar = context4.vars.sessionAffinity;
|
|
69145
|
-
const affinityDisabled = sessionAffinityVar === "false" || typeof sessionAffinityVar === "boolean" && !sessionAffinityVar || options.sessionAffinity === false ||
|
|
69135
|
+
const affinityDisabled = sessionAffinityVar === "false" || typeof sessionAffinityVar === "boolean" && !sessionAffinityVar || options.sessionAffinity === false || this.agentConfig.sessionAffinity === false;
|
|
69146
69136
|
const affinityOn = !affinityDisabled;
|
|
69147
69137
|
const agentLabel = dispatchAgent ?? "<default>";
|
|
69148
|
-
const targetAgentDir = dispatchAgent ??
|
|
69138
|
+
const targetAgentDir = dispatchAgent ?? this.agentConfig.default ?? "omp";
|
|
69149
69139
|
const prevAgent = asOptionalString(context4.vars.__agentSessionAgent);
|
|
69150
69140
|
let sessionDir = asOptionalString(context4.vars.__agentSessionDir);
|
|
69151
69141
|
if (affinityOn) {
|
|
69152
69142
|
if (!sessionDir || prevAgent && prevAgent !== targetAgentDir) {
|
|
69153
69143
|
sessionDir = join15(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir);
|
|
69154
69144
|
}
|
|
69145
|
+
} else {
|
|
69146
|
+
if (!sessionDir) {
|
|
69147
|
+
sessionDir = join15(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir);
|
|
69148
|
+
}
|
|
69155
69149
|
}
|
|
69156
69150
|
const storedSessionId = asOptionalString(context4.vars.__agentSessionId);
|
|
69157
69151
|
let continueFlag = asOptionalBoolean(options.continue);
|
|
69158
69152
|
const latch = context4.vars.__agentSession;
|
|
69159
69153
|
const latchAutoContinued = continueFlag === undefined && latch === "open";
|
|
69160
69154
|
if (latchAutoContinued) {
|
|
69161
|
-
if (affinityOn) {
|
|
69155
|
+
if (!affinityOn) {
|
|
69162
69156
|
continueFlag = true;
|
|
69163
|
-
} else {
|
|
69164
|
-
continueFlag = undefined;
|
|
69165
69157
|
}
|
|
69166
69158
|
}
|
|
69167
69159
|
if (input === undefined && !continueFlag) {
|
|
@@ -69178,9 +69170,9 @@ class AgentRunActionRunner {
|
|
|
69178
69170
|
flags.mode = mode;
|
|
69179
69171
|
if (cwd !== "")
|
|
69180
69172
|
flags.cwd = cwd;
|
|
69181
|
-
if (
|
|
69173
|
+
if (sessionDir) {
|
|
69182
69174
|
flags.sessionDir = sessionDir;
|
|
69183
|
-
if (storedSessionId) {
|
|
69175
|
+
if (affinityOn && storedSessionId) {
|
|
69184
69176
|
flags.sessionId = storedSessionId;
|
|
69185
69177
|
}
|
|
69186
69178
|
}
|
|
@@ -69269,11 +69261,11 @@ class AgentRunActionRunner {
|
|
|
69269
69261
|
}
|
|
69270
69262
|
}
|
|
69271
69263
|
const stepLabel = context4.stateOrNodeId;
|
|
69272
|
-
if (ok && requireDiff === true && !await gitHasNonCorpusChanges(cwd)) {
|
|
69264
|
+
if (ok && requireDiff === true && !await gitHasNonCorpusChanges(cwd, this.agentConfig.excludeGlobs)) {
|
|
69273
69265
|
return {
|
|
69274
69266
|
ok: false,
|
|
69275
69267
|
data: buildResultData(exitCode, agentLabel, capture, answer, invocation),
|
|
69276
|
-
error: `agent.run '${stepLabel}' (${agentLabel}) exited 0 but produced zero non-corpus file changes \u2014 empty implement (no-op). The implement agent must change at least one file outside
|
|
69268
|
+
error: `agent.run '${stepLabel}' (${agentLabel}) exited 0 but produced zero non-corpus file changes \u2014 empty implement (no-op). The implement agent must change at least one file outside the configured task/feature folders; fix the implement input and re-run the pipeline.`
|
|
69277
69269
|
};
|
|
69278
69270
|
}
|
|
69279
69271
|
if (!ok) {
|
|
@@ -69281,19 +69273,24 @@ class AgentRunActionRunner {
|
|
|
69281
69273
|
}
|
|
69282
69274
|
const partialWorkHint = `partial work (if any) preserved at .spur/run/${context4.runId}-${stepLabel}-partial.md; resume from that tree per the timed-out-implement runbook (plugins/sp/skills/spur-dev/references/execution-workflow.md)`;
|
|
69283
69275
|
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); ${partialWorkHint}` : `agent.run '${stepLabel}' (${agentLabel}) was cancelled by signal ${traced.signal}; ${partialWorkHint}` : traced.message !== undefined ? `agent.run '${stepLabel}' (${agentLabel}) dispatch failed: ${traced.message}` : `agent.run '${stepLabel}' (${agentLabel}) exited with code ${exitCode}; ${partialWorkHint}`;
|
|
69276
|
+
const resolvedAgent = invocation?.agent ?? targetAgentDir;
|
|
69277
|
+
let resolvedSessionDir = sessionDir;
|
|
69278
|
+
if (ok && affinityOn && resolvedAgent !== targetAgentDir && !context4.vars.__agentSessionDir) {
|
|
69279
|
+
resolvedSessionDir = join15(cwd, ".spur", "run", context4.runId, "agent-sessions", resolvedAgent);
|
|
69280
|
+
}
|
|
69284
69281
|
let discoveredSessionId = storedSessionId;
|
|
69285
|
-
if (ok && affinityOn &&
|
|
69286
|
-
discoveredSessionId = await discoverSessionId(
|
|
69282
|
+
if (ok && affinityOn && resolvedSessionDir && !discoveredSessionId) {
|
|
69283
|
+
discoveredSessionId = await discoverSessionId(resolvedSessionDir);
|
|
69287
69284
|
}
|
|
69288
|
-
if (ok && affinityOn &&
|
|
69285
|
+
if (ok && affinityOn && resolvedSessionDir) {
|
|
69289
69286
|
try {
|
|
69290
69287
|
const sidecarPath = join15(cwd, ".spur", "run", `${context4.runId}-agent-session.json`);
|
|
69291
69288
|
const fs3 = createNodeFileSystem3(cwd);
|
|
69292
69289
|
await fs3.ensureDir(dirname12(sidecarPath));
|
|
69293
69290
|
await fs3.writeFile(sidecarPath, JSON.stringify({
|
|
69294
|
-
agent:
|
|
69291
|
+
agent: resolvedAgent,
|
|
69295
69292
|
sessionId: discoveredSessionId ?? null,
|
|
69296
|
-
sessionDir,
|
|
69293
|
+
sessionDir: resolvedSessionDir,
|
|
69297
69294
|
openedAt: new Date().toISOString()
|
|
69298
69295
|
}, null, 2));
|
|
69299
69296
|
} catch {}
|
|
@@ -69304,9 +69301,9 @@ class AgentRunActionRunner {
|
|
|
69304
69301
|
error: error51,
|
|
69305
69302
|
setVars: ok ? {
|
|
69306
69303
|
__agentSession: resumeRetried ? "no-resume" : "open",
|
|
69307
|
-
...affinityOn &&
|
|
69304
|
+
...affinityOn && resolvedSessionDir ? { __agentSessionDir: resolvedSessionDir } : {},
|
|
69308
69305
|
...affinityOn && (discoveredSessionId || storedSessionId) ? { __agentSessionId: discoveredSessionId || storedSessionId } : {},
|
|
69309
|
-
...affinityOn ? { __agentSessionAgent:
|
|
69306
|
+
...affinityOn ? { __agentSessionAgent: resolvedAgent } : {},
|
|
69310
69307
|
...steeringNote !== undefined ? { __steeringNote: steeringNote } : {}
|
|
69311
69308
|
} : undefined
|
|
69312
69309
|
};
|
|
@@ -69321,9 +69318,12 @@ async function discoverSessionId(sessionDir) {
|
|
|
69321
69318
|
const entries = await fs3.readDir(sessionDir);
|
|
69322
69319
|
if (entries.length === 0)
|
|
69323
69320
|
return;
|
|
69321
|
+
const jsonEntries = entries.filter((e) => e.toLowerCase().endsWith(".json"));
|
|
69322
|
+
if (jsonEntries.length === 0)
|
|
69323
|
+
return;
|
|
69324
69324
|
let newestFile;
|
|
69325
69325
|
let newestMtime = 0;
|
|
69326
|
-
for (const entry of
|
|
69326
|
+
for (const entry of jsonEntries) {
|
|
69327
69327
|
const fullPath = join15(sessionDir, entry);
|
|
69328
69328
|
const st = await fs3.stat(fullPath);
|
|
69329
69329
|
if (st?.isFile() && st.mtimeMs > newestMtime) {
|
|
@@ -69442,11 +69442,12 @@ async function gitDiffStat(cwd) {
|
|
|
69442
69442
|
return "";
|
|
69443
69443
|
}
|
|
69444
69444
|
}
|
|
69445
|
-
async function gitHasNonCorpusChanges(cwd) {
|
|
69445
|
+
async function gitHasNonCorpusChanges(cwd, excludeGlobs = ["docs/tasks3/*", "docs/features/*"]) {
|
|
69446
69446
|
try {
|
|
69447
|
+
const excludes = excludeGlobs.map((g) => `:(exclude)${g}`);
|
|
69447
69448
|
const result = await new NodeProcessExecutor3().run({
|
|
69448
69449
|
command: "git",
|
|
69449
|
-
args: ["status", "--porcelain", "--", ".",
|
|
69450
|
+
args: ["status", "--porcelain", "--", ".", ...excludes],
|
|
69450
69451
|
cwd,
|
|
69451
69452
|
maxOutput: 1024 * 1024,
|
|
69452
69453
|
forceBuffered: true,
|
|
@@ -70161,7 +70162,7 @@ var KIND12 = "shell";
|
|
|
70161
70162
|
// ../../packages/app/src/workflow/builtins.ts
|
|
70162
70163
|
function registerSpurBuiltins(host, options) {
|
|
70163
70164
|
const fileSystem = options.fileSystem ?? createNodeFileSystem3();
|
|
70164
|
-
host.registerAction(new AgentRunActionRunner(options.agentService, options.observabilityBus, options.steeringController), "builtin");
|
|
70165
|
+
host.registerAction(new AgentRunActionRunner(options.agentService, options.observabilityBus, options.steeringController, options.agentConfig), "builtin");
|
|
70165
70166
|
host.registerAction(new StreamingShellActionRunner(options.processExecutor ?? new NodeProcessExecutor3, options.observabilityBus), "builtin");
|
|
70166
70167
|
host.registerGuard(new EnvShellGuardRunner(options.processExecutor ?? new NodeProcessExecutor3), "builtin");
|
|
70167
70168
|
host.registerAction(new RuleCheckActionRunner(options.ruleService), "builtin");
|
|
@@ -70271,6 +70272,30 @@ class WorkflowAppService {
|
|
|
70271
70272
|
validateSchema: opts.validateSchema !== false,
|
|
70272
70273
|
...embedded !== undefined ? embedded : {}
|
|
70273
70274
|
});
|
|
70275
|
+
const shellErrors = [];
|
|
70276
|
+
const commands = collectShellCommands(workflow);
|
|
70277
|
+
const executor = new NodeProcessExecutor3;
|
|
70278
|
+
for (const { stateId, kind, index: index2, command } of commands) {
|
|
70279
|
+
if (!command || command.trim().length === 0)
|
|
70280
|
+
continue;
|
|
70281
|
+
try {
|
|
70282
|
+
const result = await executor.run({
|
|
70283
|
+
command: "sh",
|
|
70284
|
+
args: ["-n", "-c", command],
|
|
70285
|
+
rejectOnError: false
|
|
70286
|
+
});
|
|
70287
|
+
if (result.exitCode !== 0) {
|
|
70288
|
+
const stderr = (result.stderr ?? "").trim();
|
|
70289
|
+
const location = stateId ? `${stateId}/${kind}[${index2}]` : `${kind}[${index2}]`;
|
|
70290
|
+
shellErrors.push(`Shell syntax error at ${location}: ${stderr || "non-zero exit"}`);
|
|
70291
|
+
}
|
|
70292
|
+
} catch {
|
|
70293
|
+
shellErrors.push(`Shell syntax check failed at ${stateId}/${kind}[${index2}]: process error`);
|
|
70294
|
+
}
|
|
70295
|
+
}
|
|
70296
|
+
if (shellErrors.length > 0) {
|
|
70297
|
+
return { ok: false, valid: false, file: file2, errors: shellErrors };
|
|
70298
|
+
}
|
|
70274
70299
|
return { ok: true, valid: true, workflow };
|
|
70275
70300
|
} catch (error51) {
|
|
70276
70301
|
return {
|
|
@@ -70573,6 +70598,23 @@ class WorkflowAppService {
|
|
|
70573
70598
|
const processExec = this.ctx.processExecutor?.();
|
|
70574
70599
|
const host = createDefaultWorkflowEngineHost(processExec !== undefined ? { processExecutor: processExec } : {});
|
|
70575
70600
|
const bus = this.ctx.observabilityBus?.();
|
|
70601
|
+
let agentSlice = {};
|
|
70602
|
+
try {
|
|
70603
|
+
const agent = (await loadSpurConfig(this.ctx.cwd)).agent;
|
|
70604
|
+
agentSlice = {
|
|
70605
|
+
...agent?.default !== undefined ? { default: agent.default } : {},
|
|
70606
|
+
...agent?.sessionAffinity !== undefined ? { sessionAffinity: agent.sessionAffinity } : {}
|
|
70607
|
+
};
|
|
70608
|
+
} catch {}
|
|
70609
|
+
try {
|
|
70610
|
+
const fs3 = createNodeFileSystem3(this.ctx.cwd);
|
|
70611
|
+
const { foldersConfig, featuresDir } = await resolvePlanningFolders(fs3);
|
|
70612
|
+
const folderGlobs = Object.keys(foldersConfig.folders).map((k) => `${k}/*`);
|
|
70613
|
+
agentSlice = {
|
|
70614
|
+
...agentSlice,
|
|
70615
|
+
excludeGlobs: [...folderGlobs, `${featuresDir}/*`]
|
|
70616
|
+
};
|
|
70617
|
+
} catch {}
|
|
70576
70618
|
registerSpurBuiltins(host, {
|
|
70577
70619
|
agentService: this.ctx.agentService(),
|
|
70578
70620
|
ruleService: this.ctx.ruleService(),
|
|
@@ -70581,7 +70623,8 @@ class WorkflowAppService {
|
|
|
70581
70623
|
hostAllowlist: this.ctx.hostAllowlist?.(),
|
|
70582
70624
|
...bus !== undefined ? { observabilityBus: bus } : {},
|
|
70583
70625
|
...processExec !== undefined ? { processExecutor: processExec } : {},
|
|
70584
|
-
...opts.steeringController !== undefined ? { steeringController: opts.steeringController } : {}
|
|
70626
|
+
...opts.steeringController !== undefined ? { steeringController: opts.steeringController } : {},
|
|
70627
|
+
agentConfig: agentSlice
|
|
70585
70628
|
});
|
|
70586
70629
|
const db2 = await this.ctx.getDb();
|
|
70587
70630
|
let persistence2 = new DbWorkflowPersistenceAdapter(db2);
|
|
@@ -70592,6 +70635,55 @@ class WorkflowAppService {
|
|
|
70592
70635
|
return new WorkflowService(host, adapter);
|
|
70593
70636
|
}
|
|
70594
70637
|
}
|
|
70638
|
+
function collectShellCommands(def) {
|
|
70639
|
+
const entries = [];
|
|
70640
|
+
const visitAction = (stateId, action, idx) => {
|
|
70641
|
+
if (action.kind === "shell") {
|
|
70642
|
+
const cmd = action.options?.command;
|
|
70643
|
+
if (typeof cmd === "string" && cmd.length > 0) {
|
|
70644
|
+
entries.push({ stateId, kind: "action", index: idx, command: cmd });
|
|
70645
|
+
}
|
|
70646
|
+
}
|
|
70647
|
+
};
|
|
70648
|
+
const visitGuard = (stateId, guard) => {
|
|
70649
|
+
if (guard.kind === "shell") {
|
|
70650
|
+
const cmd = guard.options?.command;
|
|
70651
|
+
if (typeof cmd === "string" && cmd.length > 0) {
|
|
70652
|
+
entries.push({ stateId, kind: "guard", index: 0, command: cmd });
|
|
70653
|
+
}
|
|
70654
|
+
}
|
|
70655
|
+
};
|
|
70656
|
+
if (def.kind === "transition-flow" || def.kind === undefined) {
|
|
70657
|
+
const flowDef = def;
|
|
70658
|
+
for (const node of flowDef.nodes ?? []) {
|
|
70659
|
+
if (node.action)
|
|
70660
|
+
visitAction(node.id, node.action, 0);
|
|
70661
|
+
}
|
|
70662
|
+
for (const edge of flowDef.edges ?? []) {
|
|
70663
|
+
if (edge.condition)
|
|
70664
|
+
visitGuard(edge.from, edge.condition);
|
|
70665
|
+
}
|
|
70666
|
+
} else {
|
|
70667
|
+
const smDef = def;
|
|
70668
|
+
for (const state of smDef.states ?? []) {
|
|
70669
|
+
if (state.onEnter) {
|
|
70670
|
+
state.onEnter.forEach((action, i2) => {
|
|
70671
|
+
visitAction(state.id, action, i2);
|
|
70672
|
+
});
|
|
70673
|
+
}
|
|
70674
|
+
if (state.onExit) {
|
|
70675
|
+
state.onExit.forEach((action, i2) => {
|
|
70676
|
+
visitAction(state.id, action, i2);
|
|
70677
|
+
});
|
|
70678
|
+
}
|
|
70679
|
+
}
|
|
70680
|
+
for (const trans of smDef.transitions ?? []) {
|
|
70681
|
+
if (trans.guard)
|
|
70682
|
+
visitGuard(`${trans.from}\u2192${trans.to}`, trans.guard);
|
|
70683
|
+
}
|
|
70684
|
+
}
|
|
70685
|
+
return entries;
|
|
70686
|
+
}
|
|
70595
70687
|
async function fileExists(path9) {
|
|
70596
70688
|
const fs3 = createNodeFileSystem3();
|
|
70597
70689
|
return await fs3.exists(path9);
|
|
@@ -78982,6 +79074,9 @@ function registerFeatureCommand(program2, context4) {
|
|
|
78982
79074
|
const resolved = await resolvePlanningFolders(context4.fs);
|
|
78983
79075
|
const featuresDir = options.folder ?? context4.fs.resolve(resolved.featuresDir);
|
|
78984
79076
|
const tasksDir = context4.fs.resolve(resolved.tasksDir);
|
|
79077
|
+
const tasksDirs = Object.keys(resolved.foldersConfig.folders).map((p) => context4.fs.resolve(p));
|
|
79078
|
+
if (!tasksDirs.includes(tasksDir))
|
|
79079
|
+
tasksDirs.unshift(tasksDir);
|
|
78985
79080
|
const svc = new FeatureCheckService(context4.fs);
|
|
78986
79081
|
const json3 = options.json === true;
|
|
78987
79082
|
const strict = options.strict === true;
|
|
@@ -79000,6 +79095,7 @@ function registerFeatureCommand(program2, context4) {
|
|
|
79000
79095
|
strict,
|
|
79001
79096
|
featuresDir,
|
|
79002
79097
|
tasksDir,
|
|
79098
|
+
tasksDirs,
|
|
79003
79099
|
runDir: context4.fs.resolve(".spur/run"),
|
|
79004
79100
|
severityOverrides: resolved.severityOverrides,
|
|
79005
79101
|
asStatus: options.as
|
|
@@ -79095,6 +79191,9 @@ async function assertFeatureCheckPass(context4, id, folderOverride, strict, asSt
|
|
|
79095
79191
|
const resolved = await resolvePlanningFolders(context4.fs);
|
|
79096
79192
|
const featuresDir = folderOverride ?? context4.fs.resolve(resolved.featuresDir);
|
|
79097
79193
|
const tasksDir = context4.fs.resolve(resolved.tasksDir);
|
|
79194
|
+
const tasksDirs = Object.keys(resolved.foldersConfig.folders).map((p) => context4.fs.resolve(p));
|
|
79195
|
+
if (!tasksDirs.includes(tasksDir))
|
|
79196
|
+
tasksDirs.unshift(tasksDir);
|
|
79098
79197
|
const entries = await context4.fs.readDir(featuresDir);
|
|
79099
79198
|
const fileName = entries.find((name) => name.match(new RegExp(`^${id}_.+\\.md$`)));
|
|
79100
79199
|
if (fileName === undefined) {
|
|
@@ -79104,6 +79203,7 @@ async function assertFeatureCheckPass(context4, id, folderOverride, strict, asSt
|
|
|
79104
79203
|
strict,
|
|
79105
79204
|
featuresDir,
|
|
79106
79205
|
tasksDir,
|
|
79206
|
+
tasksDirs,
|
|
79107
79207
|
asStatus
|
|
79108
79208
|
});
|
|
79109
79209
|
if (!result.pass) {
|
|
@@ -79134,9 +79234,13 @@ function registerHistoryCommand(program2, context4) {
|
|
|
79134
79234
|
const summary = await svc.analyze(options.since || undefined);
|
|
79135
79235
|
context4.output.write(options.json ? toJson2(summary) : formatSummary(summary));
|
|
79136
79236
|
});
|
|
79137
|
-
noun.command("report").description("Reserved richer report surface;
|
|
79138
|
-
const message =
|
|
79139
|
-
|
|
79237
|
+
noun.command("report").description("Reserved richer report surface; not yet implemented (see docs/04_DESIGN.md \xA7spur history report).").option("--json", "Output machine-readable JSON where supported").action(async (options) => {
|
|
79238
|
+
const message = `spur history report is reserved but not yet implemented (P8).
|
|
79239
|
+
` + `See docs/04_DESIGN.md for the planned surface.
|
|
79240
|
+
` + `Workaround: use "spur history analyze" for aggregate summaries, or
|
|
79241
|
+
` + '"spur history import" to load session data, then query the database directly.';
|
|
79242
|
+
context4.output.write(options.json ? toJson2({ status: "not-implemented", message }) : message);
|
|
79243
|
+
context4.setExitCode(0);
|
|
79140
79244
|
});
|
|
79141
79245
|
}
|
|
79142
79246
|
function formatImportResult(r) {
|
|
@@ -79164,7 +79268,7 @@ import { join as join19, resolve as resolve6 } from "path";
|
|
|
79164
79268
|
var CLI_CONFIG = {
|
|
79165
79269
|
binaryName: "spur",
|
|
79166
79270
|
binaryLabel: "spur",
|
|
79167
|
-
binaryVersion: "0.3.
|
|
79271
|
+
binaryVersion: "0.3.34",
|
|
79168
79272
|
configDir: ".spur",
|
|
79169
79273
|
configFile: ".spur/config.yaml",
|
|
79170
79274
|
databaseFile: ".spur/spur.db"
|
|
@@ -86131,7 +86235,9 @@ function createFeatureHandlers(ctx) {
|
|
|
86131
86235
|
}),
|
|
86132
86236
|
sync: os2.feature.sync.handler(async ({ input }) => {
|
|
86133
86237
|
if (input.direction === "push") {
|
|
86134
|
-
throw new
|
|
86238
|
+
throw new HTTPException(501, {
|
|
86239
|
+
message: "Push sync (feature->tasks cascade) is not implemented; use the pull direction or spur feature sync CLI"
|
|
86240
|
+
});
|
|
86135
86241
|
}
|
|
86136
86242
|
const svc = ctx.featureService();
|
|
86137
86243
|
const res = await svc.syncFeature(input.id);
|
|
@@ -89835,7 +89941,7 @@ var spur_config_schema_default = {
|
|
|
89835
89941
|
},
|
|
89836
89942
|
executors: {
|
|
89837
89943
|
type: "array",
|
|
89838
|
-
description: "Named executor profiles: each pairs a canonical agent with an optional model override. Referenced by name from `default` and
|
|
89944
|
+
description: "Named executor profiles: each pairs a canonical agent with an optional model override. Referenced by name from `agent.default` and stage model_policy (default-by-phase removed 0452).",
|
|
89839
89945
|
items: {
|
|
89840
89946
|
type: "object",
|
|
89841
89947
|
required: ["name", "agent"],
|
|
@@ -89863,13 +89969,6 @@ var spur_config_schema_default = {
|
|
|
89863
89969
|
}
|
|
89864
89970
|
}
|
|
89865
89971
|
},
|
|
89866
|
-
"default-by-phase": {
|
|
89867
|
-
type: "object",
|
|
89868
|
-
description: "DEPRECATED (ADR-033). Backward-compat map of dev phase (e.g. dev-run, dev-review) to executor selector. Prefer executor `tier` + stage-registry model_policy. When present, a matching phase mapping is still authoritative (fail-fast; no fall back to `default`) and shadows stage routing for that phase.",
|
|
89869
|
-
additionalProperties: {
|
|
89870
|
-
type: "string"
|
|
89871
|
-
}
|
|
89872
|
-
},
|
|
89873
89972
|
team: {
|
|
89874
89973
|
type: "object",
|
|
89875
89974
|
description: "Declarative agent teams keyed by team id (the map key is the teamId). Each team materializes a roster of agent specs (feature M). Mirrors @gobing-ai/spur-config TeamConfigSchema (Zod is SSOT).",
|
|
@@ -89897,7 +89996,10 @@ var spur_config_schema_default = {
|
|
|
89897
89996
|
description: "Roster of member references (>= 1). A bare string is shorthand for { executor: <string> }; the composed agent id is <teamId>-<id ?? executor>.",
|
|
89898
89997
|
items: {
|
|
89899
89998
|
oneOf: [
|
|
89900
|
-
{
|
|
89999
|
+
{
|
|
90000
|
+
type: "string",
|
|
90001
|
+
minLength: 1
|
|
90002
|
+
},
|
|
89901
90003
|
{
|
|
89902
90004
|
type: "object",
|
|
89903
90005
|
required: ["executor"],
|
|
@@ -89912,20 +90014,34 @@ var spur_config_schema_default = {
|
|
|
89912
90014
|
minLength: 1,
|
|
89913
90015
|
description: "Local member id; composed id is <teamId>-<id ?? executor> (must match ^[a-z][a-z0-9_-]{1,63}$)."
|
|
89914
90016
|
},
|
|
89915
|
-
purpose: {
|
|
90017
|
+
purpose: {
|
|
90018
|
+
type: "string"
|
|
90019
|
+
},
|
|
89916
90020
|
workspace: {
|
|
89917
90021
|
type: "string",
|
|
89918
90022
|
minLength: 1,
|
|
89919
90023
|
description: "Per-member workspace override. A leading `~` is tilde-expanded at load."
|
|
89920
90024
|
},
|
|
89921
|
-
model: {
|
|
89922
|
-
|
|
89923
|
-
|
|
90025
|
+
model: {
|
|
90026
|
+
type: "string",
|
|
90027
|
+
minLength: 1
|
|
90028
|
+
},
|
|
90029
|
+
autonomy: {
|
|
90030
|
+
type: "string"
|
|
90031
|
+
},
|
|
90032
|
+
systemPrompt: {
|
|
90033
|
+
type: "string"
|
|
90034
|
+
},
|
|
89924
90035
|
command: {
|
|
89925
90036
|
type: "array",
|
|
89926
|
-
items: {
|
|
90037
|
+
items: {
|
|
90038
|
+
type: "string",
|
|
90039
|
+
minLength: 1
|
|
90040
|
+
}
|
|
89927
90041
|
},
|
|
89928
|
-
autostart: {
|
|
90042
|
+
autostart: {
|
|
90043
|
+
type: "boolean"
|
|
90044
|
+
}
|
|
89929
90045
|
}
|
|
89930
90046
|
}
|
|
89931
90047
|
]
|
|
@@ -89941,7 +90057,9 @@ var spur_config_schema_default = {
|
|
|
89941
90057
|
properties: {
|
|
89942
90058
|
paths: {
|
|
89943
90059
|
type: "array",
|
|
89944
|
-
items: {
|
|
90060
|
+
items: {
|
|
90061
|
+
type: "string"
|
|
90062
|
+
},
|
|
89945
90063
|
description: "Glob paths to rule files."
|
|
89946
90064
|
}
|
|
89947
90065
|
}
|
|
@@ -89951,7 +90069,9 @@ var spur_config_schema_default = {
|
|
|
89951
90069
|
properties: {
|
|
89952
90070
|
paths: {
|
|
89953
90071
|
type: "array",
|
|
89954
|
-
items: {
|
|
90072
|
+
items: {
|
|
90073
|
+
type: "string"
|
|
90074
|
+
},
|
|
89955
90075
|
description: "Glob paths to workflow directories."
|
|
89956
90076
|
}
|
|
89957
90077
|
}
|
package/web/index.html
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
<!DOCTYPE html><html lang="en"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Spur Board</title><script type="module">(function(){var e=localStorage.getItem("spur-theme"),t=e||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light");document.documentElement.setAttribute("data-theme",t)})();</script><link rel="stylesheet" href="/_astro/index.yAse9IaO.css">
|
|
2
|
-
<link rel="stylesheet" href="/_astro/BoardApp.t7x-dYDo.css"></head> <body> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event("astro:only"));})();</script><script>(()=>{var g=Object.defineProperty;var w=(c,s,d)=>s in c?g(c,s,{enumerable:!0,configurable:!0,writable:!0,value:d}):c[s]=d;var l=(c,s,d)=>w(c,typeof s!="symbol"?s+"":s,d);var E=new Set(["__proto__","constructor","prototype"]);{let c={0:t=>y(t),1:t=>d(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(d(t)),5:t=>new Set(d(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>Number.POSITIVE_INFINITY*t},s=t=>{let[p,e]=t;return p in c?c[p](e):void 0},d=t=>t.map(s),y=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([p,e])=>[p,s(e)]));class f extends HTMLElement{constructor(){super(...arguments);l(this,"Component");l(this,"hydrator");l(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let n=this.querySelectorAll("astro-slot"),r={},i=this.querySelectorAll("template[data-astro-template]");for(let o of i){let a=o.closest(this.tagName);a!=null&&a.isSameNode(this)&&(r[o.getAttribute("data-astro-template")||"default"]=o.innerHTML,o.remove())}for(let o of n){let a=o.closest(this.tagName);a!=null&&a.isSameNode(this)&&(r[o.getAttribute("name")||"default"]=o.innerHTML)}let u;try{u=this.hasAttribute("props")?y(JSON.parse(this.getAttribute("props"))):{}}catch(o){let a=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(a+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${a}`,this.getAttribute("props"),o),o}let h;await this.hydrator(this)(this.Component,u,r,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});l(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),n.disconnect(),this.childrenConnectedCallback()},n=new MutationObserver(()=>{var r;((r=this.lastChild)==null?void 0:r.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});n.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}getRetryImportUrl(e){let n=new URL(e,document.baseURI),r=`astro-retry=${Date.now()}`,i=n.hash.replace(/^#/,"");return n.hash=i?`${i}&${r}`:r,n.toString()}async importWithRetry(e){try{return await import(e)}catch(n){return await new Promise(r=>setTimeout(r,1e3)),import(this.getRetryImportUrl(e))}}handleHydrationError(e){let n=this.getAttribute("component-url"),r=new CustomEvent("astro:hydration-error",{cancelable:!0,bubbles:!0,composed:!0,detail:{error:e,componentUrl:n}});this.dispatchEvent(r)&&console.error(`[astro-island] Error hydrating ${n}`,e)}async start(){let e=JSON.parse(this.getAttribute("opts")),n=this.getAttribute("client");if(Astro[n]===void 0){window.addEventListener(`astro:${n}`,()=>this.start(),{once:!0});return}try{await Astro[n](async()=>{let r=this.getAttribute("renderer-url");try{let[i,{default:u}]=await Promise.all([this.importWithRetry(this.getAttribute("component-url")),r?this.importWithRetry(r):Promise.resolve({default:()=>()=>{}})]),h=this.getAttribute("component-export")||"default";if(h.includes(".")){this.Component=i;for(let m of h.split(".")){if(E.has(m)||!this.Component||typeof this.Component!="object"&&typeof this.Component!="function"||!Object.hasOwn(this.Component,m))throw new Error(`Invalid component export path: ${h}`);this.Component=this.Component[m]}}else{if(E.has(h))throw new Error(`Invalid component export path: ${h}`);this.Component=i[h]}return this.hydrator=u,this.hydrate}catch(i){return this.handleHydrationError(i),()=>{}}},e,this)}catch(r){this.handleHydrationError(r)}}attributeChangedCallback(){this.hydrate()}}l(f,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",f)}})();</script><astro-island uid="
|
|
2
|
+
<link rel="stylesheet" href="/_astro/BoardApp.t7x-dYDo.css"></head> <body> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event("astro:only"));})();</script><script>(()=>{var g=Object.defineProperty;var w=(c,s,d)=>s in c?g(c,s,{enumerable:!0,configurable:!0,writable:!0,value:d}):c[s]=d;var l=(c,s,d)=>w(c,typeof s!="symbol"?s+"":s,d);var E=new Set(["__proto__","constructor","prototype"]);{let c={0:t=>y(t),1:t=>d(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(d(t)),5:t=>new Set(d(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>Number.POSITIVE_INFINITY*t},s=t=>{let[p,e]=t;return p in c?c[p](e):void 0},d=t=>t.map(s),y=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([p,e])=>[p,s(e)]));class f extends HTMLElement{constructor(){super(...arguments);l(this,"Component");l(this,"hydrator");l(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let n=this.querySelectorAll("astro-slot"),r={},i=this.querySelectorAll("template[data-astro-template]");for(let o of i){let a=o.closest(this.tagName);a!=null&&a.isSameNode(this)&&(r[o.getAttribute("data-astro-template")||"default"]=o.innerHTML,o.remove())}for(let o of n){let a=o.closest(this.tagName);a!=null&&a.isSameNode(this)&&(r[o.getAttribute("name")||"default"]=o.innerHTML)}let u;try{u=this.hasAttribute("props")?y(JSON.parse(this.getAttribute("props"))):{}}catch(o){let a=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(a+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${a}`,this.getAttribute("props"),o),o}let h;await this.hydrator(this)(this.Component,u,r,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});l(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),n.disconnect(),this.childrenConnectedCallback()},n=new MutationObserver(()=>{var r;((r=this.lastChild)==null?void 0:r.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});n.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}getRetryImportUrl(e){let n=new URL(e,document.baseURI),r=`astro-retry=${Date.now()}`,i=n.hash.replace(/^#/,"");return n.hash=i?`${i}&${r}`:r,n.toString()}async importWithRetry(e){try{return await import(e)}catch(n){return await new Promise(r=>setTimeout(r,1e3)),import(this.getRetryImportUrl(e))}}handleHydrationError(e){let n=this.getAttribute("component-url"),r=new CustomEvent("astro:hydration-error",{cancelable:!0,bubbles:!0,composed:!0,detail:{error:e,componentUrl:n}});this.dispatchEvent(r)&&console.error(`[astro-island] Error hydrating ${n}`,e)}async start(){let e=JSON.parse(this.getAttribute("opts")),n=this.getAttribute("client");if(Astro[n]===void 0){window.addEventListener(`astro:${n}`,()=>this.start(),{once:!0});return}try{await Astro[n](async()=>{let r=this.getAttribute("renderer-url");try{let[i,{default:u}]=await Promise.all([this.importWithRetry(this.getAttribute("component-url")),r?this.importWithRetry(r):Promise.resolve({default:()=>()=>{}})]),h=this.getAttribute("component-export")||"default";if(h.includes(".")){this.Component=i;for(let m of h.split(".")){if(E.has(m)||!this.Component||typeof this.Component!="object"&&typeof this.Component!="function"||!Object.hasOwn(this.Component,m))throw new Error(`Invalid component export path: ${h}`);this.Component=this.Component[m]}}else{if(E.has(h))throw new Error(`Invalid component export path: ${h}`);this.Component=i[h]}return this.hydrator=u,this.hydrate}catch(i){return this.handleHydrationError(i),()=>{}}},e,this)}catch(r){this.handleHydrationError(r)}}attributeChangedCallback(){this.hydrate()}}l(f,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",f)}})();</script><astro-island uid="Z1fJt2d" component-url="/_astro/BoardApp.CBVEjvSW.js" component-export="default" renderer-url="/_astro/client.CdFpTatq.js" props="{}" ssr client="only" opts="{"name":"BoardApp","value":"react"}"></astro-island> </body></html>
|