dsh-pstack 0.2.0-beta.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +35 -16
  2. package/README.zh.md +36 -17
  3. package/assets/pstack-config-entry.png +0 -0
  4. package/cordis.patch.yml +12 -0
  5. package/dist/client.mjs +2448 -0
  6. package/dist/client.mjs.map +1 -0
  7. package/dist/index.mjs +1 -1
  8. package/dist/{paths-BT77khwm.mjs → paths-yji9SNak.mjs} +0 -1
  9. package/dist/pstack/docs/harness/dsh.md +7 -7
  10. package/dist/remote.d.mts +27 -0
  11. package/dist/remote.mjs +235 -0
  12. package/dist/role-catalog.d.mts +35 -0
  13. package/dist/role-catalog.mjs +122 -0
  14. package/dist/role-config-a4JpVdc-.mjs +379 -0
  15. package/dist/role-manifest-79cnFfDM.mjs +112 -0
  16. package/dist/role-manifest-8OERX9-s.d.mts +90 -0
  17. package/dist/role-wire-CAJsVA6e.d.mts +54 -0
  18. package/dist/roles.d.mts +18 -0
  19. package/dist/roles.mjs +293 -0
  20. package/dist/script.mjs +1 -1
  21. package/dist/skills/authored/decision-audit/SKILL.md +21 -17
  22. package/dist/skills/authored/decision-audit/SKILL.zh.md +21 -17
  23. package/dist/skills/authored/decision-audit/references/recover-commit.md +3 -3
  24. package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +3 -3
  25. package/dist/skills/authored/decision-write/SKILL.md +11 -9
  26. package/dist/skills/authored/decision-write/SKILL.zh.md +11 -9
  27. package/dist/skills/authored/decision-write/references/land.md +3 -3
  28. package/dist/skills/authored/decision-write/references/land.zh.md +3 -3
  29. package/dist/skills/authored/decision-write/references/scope.md +16 -0
  30. package/dist/skills/authored/decision-write/references/scope.zh.md +16 -0
  31. package/dist/skills/authored/docs-drift/SKILL.md +120 -0
  32. package/dist/skills/authored/docs-drift/SKILL.zh.md +120 -0
  33. package/dist/skills/authored/setup-project-docs/SKILL.md +14 -9
  34. package/dist/skills/authored/setup-project-docs/SKILL.zh.md +14 -9
  35. package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +3 -1
  36. package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +3 -1
  37. package/dist/subagent.d.mts +27 -20
  38. package/dist/subagent.mjs +71 -448
  39. package/package.json +34 -3
