@jaggerxtrm/specialists 3.7.1 → 3.9.0
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/README.md +23 -9
- package/config/mandatory-rules/README.md +109 -0
- package/config/skills/specialists-creator/SKILL.md +6 -32
- package/config/skills/specialists-creator/scripts/audit-spec-uniformity.mjs +86 -0
- package/config/skills/specialists-creator/scripts/scaffold-specialist.ts +1 -6
- package/config/skills/update-specialists/SKILL.md +83 -8
- package/config/skills/using-specialists/SKILL.md +13 -0
- package/config/skills/using-specialists-v2/SKILL.md +639 -0
- package/config/specialists/debugger.specialist.json +5 -8
- package/config/specialists/executor.specialist.json +3 -5
- package/config/specialists/explorer.specialist.json +1 -6
- package/config/specialists/memory-processor.specialist.json +2 -8
- package/config/specialists/node-coordinator.specialist.json +1 -5
- package/config/specialists/overthinker.specialist.json +5 -12
- package/config/specialists/planner.specialist.json +6 -11
- package/config/specialists/researcher.specialist.json +7 -13
- package/config/specialists/reviewer.specialist.json +6 -7
- package/config/specialists/specialists-creator.specialist.json +2 -3
- package/config/specialists/sync-docs.specialist.json +5 -10
- package/config/specialists/test-runner.specialist.json +2 -12
- package/config/specialists/xt-merge.specialist.json +3 -5
- package/dist/index.js +2465 -1232
- package/package.json +1 -1
- package/config/specialists/parallel-review.specialist.json +0 -65
package/README.md
CHANGED
|
@@ -47,7 +47,9 @@ specialists merge <bead-id> # single chain or epic (topological)
|
|
|
47
47
|
specialists merge <bead-id> --rebuild # rebuild after merge
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
`specialists run` prints `[job started: <id>]` early
|
|
50
|
+
`specialists run` prints `[job started: <id>]` early. Normal runtime is DB-backed; `.specialists/jobs/latest` is legacy/operator-only.
|
|
51
|
+
|
|
52
|
+
Runtime state lives in `observability.db`; `.specialists/jobs/latest` is legacy convenience pointer only.
|
|
51
53
|
|
|
52
54
|
Ad-hoc work:
|
|
53
55
|
|
|
@@ -77,6 +79,9 @@ specialists doctor
|
|
|
77
79
|
| Need | Doc |
|
|
78
80
|
|---|---|
|
|
79
81
|
| Install and bootstrap a project | [docs/bootstrap.md](docs/bootstrap.md) |
|
|
82
|
+
| Run a script-class specialist over HTTP (`sp serve`) — overview & contract | [docs/specialists-service.md](docs/specialists-service.md) |
|
|
83
|
+
| Install `sp serve` in another project (sidecar Docker / Podman) | [docs/specialists-service-install.md](docs/specialists-service-install.md) |
|
|
84
|
+
| Build & publish the specialists-service image | [docs/release-image.md](docs/release-image.md) |
|
|
80
85
|
| Bead-first workflow and semantics | [docs/workflow.md](docs/workflow.md) |
|
|
81
86
|
| CLI commands and flags | [docs/cli-reference.md](docs/cli-reference.md) |
|
|
82
87
|
| Background jobs, feed, result, stop | [docs/background-jobs.md](docs/background-jobs.md) |
|
|
@@ -90,25 +95,34 @@ specialists doctor
|
|
|
90
95
|
| Pi subprocess isolation and extensions | [docs/pi-session.md](docs/pi-session.md) |
|
|
91
96
|
| NodeSupervisor architecture, node lifecycle, and `sp node` CLI | [docs/nodes.md](docs/nodes.md) |
|
|
92
97
|
|
|
98
|
+
## Ownership model
|
|
99
|
+
|
|
100
|
+
Specialists uses layered ownership with deterministic loader precedence: user layer overrides default layer, and default layer falls back to package source (`.specialists/user/*` > `.specialists/default/*` > `config/*`). Operationally: `config/*` is upstream source shipped by package, `.specialists/default/*` is managed mirror refreshed by `specialists init --sync-defaults` (scope: specialists + mandatory-rules + nodes), `.specialists/user/*` is repo customization layer, and `.specialists/{jobs,ready,db}` is runtime/generated state; `.specialists/jobs/` is legacy mirror/debug surface, not normal-runtime source of truth. Use `sp edit --fork-from <base>` to promote non-user specialist into user layer before editing.
|
|
101
|
+
|
|
93
102
|
## Project structure
|
|
94
103
|
|
|
95
104
|
```text
|
|
96
105
|
config/
|
|
97
|
-
├── specialists/
|
|
98
|
-
├──
|
|
99
|
-
├──
|
|
100
|
-
|
|
106
|
+
├── specialists/ canonical specialist definitions (.specialist.json)
|
|
107
|
+
├── mandatory-rules/ canonical rule sets injected into specialist prompts (+ README)
|
|
108
|
+
├── nodes/ canonical node configs
|
|
109
|
+
├── hooks/ bundled hook scripts
|
|
110
|
+
├── skills/ repo-local skills used by specialists
|
|
111
|
+
└── extensions/ pi extensions (future)
|
|
101
112
|
.specialists/
|
|
102
|
-
├── default/
|
|
113
|
+
├── default/ managed mirror of canonical (from sp init --sync-defaults)
|
|
103
114
|
│ ├── specialists/
|
|
115
|
+
│ ├── mandatory-rules/
|
|
116
|
+
│ ├── nodes/
|
|
104
117
|
│ ├── hooks/
|
|
105
118
|
│ └── skills/
|
|
106
|
-
├── user/
|
|
119
|
+
├── user/ repo-owned customizations (overrides default + canonical)
|
|
107
120
|
│ ├── specialists/
|
|
108
121
|
│ ├── hooks/
|
|
109
122
|
│ └── skills/
|
|
110
|
-
├──
|
|
111
|
-
|
|
123
|
+
├── mandatory-rules/ repo-specific rule overlay (wins on set-id conflict)
|
|
124
|
+
├── jobs/ runtime — gitignored
|
|
125
|
+
└── ready/ runtime — gitignored
|
|
112
126
|
src/ CLI, server, loader, runner, tools
|
|
113
127
|
```
|
|
114
128
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# MANDATORY_RULES
|
|
2
|
+
|
|
3
|
+
Rule sets injected at the end of every specialist prompt at spawn time. Enforces
|
|
4
|
+
behaviors the model must follow regardless of the specific task.
|
|
5
|
+
|
|
6
|
+
## Layout (three tiers)
|
|
7
|
+
|
|
8
|
+
The loader reads and unions indexes from three paths, in this precedence:
|
|
9
|
+
|
|
10
|
+
| Tier | Path | Writer | Role |
|
|
11
|
+
|------|------|--------|------|
|
|
12
|
+
| 1. Source | `config/mandatory-rules/` | specialists repo commits | Canonical source of truth. Ships with the tool. |
|
|
13
|
+
| 2. Canonical copy | `.specialists/default/mandatory-rules/` | `sp init --sync-defaults` | Mirror of canonical, placed in every downstream project. |
|
|
14
|
+
| 3. Overlay | `.specialists/mandatory-rules/` | you (per-repo) | Repo-specific additions and overrides. Wins on set-id conflict. |
|
|
15
|
+
|
|
16
|
+
A rule set defined in tier 3 overrides a same-id rule set from tier 2 or 1,
|
|
17
|
+
letting a repo tailor or replace canonical rules without editing the source.
|
|
18
|
+
|
|
19
|
+
## What gets injected
|
|
20
|
+
|
|
21
|
+
At specialist spawn, the runner resolves sets from:
|
|
22
|
+
|
|
23
|
+
1. `required_template_sets` — always loaded
|
|
24
|
+
2. `default_template_sets` — loaded unless `mandatory_rules.disable_default_globals: true` on the specialist
|
|
25
|
+
3. `specialist.mandatory_rules.template_sets` — per-specialist additions
|
|
26
|
+
4. `specialist.mandatory_rules.inline_rules` — per-specialist inline rules (no file)
|
|
27
|
+
|
|
28
|
+
The resulting block is appended to the rendered task prompt as
|
|
29
|
+
`## MANDATORY_RULES` with one `### <set-id>` section per set.
|
|
30
|
+
|
|
31
|
+
## Authoring a rule set
|
|
32
|
+
|
|
33
|
+
Create `<your-set>.md` in one of the three tiers. File format:
|
|
34
|
+
|
|
35
|
+
```markdown
|
|
36
|
+
---
|
|
37
|
+
name: <your-set>
|
|
38
|
+
kind: mandatory-rule
|
|
39
|
+
rules:
|
|
40
|
+
- id: my-rule-1
|
|
41
|
+
level: required
|
|
42
|
+
text: "Single-line rule text. Use quotes if it contains colons."
|
|
43
|
+
- id: my-rule-2
|
|
44
|
+
level: warn
|
|
45
|
+
text: "Another rule."
|
|
46
|
+
when: "optional guard, e.g. 'node > 20'"
|
|
47
|
+
---
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- **`id`** — stable identifier, shown in the rendered block. Auto-filled from `<set-id>-<n>` if omitted.
|
|
51
|
+
- **`level`** — `required`, `error`, `warn`, `info`. Display only; the model treats them as priority hints.
|
|
52
|
+
- **`text`** — one-line rule (multi-line supported via YAML `|` block).
|
|
53
|
+
- **`when`** — optional conditional context.
|
|
54
|
+
|
|
55
|
+
**Shorthand**: a file with only a body (no `rules:` frontmatter) is loaded as
|
|
56
|
+
a single rule with `level: required` and the body as `text`.
|
|
57
|
+
|
|
58
|
+
## Wiring a set
|
|
59
|
+
|
|
60
|
+
### Option 1 — via index.json (applies to all specialists)
|
|
61
|
+
|
|
62
|
+
Add the set id to the index in the tier you're writing to. Tier-3 example:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
// .specialists/mandatory-rules/index.json
|
|
66
|
+
{
|
|
67
|
+
"required_template_sets": [],
|
|
68
|
+
"default_template_sets": ["my-repo-rule"]
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Index files are union-merged across tiers; dedup by set id.
|
|
73
|
+
|
|
74
|
+
### Option 2 — per specialist
|
|
75
|
+
|
|
76
|
+
Reference the set id in the specialist's JSON:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
// config/specialists/<name>.specialist.json
|
|
80
|
+
{
|
|
81
|
+
"specialist": {
|
|
82
|
+
"mandatory_rules": {
|
|
83
|
+
"template_sets": ["my-set"],
|
|
84
|
+
"inline_rules": [
|
|
85
|
+
{ "id": "xtra-1", "level": "required", "text": "One-off rule inline." }
|
|
86
|
+
],
|
|
87
|
+
"disable_default_globals": false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Keep repo-specific rules out of canonical
|
|
94
|
+
|
|
95
|
+
Canonical rules (`config/` tier) ship to every downstream project. If a rule
|
|
96
|
+
only applies to this repo (e.g. "use bunx here"), put it in
|
|
97
|
+
`.specialists/mandatory-rules/` (tier 3). Other repos won't see it.
|
|
98
|
+
|
|
99
|
+
## Budget
|
|
100
|
+
|
|
101
|
+
The full injection block is capped at ~2000 tokens (`src/specialist/runner.ts`).
|
|
102
|
+
Over-budget: the block is skipped and a warning is logged.
|
|
103
|
+
|
|
104
|
+
## Debugging
|
|
105
|
+
|
|
106
|
+
- Loader warnings appear on stderr prefixed `[specialist runner]`.
|
|
107
|
+
- The supervisor emits a `mandatory_rules_injection` meta event on every run
|
|
108
|
+
with `sets_loaded`, `rules_count`, `inline_rules_count`, `token_estimate`.
|
|
109
|
+
- Inspect any run's injection: `sp ps <job-id>` shows the metadata.
|
|
@@ -169,7 +169,7 @@ specialists models # confirm assignments look balanced
|
|
|
169
169
|
node config/skills/specialists-creator/scripts/scaffold-specialist.ts config/specialists/my-specialist.specialist.json
|
|
170
170
|
|
|
171
171
|
# 2. Apply a preset for common model/thinking defaults (optional but preferred)
|
|
172
|
-
sp edit my-specialist --preset
|
|
172
|
+
sp edit my-specialist --preset medium
|
|
173
173
|
|
|
174
174
|
# 3. Set individual fields via dot.path (primary mutation workflow)
|
|
175
175
|
sp edit my-specialist specialist.metadata.name my-specialist
|
|
@@ -188,7 +188,7 @@ sp edit my-specialist specialist.prompt.task_template --file .tmp/task-template.
|
|
|
188
188
|
sp view my-specialist
|
|
189
189
|
|
|
190
190
|
# 6. Validate schema
|
|
191
|
-
bun skills/
|
|
191
|
+
bun config/skills/specialists-creator/scripts/validate-specialist.ts config/specialists/my-specialist.specialist.json
|
|
192
192
|
```
|
|
193
193
|
|
|
194
194
|
---
|
|
@@ -383,8 +383,6 @@ planner — epic result:
|
|
|
383
383
|
|
|
384
384
|
`run` accepts either a **file path** (`./scripts/foo.sh`, `~/scripts/foo.sh`) or a **shell command** (`bd ready`, `git status`). Pre-run validation checks that file paths exist and shell commands are on `PATH`. Shebang typos (e.g. `pytho` instead of `python`) are caught and reported as errors before the session starts.
|
|
385
385
|
|
|
386
|
-
`path` is accepted as a deprecated alias for `run`.
|
|
387
|
-
|
|
388
386
|
### `specialist.capabilities` (optional)
|
|
389
387
|
|
|
390
388
|
Informational declarations used by pre-run validation and future tooling (e.g. `specialists doctor`).
|
|
@@ -410,27 +408,6 @@ Informational declarations used by pre-run validation and future tooling (e.g. `
|
|
|
410
408
|
|
|
411
409
|
Writes the final session output to this file path after the session completes. Relative to the working directory.
|
|
412
410
|
|
|
413
|
-
### `specialist.communication` (optional)
|
|
414
|
-
|
|
415
|
-
```json
|
|
416
|
-
{
|
|
417
|
-
"communication": {
|
|
418
|
-
"next_specialists": "planner"
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
Or as an array:
|
|
424
|
-
```json
|
|
425
|
-
{
|
|
426
|
-
"communication": {
|
|
427
|
-
"next_specialists": ["planner", "test-runner"]
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
`next_specialists` declares which specialist(s) should receive this specialist's output as `$previous_result`. Chaining is executed by the caller (e.g. `run_parallel` pipeline) — this field is declarative metadata.
|
|
433
|
-
|
|
434
411
|
### `specialist.validation` (optional)
|
|
435
412
|
|
|
436
413
|
Drives the staleness detection shown in `specialists status` and `specialists list`.
|
|
@@ -507,7 +484,7 @@ Files listed under `skills.paths` are read and appended to the system prompt at
|
|
|
507
484
|
{
|
|
508
485
|
"skills": {
|
|
509
486
|
"paths": [
|
|
510
|
-
"skills/
|
|
487
|
+
".xtrm/skills/active/specialists-creator/SKILL.md",
|
|
511
488
|
".claude/agents.md"
|
|
512
489
|
]
|
|
513
490
|
}
|
|
@@ -603,9 +580,6 @@ Scripts run **locally** (not inside the agent session):
|
|
|
603
580
|
"required_tools": ["bash", "read"],
|
|
604
581
|
"external_commands": ["git"]
|
|
605
582
|
},
|
|
606
|
-
"communication": {
|
|
607
|
-
"next_specialists": ["sync-docs"]
|
|
608
|
-
},
|
|
609
583
|
"output_file": ".specialists/review.md",
|
|
610
584
|
"beads_integration": "auto"
|
|
611
585
|
}
|
|
@@ -708,7 +682,7 @@ pi --model <provider>/<fallback-model-id> --print "ping" # must return "pong"
|
|
|
708
682
|
node config/skills/specialists-creator/scripts/scaffold-specialist.ts config/specialists/my-specialist.specialist.json
|
|
709
683
|
|
|
710
684
|
# 3. Mutate with sp edit (dot.path + presets)
|
|
711
|
-
sp edit my-specialist --preset
|
|
685
|
+
sp edit my-specialist --preset medium
|
|
712
686
|
sp edit my-specialist specialist.execution.model <provider>/<primary-model-id>
|
|
713
687
|
sp edit my-specialist specialist.execution.fallback_model <provider>/<fallback-model-id>
|
|
714
688
|
|
|
@@ -720,7 +694,7 @@ sp edit my-specialist specialist.prompt.task_template --file .tmp/task-template.
|
|
|
720
694
|
sp view my-specialist
|
|
721
695
|
|
|
722
696
|
# 6. Validate schema with the bundled helper
|
|
723
|
-
bun skills/
|
|
697
|
+
bun config/skills/specialists-creator/scripts/validate-specialist.ts config/specialists/my-specialist.specialist.json
|
|
724
698
|
|
|
725
699
|
# 7. List to confirm discovery
|
|
726
700
|
specialists list
|
|
@@ -729,4 +703,4 @@ specialists list
|
|
|
729
703
|
specialists run my-specialist --prompt "ping" --no-beads
|
|
730
704
|
```
|
|
731
705
|
|
|
732
|
-
If you need the underlying implementation, read `skills/
|
|
706
|
+
If you need the underlying implementation, read `config/skills/specialists-creator/scripts/validate-specialist.ts`. It is a thin Bun/TypeScript wrapper over `parseSpecialist()` from `src/specialist/schema.ts`, which keeps the helper cross-platform for Windows, macOS, and Linux.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Audit every .specialist.json under config/specialists/ and .specialists/default/
|
|
2
|
+
// for: (1) schema parse failures, (2) unknown keys that survive .passthrough() silently.
|
|
3
|
+
//
|
|
4
|
+
// Usage (from repo root): bun config/skills/specialists-creator/scripts/audit-spec-uniformity.mjs
|
|
5
|
+
//
|
|
6
|
+
// Keep KNOWN sets in sync with src/specialist/schema.ts. If a sub-schema gains
|
|
7
|
+
// or drops a field, update this file in the same commit.
|
|
8
|
+
|
|
9
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const here = fileURLToPath(import.meta.url);
|
|
14
|
+
const repoRoot = resolve(here, '../../../../..');
|
|
15
|
+
const { validateSpecialist } = await import(resolve(repoRoot, 'src/specialist/schema.ts'));
|
|
16
|
+
|
|
17
|
+
// Walk known schema keys to detect "unknown" passthrough survivors
|
|
18
|
+
const KNOWN = {
|
|
19
|
+
root: new Set(['specialist']),
|
|
20
|
+
specialist: new Set(['metadata','execution','prompt','skills','capabilities','communication','validation','beads_integration','beads_write_notes','stall_detection','heartbeat','mandatory_rules','output_file']),
|
|
21
|
+
metadata: new Set(['name','version','description','category','author','created','updated','tags']),
|
|
22
|
+
execution: new Set(['mode','model','fallback_model','timeout_ms','stall_timeout_ms','max_retries','interactive','response_format','output_type','permission_required','requires_worktree','thinking_level','auto_commit','extensions','preferred_profile','approval_mode']),
|
|
23
|
+
'execution.extensions': new Set(['serena','gitnexus']),
|
|
24
|
+
prompt: new Set(['system','task_template','normalize_template','output_schema','examples','skill_inherit']),
|
|
25
|
+
skills: new Set(['paths','scripts']),
|
|
26
|
+
'skills.scripts.item': new Set(['run','path','phase','inject_output']),
|
|
27
|
+
capabilities: new Set(['required_tools','external_commands','diagnostic_scripts']),
|
|
28
|
+
communication: new Set(['next_specialists','publishes']),
|
|
29
|
+
validation: new Set(['files_to_watch','stale_threshold_days']),
|
|
30
|
+
stall_detection: new Set(['running_idle_warn_ms','running_idle_kill_ms','waiting_stale_ms','tool_duration_warn_ms']),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function unknownKeys(obj, knownSet, path) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const k of Object.keys(obj || {})) if (!knownSet.has(k)) out.push(`${path}.${k}`);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function audit(file) {
|
|
40
|
+
const raw = JSON.parse(readFileSync(file,'utf8'));
|
|
41
|
+
const findings = [];
|
|
42
|
+
// raw key check (before parse strips/preserves)
|
|
43
|
+
findings.push(...unknownKeys(raw, KNOWN.root, ''));
|
|
44
|
+
const s = raw.specialist ?? {};
|
|
45
|
+
findings.push(...unknownKeys(s, KNOWN.specialist, 'specialist'));
|
|
46
|
+
if (s.metadata) findings.push(...unknownKeys(s.metadata, KNOWN.metadata, 'specialist.metadata'));
|
|
47
|
+
if (s.execution) findings.push(...unknownKeys(s.execution, KNOWN.execution, 'specialist.execution'));
|
|
48
|
+
if (s.execution?.extensions) findings.push(...unknownKeys(s.execution.extensions, KNOWN['execution.extensions'], 'specialist.execution.extensions'));
|
|
49
|
+
if (s.prompt) findings.push(...unknownKeys(s.prompt, KNOWN.prompt, 'specialist.prompt'));
|
|
50
|
+
if (s.skills) findings.push(...unknownKeys(s.skills, KNOWN.skills, 'specialist.skills'));
|
|
51
|
+
if (Array.isArray(s.skills?.scripts)) for (const [i, sc] of s.skills.scripts.entries()) findings.push(...unknownKeys(sc, KNOWN['skills.scripts.item'], `specialist.skills.scripts[${i}]`));
|
|
52
|
+
if (s.capabilities) findings.push(...unknownKeys(s.capabilities, KNOWN.capabilities, 'specialist.capabilities'));
|
|
53
|
+
if (s.communication) findings.push(...unknownKeys(s.communication, KNOWN.communication, 'specialist.communication'));
|
|
54
|
+
if (s.validation) findings.push(...unknownKeys(s.validation, KNOWN.validation, 'specialist.validation'));
|
|
55
|
+
if (s.stall_detection) findings.push(...unknownKeys(s.stall_detection, KNOWN.stall_detection, 'specialist.stall_detection'));
|
|
56
|
+
return findings;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const files = [
|
|
60
|
+
...readdirSync(resolve(repoRoot,'config/specialists')).filter(f=>f.endsWith('.specialist.json')).map(f=>join(repoRoot,'config/specialists',f)),
|
|
61
|
+
...readdirSync(resolve(repoRoot,'.specialists/default')).filter(f=>f.endsWith('.specialist.json')).map(f=>join(repoRoot,'.specialists/default',f)),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
let totalUnknown = 0;
|
|
65
|
+
let parseErrors = 0;
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
try {
|
|
68
|
+
const v = await validateSpecialist(readFileSync(file,'utf8'));
|
|
69
|
+
if (!v.valid) {
|
|
70
|
+
parseErrors++;
|
|
71
|
+
console.log(`\n✗ PARSE FAIL ${file}`);
|
|
72
|
+
for (const e of v.errors) console.log(` ${e.path}: ${e.message}`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const unk = audit(file);
|
|
76
|
+
if (unk.length) {
|
|
77
|
+
totalUnknown += unk.length;
|
|
78
|
+
console.log(`\n⚠ ${file}`);
|
|
79
|
+
for (const k of unk) console.log(` unknown key: ${k}`);
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
parseErrors++;
|
|
83
|
+
console.log(`\n✗ ERROR ${file}: ${e.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
console.log(`\n=== ${files.length} specs · ${parseErrors} parse errors · ${totalUnknown} unknown keys ===`);
|
|
@@ -3,12 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import { SpecialistSchema } from "../../../../src/specialist/schema.ts";
|
|
5
5
|
|
|
6
|
-
const DEAD_FIELDS = new Set([
|
|
7
|
-
"preferred_profile",
|
|
8
|
-
"approval_mode",
|
|
9
|
-
"normalize_template",
|
|
10
|
-
"heartbeat",
|
|
11
|
-
]);
|
|
6
|
+
const DEAD_FIELDS = new Set<string>([]);
|
|
12
7
|
|
|
13
8
|
interface AddedField {
|
|
14
9
|
path: string;
|
|
@@ -6,8 +6,8 @@ description: >
|
|
|
6
6
|
"sp is out of date", "hooks not firing", "skills not loading after update",
|
|
7
7
|
or when drift is detected in installed specialists config, hooks, jobs, DB,
|
|
8
8
|
extensions, or worktree cleanup.
|
|
9
|
-
version: 1.
|
|
10
|
-
synced_at:
|
|
9
|
+
version: 1.3
|
|
10
|
+
synced_at: 2026-04-25
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# update-specialists
|
|
@@ -15,9 +15,17 @@ synced_at: 00000000
|
|
|
15
15
|
Bring specialists install back to canonical state. Detect drift, apply targeted
|
|
16
16
|
fixes, then verify with `sp doctor`. Treat canonical state as both:
|
|
17
17
|
1. healthy repo wiring and runtime behavior, and
|
|
18
|
-
2. parity with
|
|
18
|
+
2. parity with currently installed `@jaggerxtrm/specialists` package version
|
|
19
19
|
when package-level comparison is available.
|
|
20
20
|
|
|
21
|
+
Ownership contract during repair:
|
|
22
|
+
- upstream source: package `config/*` (read-only for repo operators)
|
|
23
|
+
- managed mirror: `.specialists/default/*` (refresh via `sp init --sync-defaults`; sync scope = specialists + mandatory-rules + nodes; no hand edits)
|
|
24
|
+
- repo custom layer: `.specialists/user/*` + `config/nodes/*` + `.specialists/mandatory-rules/*` (rule overlay, wins on set-id conflict; NOT drift — do not overwrite or flag)
|
|
25
|
+
- runtime/generated: `.specialists/{jobs,ready,db}`
|
|
26
|
+
|
|
27
|
+
Isolation rule: backlog-clean surfaces out of scope for this skill.
|
|
28
|
+
|
|
21
29
|
## Canonical State
|
|
22
30
|
|
|
23
31
|
Check each item explicitly. This is what a healthy specialists-initialized project
|
|
@@ -82,10 +90,14 @@ looks like.
|
|
|
82
90
|
|
|
83
91
|
| Check | Expected value |
|
|
84
92
|
|-------|----------------|
|
|
85
|
-
| specialists DB | Opens cleanly |
|
|
86
|
-
| Schema version | Matches runtime expectation |
|
|
93
|
+
| specialists DB | Opens cleanly (`.specialists/db/observability.db`) |
|
|
94
|
+
| Schema version | Matches runtime expectation (current: v11) |
|
|
95
|
+
| `specialist_job_metrics` table | Present at v11+ — holds aggregated per-job metrics |
|
|
87
96
|
| WAL / busy timeout settings | Present when runtime uses SQLite |
|
|
88
97
|
| Corruption / lock errors | None in `sp doctor` |
|
|
98
|
+
| Pre-prune extract | `sp db prune --apply` extracts metrics to `specialist_job_metrics` before deleting events |
|
|
99
|
+
| Extract backfill | `sp db extract --all-missing` populates metrics for jobs whose events still exist |
|
|
100
|
+
| Historical stats query | `sp db stats [--spec <name>] [--model <glob>] [--since <dur>]` reads the aggregated table |
|
|
89
101
|
|
|
90
102
|
### Skills + extensions parity
|
|
91
103
|
|
|
@@ -99,6 +111,21 @@ looks like.
|
|
|
99
111
|
| `pi-serena-tools` | Registered when Serena integration is expected |
|
|
100
112
|
| Extension paths | Resolve from installed project, not stale workspace copies |
|
|
101
113
|
|
|
114
|
+
### Mandatory-rules template parity (three-tier)
|
|
115
|
+
|
|
116
|
+
Loader unions indexes from three paths and probes set files in reverse precedence
|
|
117
|
+
(overlay wins on set-id conflict). Full authoring guide:
|
|
118
|
+
`config/mandatory-rules/README.md`.
|
|
119
|
+
|
|
120
|
+
| Check | Expected value |
|
|
121
|
+
|-------|----------------|
|
|
122
|
+
| `.specialists/default/mandatory-rules/*` | Mirrors canonical package templates after `sp init --sync-defaults` (managed mirror, no hand edits) |
|
|
123
|
+
| `.specialists/mandatory-rules/*` | Repo-specific overlay (user-maintained). Present when repo ships its own rules. NOT drift. |
|
|
124
|
+
| Template frontmatter | YAML frontmatter present and parseable |
|
|
125
|
+
| `specialist.mandatory_rules.template_sets` references | Resolve in order: `.specialists/mandatory-rules/` → `.specialists/default/mandatory-rules/` → `config/mandatory-rules/` |
|
|
126
|
+
| Index files (`index.json`) | Any of the three tiers may define `required_template_sets` / `default_template_sets`; loader unions + dedups |
|
|
127
|
+
| Prompt injection behavior | Runner appends resolved `MANDATORY_RULES` block at end of prompt; supervisor emits `mandatory_rules_injection` meta event |
|
|
128
|
+
|
|
102
129
|
## Detection
|
|
103
130
|
|
|
104
131
|
Run these in order. Report which checks pass and which drift.
|
|
@@ -145,6 +172,7 @@ node -e "const fs=require('fs'); const p='.claude/settings.json'; if (fs.existsS
|
|
|
145
172
|
command -v sp
|
|
146
173
|
command -v specialists
|
|
147
174
|
specialists init --help | sed -n '1,120p'
|
|
175
|
+
specialists edit --help | sed -n '1,120p' | grep -E -- '--fork-from|fork-from' || true
|
|
148
176
|
sp doctor --json 2>/dev/null || true
|
|
149
177
|
|
|
150
178
|
# 11. Jobs and worktrees
|
|
@@ -154,7 +182,15 @@ find .worktrees -maxdepth 2 -mindepth 1 -type d 2>/dev/null || true
|
|
|
154
182
|
# 12. Extension registration
|
|
155
183
|
node -e "const fs=require('fs'); const p='.pi/settings.json'; if (fs.existsSync(p)) console.log(JSON.stringify(JSON.parse(fs.readFileSync(p,'utf8')).skills ?? JSON.parse(fs.readFileSync(p,'utf8')).extensions ?? {}, null, 2)); else console.log('MISSING .pi/settings.json')"
|
|
156
184
|
|
|
157
|
-
#
|
|
185
|
+
# 13a. Observability schema + metrics coverage
|
|
186
|
+
node -e "const {Database} = require('bun:sqlite'); const p='.specialists/db/observability.db'; const fs=require('fs'); if (!fs.existsSync(p)) { console.log('NO_DB'); process.exit(0); } const db=new Database(p,{readonly:true}); const v=db.query(\"SELECT value FROM schema_meta WHERE key='version'\").get(); const has=db.query(\"SELECT name FROM sqlite_master WHERE type='table' AND name='specialist_job_metrics'\").get(); const jobs=db.query('SELECT COUNT(*) c FROM specialist_jobs').get(); const metrics=has ? db.query('SELECT COUNT(*) c FROM specialist_job_metrics').get() : null; console.log(JSON.stringify({schema_version: v?.value, has_metrics_table: !!has, jobs: jobs.c, metrics_rows: metrics?.c ?? 0, metrics_coverage: metrics ? (metrics.c/jobs.c).toFixed(2) : null}, null, 2));" 2>/dev/null || echo "REQUIRES_BUN_RUNTIME"
|
|
187
|
+
|
|
188
|
+
# 13. Mandatory-rules template tiers + reference checks (three-tier resolution)
|
|
189
|
+
find .specialists/default/mandatory-rules -maxdepth 1 -type f 2>/dev/null || true
|
|
190
|
+
find .specialists/mandatory-rules -maxdepth 1 -type f 2>/dev/null || true
|
|
191
|
+
node -e "const fs=require('fs'); const path=require('path'); const roots=['.specialists/default/specialists','.specialists/user/specialists']; const missing=[]; for (const root of roots) { if (!fs.existsSync(root)) continue; for (const file of fs.readdirSync(root)) { if (!file.endsWith('.specialist.json')) continue; const spec=JSON.parse(fs.readFileSync(path.join(root,file),'utf8')); const sets=spec.specialist?.mandatory_rules?.template_sets ?? []; for (const set of sets) { const candidates=[path.join('.specialists/mandatory-rules',set+'.md'), path.join('.specialists/default/mandatory-rules',set+'.md'), path.join('config/mandatory-rules',set+'.md')]; if (!candidates.some((p)=>fs.existsSync(p))) missing.push(file+': missing template set '+set); } } } if (missing.length) console.log(missing.join('\n'));"
|
|
192
|
+
|
|
193
|
+
# 14. Shipped skill frontmatter parity
|
|
158
194
|
node -e "const fs=require('fs'); const path=require('path'); const dir='.xtrm/skills/default'; if (!fs.existsSync(dir)) process.exit(0); for (const name of fs.readdirSync(dir)) { const p=path.join(dir,name,'SKILL.md'); if (!fs.existsSync(p)) continue; const head=fs.readFileSync(p,'utf8').split('---')[1] || ''; const version=(head.match(/version:\s*([^\n]+)/)||[])[1]; const synced=(head.match(/synced_at:\s*([^\n]+)/)||[])[1]; console.log(name+': version='+(version||'missing')+' synced_at='+(synced||'missing')); }"
|
|
159
195
|
```
|
|
160
196
|
|
|
@@ -167,7 +203,8 @@ Use targeted fixes first. Escalate to full sync only if needed.
|
|
|
167
203
|
| Installed package version mismatch | reinstall / upgrade `@jaggerxtrm/specialists`, then re-run checks |
|
|
168
204
|
| CLI version mismatch vs package | reinstall runtime so `sp` / `specialists` align with installed package |
|
|
169
205
|
| Specialist JSON missing required fields | `sp edit <name> ...` or regenerate via `specialists init --sync-defaults` |
|
|
170
|
-
|
|
|
206
|
+
| Need user-layer override from default/package specialist | `sp edit <name> --fork-from <base>` to materialize editable copy in `.specialists/user/` |
|
|
207
|
+
| Specialist JSON schema mismatch | `specialists init --sync-defaults` (refreshes specialists + mandatory-rules + nodes) |
|
|
171
208
|
| Installed specialist default differs from canonical package copy | `specialists init --sync-defaults` unless local customization is intentional |
|
|
172
209
|
| Hooks missing or stale | `specialists init` |
|
|
173
210
|
| Installed hook file differs from canonical package copy | `specialists init` unless local customization is intentional |
|
|
@@ -175,11 +212,18 @@ Use targeted fixes first. Escalate to full sync only if needed.
|
|
|
175
212
|
| Job dir missing | `specialists init` |
|
|
176
213
|
| Orphaned `.worktrees/` entries | `specialists clean` |
|
|
177
214
|
| SQLite schema/version mismatch | `sp doctor` first, then `specialists init --sync-defaults` or runtime migration command |
|
|
215
|
+
| Schema below v11 (no `specialist_job_metrics`) | Reinstall / upgrade runtime; table is created by initSchema / migrateToV11. No data loss — raw events untouched. |
|
|
216
|
+
| Events about to be pruned but never aggregated | `sp db extract --all-missing` BEFORE `sp db prune --apply`. Prune refuses when extract fails (safe by design). |
|
|
217
|
+
| Emergency: need to prune but extract is wedged | `sp db prune --apply --skip-extract` — raw events deleted without aggregation. Use only when data loss is acceptable. |
|
|
218
|
+
| Historical per-job stats needed | `sp db stats` reads `specialist_job_metrics`. Replaces ad-hoc `status.json` scans. Supports `--format json\|table`. |
|
|
178
219
|
| Pi extensions missing | `specialists init --sync-skills` or reinstall extension registration |
|
|
179
220
|
| Hook config format stale | `specialists init` |
|
|
180
221
|
| Skill symlink / active-skill drift | `specialists init --sync-skills` |
|
|
181
222
|
| Installed default skill differs from canonical package copy | `specialists init --sync-skills` unless local customization is intentional |
|
|
182
223
|
| Skill frontmatter version / synced_at drift | `specialists init --sync-skills` or refresh packaged skills |
|
|
224
|
+
| Mandatory-rules mirror drift (`.specialists/default/mandatory-rules`) | `specialists init --sync-defaults` |
|
|
225
|
+
| `.specialists/mandatory-rules/` overlay present | Leave alone — this is repo overlay, NOT drift |
|
|
226
|
+
| Missing/invalid `template_sets` references | Check all three tiers first; `sp edit <name> --fork-from <base>` then fix references, or sync defaults if mirror missing, or add set to overlay if intended |
|
|
183
227
|
| Unknown manual drift | Stop, inspect, then apply user-approved fix |
|
|
184
228
|
|
|
185
229
|
## Remediation
|
|
@@ -206,8 +250,14 @@ shows missing fields, wrong names, or schema mismatch:
|
|
|
206
250
|
specialists init --sync-defaults
|
|
207
251
|
```
|
|
208
252
|
|
|
253
|
+
`--sync-defaults` refreshes specialists + mandatory-rules + nodes mirrors.
|
|
254
|
+
|
|
209
255
|
If one specialist needs a small repair and `sp edit` supports it, prefer that over
|
|
210
|
-
full sync.
|
|
256
|
+
full sync. If target specialist lives in default/package layer, fork first:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
sp edit <name> --fork-from <base>
|
|
260
|
+
```
|
|
211
261
|
|
|
212
262
|
### Fix: Hooks not firing
|
|
213
263
|
|
|
@@ -250,6 +300,31 @@ If doctor reports DB version mismatch or recovery issue:
|
|
|
250
300
|
2. Apply runtime migration command if available.
|
|
251
301
|
3. If no automated migration exists, flag manual intervention.
|
|
252
302
|
|
|
303
|
+
### Fix: metrics aggregation missing or stale
|
|
304
|
+
|
|
305
|
+
Schema v11 introduced `specialist_job_metrics` (aggregated per-job stats). If you see low `metrics_coverage` in the detection output, or want historical stats before running `sp db prune`:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# Backfill metrics for any job whose events still exist but lack a metrics row.
|
|
309
|
+
sp db extract --all-missing
|
|
310
|
+
|
|
311
|
+
# Inspect specific job metrics.
|
|
312
|
+
sp db extract --job <job-id>
|
|
313
|
+
|
|
314
|
+
# Query aggregates.
|
|
315
|
+
sp db stats
|
|
316
|
+
sp db stats --spec executor --since 7d --format json
|
|
317
|
+
sp db stats --model 'openai-codex/*' --since 30d
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`sp db prune --apply` automatically extracts for every job whose events will be deleted (unless `--skip-extract`). If extract throws, prune aborts — investigate the failing job instead of bypassing.
|
|
321
|
+
|
|
322
|
+
Safe order before a retention cleanup:
|
|
323
|
+
1. `sp db extract --all-missing` — verify no extract errors.
|
|
324
|
+
2. `sp db prune --before 30d --dry-run` — confirm scope.
|
|
325
|
+
3. `sp db prune --before 30d --apply` — prune with pre-extract built in.
|
|
326
|
+
4. `sp db vacuum` — compact file size.
|
|
327
|
+
|
|
253
328
|
### Fix: Skills/defaults differ from shipped package copy
|
|
254
329
|
|
|
255
330
|
If diff against the installed package shows `.specialists/default/`,
|
|
@@ -62,6 +62,17 @@ Specialists are autonomous AI agents that run independently — fresh context, d
|
|
|
62
62
|
8. **No destructive operations by specialists.** No `rm -rf`, no force pushes, no database drops, no credential rotation, no mass deletes, no history rewrites. Surface destructive requirements to the user.
|
|
63
63
|
9. **Executor does not run tests.** Executor runs lint + tsc only. Tests are the reviewer's and test-runner's responsibility in the chained pipeline.
|
|
64
64
|
10. **Keep specialists alive through the review cycle.** Never `sp stop` an executor or debugger before the reviewer delivers its verdict. The specialist stays in `waiting` so you can `resume` it — to commit changes, apply fixes from reviewer feedback, or continue work. Only stop after final reviewer PASS and confirmed commit.
|
|
65
|
+
11. **Respect ownership layers and loader precedence.** Loader resolution order is `.specialists/user/*` > `.specialists/default/*` > package fallback `config/*`. Upstream source = package `config/*` (read-only for repo operators); managed mirror = `.specialists/default/*` (no hand edits); repo custom layer = `.specialists/user/*`; runtime/generated = `.specialists/{jobs,ready,db}`.
|
|
66
|
+
12. **Keep backlog-clean isolated.** Do not mix backlog-clean changes into specialist ownership/migration tasks.
|
|
67
|
+
|
|
68
|
+
## Mandatory-rules template sets
|
|
69
|
+
|
|
70
|
+
Use template-driven mandatory rules for repeatable policy bundles.
|
|
71
|
+
|
|
72
|
+
- Specialist config field: `specialist.mandatory_rules.template_sets`
|
|
73
|
+
- Template source: `config/mandatory-rules/*.md`
|
|
74
|
+
- Template format: YAML frontmatter + body content
|
|
75
|
+
- Runtime behavior: runner resolves templates and injects rendered rules at end of prompt
|
|
65
76
|
|
|
66
77
|
---
|
|
67
78
|
|
|
@@ -127,11 +138,13 @@ specialists stop <job-id> --force # 5s SIGTERM timeout, then pgroup
|
|
|
127
138
|
|
|
128
139
|
# Management
|
|
129
140
|
specialists edit <name> # edit specialist config (dot-path, --preset)
|
|
141
|
+
specialists edit <name> --fork-from <base> # fork non-user specialist into .specialists/user/ then edit
|
|
130
142
|
specialists clean # purge old job dirs + worktree GC
|
|
131
143
|
specialists clean --processes # kill all running/starting specialist jobs
|
|
132
144
|
specialists db vacuum # compact SQLite storage (refuses if jobs running)
|
|
133
145
|
specialists db prune --before <iso|duration> --dry-run|--apply # prune old events/results/terminal jobs
|
|
134
146
|
specialists doctor orphans # integrity scan: orphan, stale-pointer, integrity-violation
|
|
147
|
+
specialists init --sync-defaults # refresh specialists + mandatory-rules + nodes from canonical defaults
|
|
135
148
|
specialists init --sync-skills # re-sync skills only (no full init)
|
|
136
149
|
specialists init --no-xtrm-check # skip xtrm prerequisite check (CI/testing)
|
|
137
150
|
```
|