@@ -0,0 +1,379 @@
1
+ import { i as findRoleSpec, n as PSTACK_MODELS_FILENAME, o as normalizeRoleName, r as ROLE_MANIFEST, t as INHERIT_PARENT_ALIASES } from "./role-manifest-79cnFfDM.mjs";
2
+ import { join, resolve } from "node:path";
3
+ import { readFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ // src/internal/role-grammar.ts
6
+ function frontmatterBodyStart(lines) {
7
+ if (lines[0]?.trim() !== "---") return 0;
8
+ for (let index = 1; index < lines.length; index += 1) if (lines[index]?.trim() === "---") return index + 1;
9
+ return 0;
10
+ }
11
+ /** One line grammar for role parsing and byte-preserving updates. */
12
+ function scanRoleDocument(text) {
13
+ const lines = text.split("\n");
14
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
15
+ const bodyStart = frontmatterBodyStart(lines);
16
+ const scanned = [];
17
+ let offset = 0;
18
+ for (let index = 0; index < lines.length; index += 1) {
19
+ const rawLine = lines[index];
20
+ const line = index + 1 - bodyStart;
21
+ const contentEnd = offset + rawLine.length - (rawLine.endsWith("\r") ? 1 : 0);
22
+ if (index < bodyStart) {
23
+ scanned.push({
24
+ kind: "blank",
25
+ line
26
+ });
27
+ offset += rawLine.length + 1;
28
+ continue;
29
+ }
30
+ const trimmed = rawLine.trim();
31
+ if (trimmed === "") scanned.push({
32
+ kind: "blank",
33
+ line
34
+ });
35
+ else if (trimmed.startsWith("#")) {
36
+ const match = /^#\s*budget:\s*(.+)$/i.exec(trimmed);
37
+ scanned.push({
38
+ kind: "comment",
39
+ line,
40
+ ...match === null ? {} : { budget: match[1].trim() }
41
+ });
42
+ } else {
43
+ const separator = trimmed.indexOf(":");
44
+ if (separator === -1) scanned.push({
45
+ kind: "malformed",
46
+ line,
47
+ rawLine: trimmed
48
+ });
49
+ else {
50
+ const rawKey = trimmed.slice(0, separator).trim();
51
+ const key = normalizeRoleName(rawKey);
52
+ const spec = findRoleSpec(key);
53
+ if (key === "") scanned.push({
54
+ kind: "malformed",
55
+ line,
56
+ rawLine: ""
57
+ });
58
+ else if (spec === void 0) scanned.push({
59
+ kind: "unknown-role",
60
+ line,
61
+ rawKey
62
+ });
63
+ else {
64
+ const valueText = rawLine.slice(0, contentEnd - offset);
65
+ const sourceSeparator = valueText.indexOf(":");
66
+ const afterSeparator = valueText.slice(sourceSeparator + 1);
67
+ const rawValue = afterSeparator.trim();
68
+ const valueLeading = rawValue === "" ? 0 : afterSeparator.length - afterSeparator.trimStart().length;
69
+ const valueTrailing = rawValue === "" ? 0 : afterSeparator.length - afterSeparator.trimEnd().length;
70
+ const valueStart = offset + sourceSeparator + 1 + valueLeading;
71
+ const valueEnd = contentEnd - valueTrailing;
72
+ scanned.push({
73
+ kind: "role",
74
+ line,
75
+ rawKey,
76
+ key,
77
+ rawValue,
78
+ spec,
79
+ valueStart,
80
+ valueEnd
81
+ });
82
+ }
83
+ }
84
+ }
85
+ offset += rawLine.length + 1;
86
+ }
87
+ return { lines: scanned };
88
+ }
89
+ /** Replace only the winning role's value, retaining every other source byte. */
90
+ function updateRoleDocument(text, spec, value) {
91
+ const winning = scanRoleDocument(text).lines.findLast((line) => line.kind === "role" && line.key === spec.key);
92
+ if (winning?.kind === "role") return `${text.slice(0, winning.valueStart)}${value}${text.slice(winning.valueEnd)}`;
93
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
94
+ return `${text}${text === "" || text.endsWith("\n") ? "" : eol}${spec.key}: ${value}${eol}`;
95
+ }
96
+ // src/internal/role-config.ts
97
+ /**
98
+ * The pstack role configuration: `$DSH_HOME/pstack-models.mdc`.
99
+ *
100
+ * `pstack_subagent` reads this file again on every delegation, so a change reaches the
101
+ * next child and never a child that already started.
102
+ *
103
+ * Three states stay apart because each needs a different fix: the file is missing (no
104
+ * policy, so every role inherits the parent route), unreadable (a broken install, never
105
+ * treated as "no configuration"), or loaded with per-line issues reported by line.
106
+ */
107
+ /** Absolute path of the role file for one DSH home. */
108
+ function modelsFilePath(dshHome) {
109
+ return join(dshHome, PSTACK_MODELS_FILENAME);
110
+ }
111
+ /** Resolve `$DSH_HOME` the way the Host does: the environment when set, else `~/.dsh`. */
112
+ function resolveDshHome(env = process.env, home = homedir()) {
113
+ const configured = env["DSH_HOME"];
114
+ const path = configured !== void 0 && configured.trim() !== "" ? configured : join(home, ".dsh");
115
+ const expanded = path === "~" ? home : path.startsWith("~/") || path.startsWith("~\\") ? join(home, path.slice(2)) : path;
116
+ return resolve(expanded);
117
+ }
118
+ /** Read the role file. Never throws for a missing or unreadable file. */
119
+ function readRoleConfiguration(path) {
120
+ let text;
121
+ try {
122
+ text = readFileSync(path, "utf8");
123
+ } catch (error) {
124
+ if (isMissingFile(error)) return {
125
+ status: "missing",
126
+ path
127
+ };
128
+ return {
129
+ status: "unreadable",
130
+ path,
131
+ reason: describeError(error)
132
+ };
133
+ }
134
+ return parseRoleConfiguration(text, path);
135
+ }
136
+ /** Parse role-file text. Pure: no file system access. */
137
+ function parseRoleConfiguration(text, path) {
138
+ const issues = [];
139
+ const roles = /* @__PURE__ */ new Map();
140
+ let budget;
141
+ for (const scanned of scanRoleDocument(text).lines) {
142
+ const { line } = scanned;
143
+ if (scanned.kind === "blank") continue;
144
+ if (scanned.kind === "comment") {
145
+ if (scanned.budget !== void 0) budget = scanned.budget;
146
+ continue;
147
+ }
148
+ if (scanned.kind === "malformed") {
149
+ issues.push({
150
+ line,
151
+ message: scanned.rawLine === "" ? "the role key is empty" : `expected \`<role>: <value>\`, found ${JSON.stringify(scanned.rawLine)}`
152
+ });
153
+ continue;
154
+ }
155
+ if (scanned.kind === "unknown-role") {
156
+ issues.push({
157
+ line,
158
+ message: unknownRoleMessage(scanned.rawKey, normalizeRoleName(scanned.rawKey))
159
+ });
160
+ continue;
161
+ }
162
+ const { rawKey, spec } = scanned;
163
+ const parsed = parseRouteValue(scanned.rawValue);
164
+ const entryIssues = [...parsed.issues];
165
+ if (spec.shape === "single" && parsed.routes.length > 1) entryIssues.push("a single-route role cannot hold a comma-separated list");
166
+ for (const issue of entryIssues) issues.push({
167
+ line,
168
+ message: `${rawKey}: ${issue}`
169
+ });
170
+ if (parsed.routes.length === 0) continue;
171
+ const previous = roles.get(spec.key);
172
+ if (previous !== void 0) issues.push({
173
+ line,
174
+ message: `role "${spec.key}" is already configured on line ${previous.line}; line ${line} wins`
175
+ });
176
+ roles.set(spec.key, {
177
+ line,
178
+ rawKey,
179
+ key: spec.key,
180
+ spec,
181
+ routes: parsed.routes,
182
+ issues: entryIssues
183
+ });
184
+ }
185
+ return budget === void 0 ? {
186
+ status: "loaded",
187
+ path,
188
+ roles,
189
+ issues
190
+ } : {
191
+ status: "loaded",
192
+ path,
193
+ budget,
194
+ roles,
195
+ issues
196
+ };
197
+ }
198
+ /** Parse a role value: one route, or a comma-separated list of routes. */
199
+ function parseRouteValue(raw) {
200
+ const routes = [];
201
+ const issues = [];
202
+ const segments = raw.split(",");
203
+ segments.forEach((segment, index) => {
204
+ const trimmed = segment.trim();
205
+ if (trimmed === "") {
206
+ if (index !== segments.length - 1) issues.push(`entry ${index + 1} of the list is empty`);
207
+ return;
208
+ }
209
+ const parsed = parseRoute(trimmed);
210
+ if (parsed.route === void 0) {
211
+ issues.push(parsed.issue);
212
+ return;
213
+ }
214
+ routes.push(parsed.route);
215
+ });
216
+ if (routes.length === 0 && issues.length === 0) issues.push("the value is empty; use `inherit-parent`, `auto`, or `<provider>/<model> [effort]`");
217
+ return {
218
+ routes,
219
+ issues
220
+ };
221
+ }
222
+ /** Parse one entry of a role value. */
223
+ function parseRoute(raw) {
224
+ const value = raw.trim();
225
+ if (value === "") return { issue: "the value is empty" };
226
+ const alias = value.toLowerCase();
227
+ if (INHERIT_PARENT_ALIASES.includes(alias)) return { route: {
228
+ kind: "inherit",
229
+ alias
230
+ } };
231
+ const match = /^([^\s/,[]+)\/([^\s/,[\]]+(?:\/[^\s/,[\]]+)*)(?:\s+\[([^[\]]*)\])?$/.exec(value);
232
+ if (match === null) return { issue: `${JSON.stringify(value)} is not a route; use \`inherit-parent\`, \`auto\`, or \`<provider>/<model> [effort]\`` };
233
+ const provider = match[1];
234
+ const model = match[2];
235
+ const effort = match[3];
236
+ if (effort === void 0) return { route: {
237
+ kind: "route",
238
+ provider,
239
+ model
240
+ } };
241
+ const trimmedEffort = effort.trim();
242
+ if (trimmedEffort === "") return { issue: `${JSON.stringify(value)} has empty brackets; write \`<provider>/<model> [effort]\` or drop the brackets` };
243
+ if (/\s/.test(trimmedEffort)) return { issue: `${JSON.stringify(value)} has a multi-word effort; an effort is one adapter-owned token` };
244
+ return { route: {
245
+ kind: "route",
246
+ provider,
247
+ model,
248
+ effort: trimmedEffort
249
+ } };
250
+ }
251
+ /** Raised when a role's configured list cannot be selected from. */
252
+ var RouteSelectionError = class extends Error {
253
+ constructor(message) {
254
+ super(message);
255
+ this.name = "RouteSelectionError";
256
+ }
257
+ };
258
+ /**
259
+ * Expand one role into the routes this call starts.
260
+ *
261
+ * Automatic panels start one child per entry. Another list role selects one entry, the
262
+ * first by default; `roleIndex` chooses which. A single role holds exactly one route and
263
+ * rejects `roleIndex`.
264
+ */
265
+ function selectRoleRoutes(spec, entry, roleIndex) {
266
+ if (spec.shape === "single" && roleIndex !== void 0) throw new RouteSelectionError(`role "${spec.key}" holds a single route, so \`roleIndex\` does not apply; drop \`roleIndex\` or use a list-valued role (${listRoleKeys().join(", ")})`);
267
+ if (spec.autoPanel && roleIndex !== void 0) throw new RouteSelectionError(`role "${spec.key}" is an automatic panel: it starts one child per entry, so \`roleIndex\` does not apply`);
268
+ if (entry === void 0) return {
269
+ role: spec,
270
+ mode: spec.autoPanel ? "panel" : "single",
271
+ configured: false,
272
+ routes: [{ kind: "inherit" }],
273
+ total: 1
274
+ };
275
+ if (entry.issues.length > 0) throw new RouteSelectionError(`role "${spec.key}" has invalid configuration on line ${entry.line}: ${entry.issues.join("; ")}. Fix the role file before delegating.`);
276
+ if (spec.autoPanel) return {
277
+ role: spec,
278
+ mode: "panel",
279
+ configured: true,
280
+ routes: entry.routes,
281
+ total: entry.routes.length
282
+ };
283
+ if (spec.shape === "list") {
284
+ const index = roleIndex ?? 0;
285
+ if (index >= entry.routes.length) throw new RouteSelectionError(`role "${spec.key}" has ${entry.routes.length} entr${entry.routes.length === 1 ? "y" : "ies"}, so \`roleIndex\` ${index} is out of range; valid indexes are ${entry.routes.map((_route, position) => position).join(", ")}`);
286
+ return {
287
+ role: spec,
288
+ mode: "selected",
289
+ configured: true,
290
+ routes: [entry.routes[index]],
291
+ total: entry.routes.length,
292
+ roleIndex: index
293
+ };
294
+ }
295
+ return {
296
+ role: spec,
297
+ mode: "single",
298
+ configured: true,
299
+ routes: [entry.routes[0]],
300
+ total: entry.routes.length
301
+ };
302
+ }
303
+ /** Keys of the list-valued roles, including the automatic panels. */
304
+ function listRoleKeys() {
305
+ return ROLE_MANIFEST.filter((role) => role.shape === "list").map((role) => role.key);
306
+ }
307
+ /** Raised when a role name does not match the manifest. */
308
+ var RoleNameError = class extends Error {
309
+ constructor(message) {
310
+ super(message);
311
+ this.name = "RoleNameError";
312
+ }
313
+ };
314
+ /** Resolve a role name, or throw a diagnostic that names the near misses. */
315
+ function requireRole(raw) {
316
+ const spec = findRoleSpec(normalizeRoleName(raw));
317
+ if (spec !== void 0) return spec;
318
+ const suggestions = suggestRoles(raw);
319
+ const hint = suggestions.length === 0 ? "No role key is close to it." : `Closest role keys: ${suggestions.map((key) => `"${key}"`).join(", ")}.`;
320
+ throw new RoleNameError(`unknown pstack role ${JSON.stringify(raw)}. ${hint} Role names come from the role manifest, not from free text: pass the exact key, or omit \`role\` to inherit the parent route.`);
321
+ }
322
+ function suggestRoles(raw, limit = 3) {
323
+ const target = normalizeRoleName(raw);
324
+ if (target === "") return [];
325
+ return ROLE_MANIFEST.map((role) => {
326
+ const shared = sharedTokenCount(target, role.key);
327
+ const distance = editDistance(target, role.key);
328
+ const contains = role.key.includes(target) || target.includes(role.key);
329
+ const score = (contains ? 1e3 : 0) + shared * 100 - distance + Math.min(role.key.length, target.length);
330
+ return {
331
+ key: role.key,
332
+ score,
333
+ distance,
334
+ shared,
335
+ contains
336
+ };
337
+ }).filter((candidate) => candidate.contains || candidate.shared > 0 || candidate.distance <= 4).sort((left, right) => right.score - left.score).slice(0, limit).map((candidate) => candidate.key);
338
+ }
339
+ function unknownRoleMessage(rawKey, key) {
340
+ const suggestions = suggestRoles(key);
341
+ const hint = suggestions.length === 0 ? "no role key is close to it" : `closest role keys: ${suggestions.map((candidate) => `"${candidate}"`).join(", ")}`;
342
+ return `unknown role ${JSON.stringify(rawKey)} (${hint}). A line for a retired or misspelled role is reported rather than ignored.`;
343
+ }
344
+ function sharedTokenCount(left, right) {
345
+ const rightTokens = new Set(tokenize(right));
346
+ return [...new Set(tokenize(left))].filter((token) => rightTokens.has(token)).length;
347
+ }
348
+ function tokenize(value) {
349
+ return value.split(/[^a-z0-9]+/).filter((token) => token !== "");
350
+ }
351
+ /** Levenshtein distance, iterative with one row of state. */
352
+ function editDistance(left, right) {
353
+ if (left === right) return 0;
354
+ if (left === "") return right.length;
355
+ if (right === "") return left.length;
356
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
357
+ for (let row = 1; row <= left.length; row += 1) {
358
+ const current = [row];
359
+ for (let column = 1; column <= right.length; column += 1) {
360
+ const substitution = previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1);
361
+ const deletion = previous[column] + 1;
362
+ const insertion = current[column - 1] + 1;
363
+ current.push(Math.min(substitution, deletion, insertion));
364
+ }
365
+ previous = current;
366
+ }
367
+ return previous[right.length];
368
+ }
369
+ function isMissingFile(error) {
370
+ return typeof error === "object" && error !== null && error.code === "ENOENT";
371
+ }
372
+ function describeError(error) {
373
+ if (error instanceof Error) {
374
+ const code = error.code;
375
+ return typeof code === "string" ? `${code}: ${error.message}` : error.message;
376
+ }
377
+ return String(error);
378
+ }
379
+ export { selectRoleRoutes as a, resolveDshHome as i, readRoleConfiguration as n, updateRoleDocument as o, requireRole as r, modelsFilePath as t };
@@ -0,0 +1,112 @@
1
+ // src/internal/role-manifest.ts
2
+ /**
3
+ * File name the role configuration lives under inside `$DSH_HOME`.
4
+ *
5
+ * It keeps the upstream name `pstack-models.mdc`, so a reader moving from the Cursor docs
6
+ * recognizes it, but stores it directly under `$DSH_HOME`; DSH does not load `.mdc` files
7
+ * on its own, and `pstack_subagent` is the only reader.
8
+ */
9
+ const PSTACK_MODELS_FILENAME = "pstack-models.mdc";
10
+ /** Values that resolve to "send no `agentOptions` and inherit the parent route". */
11
+ const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
12
+ /** The 17 roles, in manifest order. */
13
+ const ROLE_MANIFEST = [
14
+ {
15
+ key: "feature, refactoring",
16
+ shape: "single",
17
+ autoPanel: false
18
+ },
19
+ {
20
+ key: "bug-fix",
21
+ shape: "single",
22
+ autoPanel: false
23
+ },
24
+ {
25
+ key: "perf-issue",
26
+ shape: "single",
27
+ autoPanel: false
28
+ },
29
+ {
30
+ key: "hillclimb",
31
+ shape: "single",
32
+ autoPanel: false
33
+ },
34
+ {
35
+ key: "judgment and prose",
36
+ shape: "single",
37
+ autoPanel: false
38
+ },
39
+ {
40
+ key: "hardest tasks",
41
+ shape: "single",
42
+ autoPanel: false
43
+ },
44
+ {
45
+ key: "how explorer",
46
+ shape: "single",
47
+ autoPanel: false
48
+ },
49
+ {
50
+ key: "how explainer",
51
+ shape: "single",
52
+ autoPanel: false
53
+ },
54
+ {
55
+ key: "why investigators",
56
+ shape: "single",
57
+ autoPanel: false
58
+ },
59
+ {
60
+ key: "why synthesizer",
61
+ shape: "single",
62
+ autoPanel: false
63
+ },
64
+ {
65
+ key: "reflect tooling",
66
+ shape: "single",
67
+ autoPanel: false
68
+ },
69
+ {
70
+ key: "reflect judgment, divergent, synthesizer",
71
+ shape: "single",
72
+ autoPanel: false
73
+ },
74
+ {
75
+ key: "arena runners",
76
+ shape: "list",
77
+ autoPanel: true
78
+ },
79
+ {
80
+ key: "arena cross-judge pool",
81
+ shape: "list",
82
+ autoPanel: false
83
+ },
84
+ {
85
+ key: "swarm workers",
86
+ shape: "list",
87
+ autoPanel: false
88
+ },
89
+ {
90
+ key: "architect runners",
91
+ shape: "list",
92
+ autoPanel: true
93
+ },
94
+ {
95
+ key: "interrogate reviewers",
96
+ shape: "list",
97
+ autoPanel: true
98
+ }
99
+ ];
100
+ function isRoleKey(value) {
101
+ return ROLE_MANIFEST.some((role) => role.key === value);
102
+ }
103
+ /** Normalize formatting while keeping commas that belong to a role name. */
104
+ function normalizeRoleName(raw) {
105
+ return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
106
+ }
107
+ /** Exact manifest lookup. Callers normalize the key first. */
108
+ function findRoleSpec(key) {
109
+ if (!isRoleKey(key)) return void 0;
110
+ return ROLE_MANIFEST.find((role) => role.key === key);
111
+ }
112
+ export { isRoleKey as a, findRoleSpec as i, PSTACK_MODELS_FILENAME as n, normalizeRoleName as o, ROLE_MANIFEST as r, INHERIT_PARENT_ALIASES as t };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The one pstack role manifest.
3
+ *
4
+ * Keys are matched exactly after normalization, and commas belong to some keys
5
+ * (`feature, refactoring`, `reflect judgment, divergent, synthesizer`), so nothing here
6
+ * may split a key on a comma. A second literal role list anywhere is a bug.
7
+ *
8
+ * This module has no Node imports: the bundle imports it directly.
9
+ */
10
+ /** Whether a role holds one route or a comma-separated list of routes. */
11
+ type RoleValueShape = "single" | "list";
12
+ interface RoleSpec {
13
+ /** Exact key as written in the role file. Commas belong to the key. */
14
+ readonly key: string;
15
+ readonly shape: RoleValueShape;
16
+ /** A list role that starts one child per entry automatically. */
17
+ readonly autoPanel: boolean;
18
+ }
19
+ /** The 17 roles, in manifest order. */
20
+ declare const ROLE_MANIFEST: readonly [{
21
+ readonly key: "feature, refactoring";
22
+ readonly shape: "single";
23
+ readonly autoPanel: false;
24
+ }, {
25
+ readonly key: "bug-fix";
26
+ readonly shape: "single";
27
+ readonly autoPanel: false;
28
+ }, {
29
+ readonly key: "perf-issue";
30
+ readonly shape: "single";
31
+ readonly autoPanel: false;
32
+ }, {
33
+ readonly key: "hillclimb";
34
+ readonly shape: "single";
35
+ readonly autoPanel: false;
36
+ }, {
37
+ readonly key: "judgment and prose";
38
+ readonly shape: "single";
39
+ readonly autoPanel: false;
40
+ }, {
41
+ readonly key: "hardest tasks";
42
+ readonly shape: "single";
43
+ readonly autoPanel: false;
44
+ }, {
45
+ readonly key: "how explorer";
46
+ readonly shape: "single";
47
+ readonly autoPanel: false;
48
+ }, {
49
+ readonly key: "how explainer";
50
+ readonly shape: "single";
51
+ readonly autoPanel: false;
52
+ }, {
53
+ readonly key: "why investigators";
54
+ readonly shape: "single";
55
+ readonly autoPanel: false;
56
+ }, {
57
+ readonly key: "why synthesizer";
58
+ readonly shape: "single";
59
+ readonly autoPanel: false;
60
+ }, {
61
+ readonly key: "reflect tooling";
62
+ readonly shape: "single";
63
+ readonly autoPanel: false;
64
+ }, {
65
+ readonly key: "reflect judgment, divergent, synthesizer";
66
+ readonly shape: "single";
67
+ readonly autoPanel: false;
68
+ }, {
69
+ readonly key: "arena runners";
70
+ readonly shape: "list";
71
+ readonly autoPanel: true;
72
+ }, {
73
+ readonly key: "arena cross-judge pool";
74
+ readonly shape: "list";
75
+ readonly autoPanel: false;
76
+ }, {
77
+ readonly key: "swarm workers";
78
+ readonly shape: "list";
79
+ readonly autoPanel: false;
80
+ }, {
81
+ readonly key: "architect runners";
82
+ readonly shape: "list";
83
+ readonly autoPanel: true;
84
+ }, {
85
+ readonly key: "interrogate reviewers";
86
+ readonly shape: "list";
87
+ readonly autoPanel: true;
88
+ }];
89
+ type RoleKey = (typeof ROLE_MANIFEST)[number]["key"];
90
+ export { RoleSpec as n, RoleValueShape as r, RoleKey as t };
@@ -0,0 +1,54 @@
1
+ import { r as RoleValueShape, t as RoleKey } from "./role-manifest-8OERX9-s.mjs";
2
+ /** Which spelling of "inherit the parent route" a role line uses. */
3
+ type InheritAlias = "inherit-parent" | "auto";
4
+ /** One role value as the configuration page sees it. */
5
+ type RouteView = {
6
+ readonly kind: "inherit";
7
+ readonly alias: InheritAlias;
8
+ } | {
9
+ readonly kind: "route";
10
+ readonly provider: string;
11
+ readonly model: string;
12
+ readonly effort?: string;
13
+ };
14
+ /** One of the 17 roles in manifest order. */
15
+ interface RoleView {
16
+ readonly key: RoleKey;
17
+ readonly shape: RoleValueShape;
18
+ /** Whether the role file configures this role. */
19
+ readonly configured: boolean;
20
+ /** The winning line, when the role file configures this role. */
21
+ readonly line?: number;
22
+ readonly routes: readonly RouteView[];
23
+ readonly issues: readonly string[];
24
+ }
25
+ /** One file-level problem. Unknown and malformed lines are reported, never dropped. */
26
+ interface RoleFileIssue {
27
+ readonly line?: number;
28
+ readonly message: string;
29
+ }
30
+ /** Everything the configuration page renders about `$DSH_HOME/pstack-models.mdc`. */
31
+ interface RoleFileSnapshot {
32
+ readonly path: string;
33
+ readonly status: "missing" | "loaded" | "unreadable";
34
+ readonly reason?: string;
35
+ readonly budget?: string;
36
+ readonly roles: readonly RoleView[];
37
+ readonly issues: readonly RoleFileIssue[];
38
+ /** False only when the file cannot be read at all. */
39
+ readonly canSave: boolean;
40
+ }
41
+ /** One role save. List roles carry every route, never only the edited one. */
42
+ interface SaveRoleRequest {
43
+ readonly key: string;
44
+ readonly routes: readonly RouteView[];
45
+ }
46
+ /** A refused save leaves the file untouched and names the reason. */
47
+ type SaveRoleResult = {
48
+ readonly ok: true;
49
+ readonly snapshot: RoleFileSnapshot;
50
+ } | {
51
+ readonly ok: false;
52
+ readonly reason: string;
53
+ };
54
+ export { SaveRoleRequest as n, SaveRoleResult as r, RoleFileSnapshot as t };
@@ -0,0 +1,18 @@
1
+ import { CatalogueResult } from "./models.mjs";
2
+ import { n as SaveRoleRequest, r as SaveRoleResult, t as RoleFileSnapshot } from "./role-wire-CAJsVA6e.mjs";
3
+ import { Context, Service } from "@deepseek-ai/cordis";
4
+ import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
5
+ /** Read and write the role file for the Plugins configuration page. */
6
+ export declare class PstackRolesService extends TypertRemoteService {
7
+ static inject: string[];
8
+ constructor(ctx: Context);
9
+ protected [Service.init](): void;
10
+ /** Current path, status, values, and per-line issues. */
11
+ status(): RoleFileSnapshot;
12
+ /** The live directory the page offers for its provider/model/effort selects. */
13
+ catalogue(signal: AbortSignal): Promise<CatalogueResult>;
14
+ /** Validate and save one role, then return the reloaded snapshot. */
15
+ save(request: SaveRoleRequest, signal: AbortSignal): Promise<SaveRoleResult>;
16
+ private get llm();
17
+ }
18
+ export { PstackRolesService as default };