@rubytech/create-sitedesk-code 0.1.524 → 0.1.525
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/package.json +1 -1
- package/payload/platform/plugins/admin/skills/superpowers-sprint/SKILL.md +26 -0
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/memory/PLUGIN.md +10 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +5 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js +141 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts +45 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js +122 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js +26 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js +201 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +89 -33
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +6 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js +148 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +83 -6
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +90 -5
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js +111 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +38 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +116 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +13 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +81 -8
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +39 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +9 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/scripts/__tests__/check-canonical-tool-names.test.sh +162 -0
- package/payload/platform/scripts/__tests__/check-no-task-id-leaks.test.sh +53 -0
- package/payload/platform/scripts/__tests__/task-id-citation.test.sh +72 -0
- package/payload/platform/scripts/check-canonical-tool-names.mjs +36 -4
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +80 -62
- package/payload/platform/scripts/lib/canonical-tool-names.mjs +130 -26
- package/payload/platform/scripts/lib/task-id-citation.mjs +67 -0
- package/payload/platform/.docs/search-surface-contract.md +0 -58
- package/payload/platform/docs/superpowers/plans/2026-06-02-task-610-follower-202-retry.md +0 -372
- package/payload/platform/docs/superpowers/plans/2026-06-04-public-agent-knowledge-delivery.md +0 -230
- package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +0 -544
- package/payload/platform/docs/superpowers/plans/2026-07-11-task-1557-operator-on-behalf-preference-attribution.md +0 -542
- package/payload/platform/docs/superpowers/plans/2026-07-13-account-schema-ontology-projection.md +0 -547
- package/payload/platform/docs/superpowers/plans/2026-07-14-graph-top-level-labels-ontology-single-source.md +0 -458
- package/payload/platform/docs/superpowers/plans/2026-07-17-task-1736-retire-mcp-stderr-tee.md +0 -397
- package/payload/platform/docs/superpowers/plans/2026-07-18-graph-native-ledger.md +0 -807
- package/payload/platform/docs/superpowers/plans/2026-07-18-ledger-write-path-fix.md +0 -109
- package/payload/platform/docs/superpowers/plans/2026-07-20-storage-pages-create.md +0 -82
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +0 -673
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +0 -1244
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +0 -462
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +0 -321
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +0 -830
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +0 -2177
- package/payload/platform/docs/superpowers/plans/2026-07-21-graph-caption-resolver.md +0 -124
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1877-bash-schema-enforcement.md +0 -107
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1887-reconcile-allowed-top-level.md +0 -205
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +0 -748
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +0 -1716
- package/payload/platform/docs/superpowers/plans/2026-07-23-task-1930-adherence-enforcement.md +0 -422
- package/payload/platform/docs/superpowers/plans/2026-07-24-task-1942-subaccount-switcher-brand-head.md +0 -327
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1974-uploads-intake-inbox.md +0 -235
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1976-intra-folder-hygiene.md +0 -215
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +0 -555
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1926-portal-push-concurrency-guard.md +0 -482
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +0 -394
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2016-disabled-agent-routing.md +0 -624
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2028-declared-file-write-deny.md +0 -303
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2052-account-machinery-write-fence.md +0 -250
- package/payload/platform/docs/superpowers/plans/2026-07-28-task-2097-email-signature-set.md +0 -65
- package/payload/platform/docs/superpowers/specs/2026-06-02-task-610-follower-202-retry-design.md +0 -116
- package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +0 -170
- package/payload/platform/docs/superpowers/specs/2026-07-11-task-1557-operator-on-behalf-preference-attribution-design.md +0 -90
- package/payload/platform/docs/superpowers/specs/2026-07-13-account-schema-ontology-projection-design.md +0 -178
- package/payload/platform/docs/superpowers/specs/2026-07-14-graph-top-level-labels-ontology-single-source-design.md +0 -119
- package/payload/platform/docs/superpowers/specs/2026-07-17-task-1736-retire-mcp-stderr-tee-design.md +0 -128
- package/payload/platform/docs/superpowers/specs/2026-07-18-graph-native-ledger-design.md +0 -241
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +0 -177
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +0 -201
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +0 -217
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +0 -80
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +0 -97
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +0 -121
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +0 -103
- package/payload/platform/docs/superpowers/specs/2026-07-23-task-1930-adherence-enforcement-design.md +0 -81
- package/payload/platform/docs/superpowers/specs/2026-07-24-task-1942-subaccount-switcher-brand-head-design.md +0 -35
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +0 -232
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1926-portal-push-concurrency-guard-design.md +0 -200
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +0 -169
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2016-disabled-agent-routing-design.md +0 -139
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2028-declared-file-write-deny-design.md +0 -127
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2052-account-machinery-write-fence-design.md +0 -150
- package/payload/platform/docs/superpowers/specs/2026-07-28-task-2097-email-signature-set-design.md +0 -155
package/payload/platform/docs/superpowers/plans/2026-07-27-task-2016-disabled-agent-routing.md
DELETED
|
@@ -1,624 +0,0 @@
|
|
|
1
|
-
# Task 2016 — disabled agents leave the admin routing table
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** A specialist the operator disabled carries no `- **specialists:<name>**:` line in `agents/admin/AGENTS.md`, gets it back on enable, and reads as `disabled` rather than `missing file` in `system-status`.
|
|
6
|
-
|
|
7
|
-
**Architecture:** One rule in one place: `agents-md-bootstrap.sh` treats `<accountDir>/agents-disabled.json` as the operator's intent and withholds a routing line for every name it holds. The disable and enable routes exec that same script after their file move, so prose is right immediately instead of after the next provisioning run. The `system-status` specialist walk moves into its own module that reads the same store and reports `disabled` and `missing file` as different states with separate counts.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** bash + python3 (the store parser already used by `provision-account-dir.sh`), TypeScript on Node, Hono routes, vitest.
|
|
10
|
-
|
|
11
|
-
## Global Constraints
|
|
12
|
-
|
|
13
|
-
- Spec: `platform/docs/superpowers/specs/2026-07-27-task-2016-disabled-agent-routing-design.md`.
|
|
14
|
-
- All paths below are relative to `maxy-code/` inside the worktree.
|
|
15
|
-
- A store entry maps to a routing line by stripping `.md`. One rule, no branch.
|
|
16
|
-
- An unreadable store withholds nothing, matching `provision-account-dir.sh:293`.
|
|
17
|
-
- The existing ` [admin-bootstrap] AGENTS.md specialists=<n> appended=<m> existing=<k>` line stays byte-identical; the current suite asserts its counts.
|
|
18
|
-
- `platform/plugins/admin/skills/platform-architecture/SKILL.md` is generated by `scripts/assemble-architecture-skill.mjs` from the docs corpus. Never hand-edit it. Doc changes go in `platform/plugins/docs/references/admin-ui.md`.
|
|
19
|
-
- Commit trailers: `Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>` and `Session: a2b8f556-8297-4184-bf57-59b078a8015d`.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## File Structure
|
|
24
|
-
|
|
25
|
-
| File | Responsibility |
|
|
26
|
-
|------|----------------|
|
|
27
|
-
| `platform/scripts/lib/agents-md-bootstrap.sh` (modify) | Reconcile specialist lines against the disabled store; emit the `op=specialist-lines` line. |
|
|
28
|
-
| `platform/scripts/__tests__/agents-md-bootstrap.test.sh` (modify) | Cases for withhold, restore, malformed store. |
|
|
29
|
-
| `platform/plugins/admin/mcp/src/specialist-registry.ts` (create) | Classify AGENTS.md entries as `ok` / `disabled (operator)` / `missing file` and count them. |
|
|
30
|
-
| `platform/plugins/admin/mcp/src/__tests__/specialist-registry.test.ts` (create) | Cover the three statuses and the two counts. |
|
|
31
|
-
| `platform/plugins/admin/mcp/src/index.ts` (modify, 508-538) | Call the module instead of classifying inline. |
|
|
32
|
-
| `platform/ui/server/routes/admin/agents.ts` (modify) | Exec the bootstrap after disable and enable; report `routingReconciled`. |
|
|
33
|
-
| `platform/ui/server/routes/admin/__tests__/agents.test.ts` (modify) | Prove the line disappears on disable and returns on enable. |
|
|
34
|
-
| `platform/plugins/docs/references/admin-ui.md` (modify, 209-241) | Document the reconcile and the new response field. |
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
### Task 1: The bootstrap reconciles against the disabled store
|
|
39
|
-
|
|
40
|
-
**Files:**
|
|
41
|
-
- Modify: `platform/scripts/lib/agents-md-bootstrap.sh`
|
|
42
|
-
- Test: `platform/scripts/__tests__/agents-md-bootstrap.test.sh`
|
|
43
|
-
|
|
44
|
-
**Interfaces:**
|
|
45
|
-
- Consumes: `<accountDir>/agents-disabled.json`, shape `{ "disabled": ["<basename>.md", …] }`, written by `platform/ui/server/routes/admin/agents.ts`.
|
|
46
|
-
- Produces: stdout line ` [agents-md] op=specialist-lines account=<id8> kept=<n> withheld=<comma-list|none|store-unreadable>`. Task 3 relies on this script being callable as `bash <PLATFORM_ROOT>/scripts/lib/agents-md-bootstrap.sh <accountDir>`.
|
|
47
|
-
|
|
48
|
-
- [ ] **Step 1: Write the failing tests**
|
|
49
|
-
|
|
50
|
-
Append to `platform/scripts/__tests__/agents-md-bootstrap.test.sh`, immediately before the final summary block that prints `PASS`/`FAIL` counts:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Case 7 (Task 2016) — a name in the disabled store gets no routing line.
|
|
54
|
-
TMP=$(make_account)
|
|
55
|
-
bash "$BOOTSTRAP" "$TMP" >/dev/null
|
|
56
|
-
printf '{"disabled":["beta-specialist.md"]}\n' > "$TMP/agents-disabled.json"
|
|
57
|
-
OUT=$(bash "$BOOTSTRAP" "$TMP")
|
|
58
|
-
assert_not_contains "$TMP/agents/admin/AGENTS.md" "- **specialists:beta-specialist**: " "withhold:beta-line-gone"
|
|
59
|
-
assert_contains "$TMP/agents/admin/AGENTS.md" "- **specialists:alpha-specialist**: " "withhold:alpha-kept"
|
|
60
|
-
assert_contains "$TMP/agents/admin/AGENTS.md" "- **specialists:gamma-specialist**: " "withhold:gamma-kept"
|
|
61
|
-
assert_matches "$OUT" "op=specialist-lines account=[^ ]+ kept=2 withheld=beta-specialist" "withhold:log-line"
|
|
62
|
-
|
|
63
|
-
# Case 8 (Task 2016) — clearing the store restores the line on the next run.
|
|
64
|
-
printf '{"disabled":[]}\n' > "$TMP/agents-disabled.json"
|
|
65
|
-
OUT=$(bash "$BOOTSTRAP" "$TMP")
|
|
66
|
-
assert_contains "$TMP/agents/admin/AGENTS.md" "- **specialists:beta-specialist**: " "restore:beta-back"
|
|
67
|
-
assert_matches "$OUT" "op=specialist-lines account=[^ ]+ kept=3 withheld=none" "restore:log-line"
|
|
68
|
-
rm -rf "$TMP"
|
|
69
|
-
|
|
70
|
-
# Case 9 (Task 2016) — a malformed store removes nothing and says so.
|
|
71
|
-
TMP=$(make_account)
|
|
72
|
-
bash "$BOOTSTRAP" "$TMP" >/dev/null
|
|
73
|
-
printf '{ not json' > "$TMP/agents-disabled.json"
|
|
74
|
-
OUT=$(bash "$BOOTSTRAP" "$TMP")
|
|
75
|
-
assert_contains "$TMP/agents/admin/AGENTS.md" "- **specialists:alpha-specialist**: " "badstore:alpha-kept"
|
|
76
|
-
assert_contains "$TMP/agents/admin/AGENTS.md" "- **specialists:beta-specialist**: " "badstore:beta-kept"
|
|
77
|
-
assert_matches "$OUT" "op=specialist-lines account=[^ ]+ kept=3 withheld=store-unreadable" "badstore:log-line"
|
|
78
|
-
rm -rf "$TMP"
|
|
79
|
-
|
|
80
|
-
# Case 10 (Task 2016) — no store on disk is the cold-install path: nothing withheld.
|
|
81
|
-
TMP=$(make_account)
|
|
82
|
-
OUT=$(bash "$BOOTSTRAP" "$TMP")
|
|
83
|
-
assert_matches "$OUT" "op=specialist-lines account=[^ ]+ kept=3 withheld=none" "nostore:log-line"
|
|
84
|
-
rm -rf "$TMP"
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
- [ ] **Step 2: Run the suite to verify the new cases fail**
|
|
88
|
-
|
|
89
|
-
Run: `bash platform/scripts/__tests__/agents-md-bootstrap.test.sh`
|
|
90
|
-
Expected: FAIL. The `withhold:*`, `restore:*`, `badstore:*` and `nostore:*` assertions fail because the script neither removes lines nor prints an `op=specialist-lines` line; every pre-existing case still passes.
|
|
91
|
-
|
|
92
|
-
- [ ] **Step 3: Implement the reconcile**
|
|
93
|
-
|
|
94
|
-
In `platform/scripts/lib/agents-md-bootstrap.sh`, extend the header comment after the existing paragraph about preserved lines:
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# A name in <account_dir>/agents-disabled.json gets no routing line: an existing
|
|
98
|
-
# one is removed and the append loop skips it. The store is the operator's
|
|
99
|
-
# intent, so this holds even in the drift state where the file is still live
|
|
100
|
-
# (the standing agent-parity check is what reports that drift). An unreadable
|
|
101
|
-
# store withholds nothing, matching provision-account-dir.sh.
|
|
102
|
-
#
|
|
103
|
-
# Second stdout line, same two-space indent:
|
|
104
|
-
# " [agents-md] op=specialist-lines account=<id8> kept=<n> withheld=<comma-list|none|store-unreadable>"
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Insert this block after the `if [ ! -f "$AGENTS_MD" ]` bootstrap block (after line 28) and before `_specialists=0`:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
# --- Disabled-agent reconcile (Task 2016) ------------------------------------
|
|
111
|
-
_store="$ACCOUNT_DIR/agents-disabled.json"
|
|
112
|
-
_withheld_names=""
|
|
113
|
-
_withheld_field="none"
|
|
114
|
-
if [ -f "$_store" ]; then
|
|
115
|
-
if _withheld_names=$(python3 -c '
|
|
116
|
-
import json, sys
|
|
117
|
-
try:
|
|
118
|
-
parsed = json.load(open(sys.argv[1]))
|
|
119
|
-
except Exception:
|
|
120
|
-
raise SystemExit(1)
|
|
121
|
-
names = parsed.get("disabled")
|
|
122
|
-
if not isinstance(names, list):
|
|
123
|
-
raise SystemExit(1)
|
|
124
|
-
for n in sorted(names):
|
|
125
|
-
if isinstance(n, str) and n.endswith(".md") and "/" not in n and ".." not in n:
|
|
126
|
-
print(n[:-3])
|
|
127
|
-
' "$_store" 2>/dev/null); then
|
|
128
|
-
if [ -n "$_withheld_names" ]; then
|
|
129
|
-
_withheld_field=$(echo "$_withheld_names" | paste -sd, -)
|
|
130
|
-
fi
|
|
131
|
-
else
|
|
132
|
-
_withheld_names=""
|
|
133
|
-
_withheld_field="store-unreadable"
|
|
134
|
-
fi
|
|
135
|
-
fi
|
|
136
|
-
|
|
137
|
-
_is_withheld() {
|
|
138
|
-
[ -n "$_withheld_names" ] || return 1
|
|
139
|
-
printf '%s\n' "$_withheld_names" | grep -qxF -- "$1"
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if [ -n "$_withheld_names" ]; then
|
|
143
|
-
while IFS= read -r _w; do
|
|
144
|
-
[ -n "$_w" ] || continue
|
|
145
|
-
if grep -qE "^- \*\*specialists:${_w}\*\*:" "$AGENTS_MD"; then
|
|
146
|
-
grep -vE "^- \*\*specialists:${_w}\*\*:" "$AGENTS_MD" > "$AGENTS_MD.tmp" || true
|
|
147
|
-
mv "$AGENTS_MD.tmp" "$AGENTS_MD"
|
|
148
|
-
fi
|
|
149
|
-
done <<< "$_withheld_names"
|
|
150
|
-
fi
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
In the walk loop, add the skip immediately after `[ -z "$_name" ] && continue` (line 40) so a withheld name is neither counted nor appended:
|
|
154
|
-
|
|
155
|
-
```bash
|
|
156
|
-
if _is_withheld "$_name"; then continue; fi
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
After the existing `echo " [admin-bootstrap] …"` line, add:
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
_kept=$(grep -cE '^- \*\*specialists:' "$AGENTS_MD" || true)
|
|
163
|
-
_id8=$(basename "$ACCOUNT_DIR" | cut -c1-8)
|
|
164
|
-
echo " [agents-md] op=specialist-lines account=$_id8 kept=$_kept withheld=$_withheld_field"
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
- [ ] **Step 4: Run the suite to verify it passes**
|
|
168
|
-
|
|
169
|
-
Run: `bash platform/scripts/__tests__/agents-md-bootstrap.test.sh`
|
|
170
|
-
Expected: every case passes, `FAIL=0`, including the pre-existing cases 1-6 unchanged.
|
|
171
|
-
|
|
172
|
-
- [ ] **Step 5: Commit**
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
git add maxy-code/platform/scripts/lib/agents-md-bootstrap.sh maxy-code/platform/scripts/__tests__/agents-md-bootstrap.test.sh
|
|
176
|
-
git commit -m "$(cat <<'EOF'
|
|
177
|
-
fix(2016): withhold routing lines for disabled specialists
|
|
178
|
-
|
|
179
|
-
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
180
|
-
Session: a2b8f556-8297-4184-bf57-59b078a8015d
|
|
181
|
-
EOF
|
|
182
|
-
)"
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
### Task 2: `system-status` separates disabled from missing
|
|
188
|
-
|
|
189
|
-
**Files:**
|
|
190
|
-
- Create: `platform/plugins/admin/mcp/src/specialist-registry.ts`
|
|
191
|
-
- Create: `platform/plugins/admin/mcp/src/__tests__/specialist-registry.test.ts`
|
|
192
|
-
- Modify: `platform/plugins/admin/mcp/src/index.ts:508-538`
|
|
193
|
-
|
|
194
|
-
**Interfaces:**
|
|
195
|
-
- Consumes: `<accountDir>/agents-disabled.json` (same shape as Task 1), `<accountDir>/agents/admin/AGENTS.md`, `<accountDir>/specialists/agents/`.
|
|
196
|
-
- Produces: `classifySpecialists(opts: { agentsMd: string; specialistsAgentsDir: string; accountDir: string; chromeUp: boolean }): SpecialistRegistryReport` where `SpecialistRegistryReport = { lines: string[]; registered: number; missing: number; disabled: number }`.
|
|
197
|
-
|
|
198
|
-
- [ ] **Step 1: Write the failing test**
|
|
199
|
-
|
|
200
|
-
Create `platform/plugins/admin/mcp/src/__tests__/specialist-registry.test.ts`:
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
// Task 2016 — an agent the operator disabled reads as disabled, not as a
|
|
204
|
-
// missing file. A `missing file` line for a name in the store is the failure
|
|
205
|
-
// this suite exists to catch.
|
|
206
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
207
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
208
|
-
import { tmpdir } from "node:os";
|
|
209
|
-
import { join } from "node:path";
|
|
210
|
-
import { classifySpecialists } from "../specialist-registry.js";
|
|
211
|
-
|
|
212
|
-
let accountDir: string;
|
|
213
|
-
|
|
214
|
-
const AGENTS_MD = [
|
|
215
|
-
"# Installed Roles",
|
|
216
|
-
"",
|
|
217
|
-
"- **specialists:live-one**: Does live work.",
|
|
218
|
-
"- **specialists:switched-off**: Was switched off.",
|
|
219
|
-
"- **specialists:never-arrived**: Never delivered.",
|
|
220
|
-
"",
|
|
221
|
-
].join("\n");
|
|
222
|
-
|
|
223
|
-
beforeEach(() => {
|
|
224
|
-
accountDir = mkdtempSync(join(tmpdir(), "spec-registry-"));
|
|
225
|
-
mkdirSync(join(accountDir, "specialists", "agents"), { recursive: true });
|
|
226
|
-
writeFileSync(join(accountDir, "specialists", "agents", "live-one.md"), "---\nname: live-one\n---\n");
|
|
227
|
-
writeFileSync(
|
|
228
|
-
join(accountDir, "agents-disabled.json"),
|
|
229
|
-
JSON.stringify({ disabled: ["switched-off.md"] }),
|
|
230
|
-
);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
afterEach(() => rmSync(accountDir, { recursive: true, force: true }));
|
|
234
|
-
|
|
235
|
-
describe("classifySpecialists", () => {
|
|
236
|
-
const run = (chromeUp = true) =>
|
|
237
|
-
classifySpecialists({
|
|
238
|
-
agentsMd: AGENTS_MD,
|
|
239
|
-
specialistsAgentsDir: join(accountDir, "specialists", "agents"),
|
|
240
|
-
accountDir,
|
|
241
|
-
chromeUp,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("reports a live file as ok", () => {
|
|
245
|
-
expect(run().lines).toContain(" live-one: ok");
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("reports a store name as disabled, never as a missing file", () => {
|
|
249
|
-
const report = run();
|
|
250
|
-
expect(report.lines).toContain(" switched-off: disabled (operator)");
|
|
251
|
-
expect(report.lines.join("\n")).not.toMatch(/switched-off: missing file/);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it("reports an absent file the store does not name as a missing file", () => {
|
|
255
|
-
expect(run().lines).toContain(" never-arrived: missing file");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("counts missing and disabled separately", () => {
|
|
259
|
-
const report = run();
|
|
260
|
-
expect(report.registered).toBe(3);
|
|
261
|
-
expect(report.missing).toBe(1);
|
|
262
|
-
expect(report.disabled).toBe(1);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("keeps the chrome degradation for a live personal-assistant", () => {
|
|
266
|
-
writeFileSync(join(accountDir, "specialists", "agents", "personal-assistant.md"), "---\n");
|
|
267
|
-
const report = classifySpecialists({
|
|
268
|
-
agentsMd: "- **specialists:personal-assistant**: Browses.\n",
|
|
269
|
-
specialistsAgentsDir: join(accountDir, "specialists", "agents"),
|
|
270
|
-
accountDir,
|
|
271
|
-
chromeUp: false,
|
|
272
|
-
});
|
|
273
|
-
expect(report.lines).toContain(" personal-assistant: degraded — chrome not running");
|
|
274
|
-
expect(report.missing).toBe(0);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("treats an unreadable store as naming nothing", () => {
|
|
278
|
-
writeFileSync(join(accountDir, "agents-disabled.json"), "{ not json");
|
|
279
|
-
const report = run();
|
|
280
|
-
expect(report.lines).toContain(" switched-off: missing file");
|
|
281
|
-
expect(report.disabled).toBe(0);
|
|
282
|
-
expect(report.missing).toBe(2);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
- [ ] **Step 2: Run the test to verify it fails**
|
|
288
|
-
|
|
289
|
-
Run: `cd platform/plugins/admin/mcp && npx vitest run src/__tests__/specialist-registry.test.ts`
|
|
290
|
-
Expected: FAIL — `Failed to resolve import "../specialist-registry.js"`.
|
|
291
|
-
|
|
292
|
-
- [ ] **Step 3: Write the module**
|
|
293
|
-
|
|
294
|
-
Create `platform/plugins/admin/mcp/src/specialist-registry.ts`:
|
|
295
|
-
|
|
296
|
-
```ts
|
|
297
|
-
// Task 2016 — the specialist half of `system-status`.
|
|
298
|
-
//
|
|
299
|
-
// Disabling an agent MOVES its file out of `specialists/agents/` (Task 1996),
|
|
300
|
-
// so a walk of AGENTS.md against that directory sees an absent file and, before
|
|
301
|
-
// this module, called it `missing file` — an operator action rendered as an
|
|
302
|
-
// install fault. The disabled store is what tells the two apart.
|
|
303
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
304
|
-
import { join, resolve } from "node:path";
|
|
305
|
-
|
|
306
|
-
export interface SpecialistRegistryReport {
|
|
307
|
-
/** One ` <name>: <status>` line per AGENTS.md entry, in file order. */
|
|
308
|
-
lines: string[];
|
|
309
|
-
registered: number;
|
|
310
|
-
missing: number;
|
|
311
|
-
disabled: number;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** Basenames the operator switched off. An unreadable store names nothing:
|
|
315
|
-
* a corrupt file is not evidence that an agent was disabled, and the absent
|
|
316
|
-
* file stays `missing file`, which is the honest answer when the store cannot
|
|
317
|
-
* be read. */
|
|
318
|
-
function readDisabledStore(accountDir: string): Set<string> {
|
|
319
|
-
const p = resolve(accountDir, "agents-disabled.json");
|
|
320
|
-
if (!existsSync(p)) return new Set();
|
|
321
|
-
try {
|
|
322
|
-
const parsed = JSON.parse(readFileSync(p, "utf-8")) as { disabled?: unknown };
|
|
323
|
-
if (!Array.isArray(parsed.disabled)) return new Set();
|
|
324
|
-
return new Set(parsed.disabled.filter((x): x is string => typeof x === "string"));
|
|
325
|
-
} catch {
|
|
326
|
-
return new Set();
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export function classifySpecialists(opts: {
|
|
331
|
-
agentsMd: string;
|
|
332
|
-
specialistsAgentsDir: string;
|
|
333
|
-
accountDir: string;
|
|
334
|
-
chromeUp: boolean;
|
|
335
|
-
}): SpecialistRegistryReport {
|
|
336
|
-
const entries = opts.agentsMd.match(/^- \*\*specialists:([^*]+)\*\*/gm) ?? [];
|
|
337
|
-
const disabled = readDisabledStore(opts.accountDir);
|
|
338
|
-
const lines: string[] = [];
|
|
339
|
-
let missingCount = 0;
|
|
340
|
-
let disabledCount = 0;
|
|
341
|
-
|
|
342
|
-
for (const entry of entries) {
|
|
343
|
-
const nameMatch = entry.match(/specialists:([^*]+)/);
|
|
344
|
-
if (!nameMatch) continue;
|
|
345
|
-
const name = nameMatch[1];
|
|
346
|
-
let status: string;
|
|
347
|
-
if (existsSync(join(opts.specialistsAgentsDir, `${name}.md`))) {
|
|
348
|
-
status = name === "personal-assistant" && !opts.chromeUp
|
|
349
|
-
? "degraded — chrome not running"
|
|
350
|
-
: "ok";
|
|
351
|
-
} else if (disabled.has(`${name}.md`)) {
|
|
352
|
-
status = "disabled (operator)";
|
|
353
|
-
disabledCount++;
|
|
354
|
-
} else {
|
|
355
|
-
status = "missing file";
|
|
356
|
-
missingCount++;
|
|
357
|
-
}
|
|
358
|
-
lines.push(` ${name}: ${status}`);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return { lines, registered: lines.length, missing: missingCount, disabled: disabledCount };
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
- [ ] **Step 4: Run the test to verify it passes**
|
|
366
|
-
|
|
367
|
-
Run: `cd platform/plugins/admin/mcp && npx vitest run src/__tests__/specialist-registry.test.ts`
|
|
368
|
-
Expected: PASS, 6 tests.
|
|
369
|
-
|
|
370
|
-
- [ ] **Step 5: Call the module from `index.ts`**
|
|
371
|
-
|
|
372
|
-
In `platform/plugins/admin/mcp/src/index.ts`, add the import beside the other local imports (near line 23):
|
|
373
|
-
|
|
374
|
-
```ts
|
|
375
|
-
import { classifySpecialists } from "./specialist-registry.js";
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
Replace the body of the `else` branch at lines 515-534 (everything from `const content = readFileSync(agentsMdPath, "utf-8");` to the closing brace of that `else`) with:
|
|
379
|
-
|
|
380
|
-
```ts
|
|
381
|
-
const content = readFileSync(agentsMdPath, "utf-8");
|
|
382
|
-
const report = classifySpecialists({
|
|
383
|
-
agentsMd: content,
|
|
384
|
-
specialistsAgentsDir,
|
|
385
|
-
accountDir,
|
|
386
|
-
chromeUp,
|
|
387
|
-
});
|
|
388
|
-
if (report.registered === 0) {
|
|
389
|
-
checks.specialists = "not configured (AGENTS.md empty)";
|
|
390
|
-
} else {
|
|
391
|
-
checks.specialists =
|
|
392
|
-
`${report.registered} registered, missing=${report.missing} disabled=${report.disabled}\n` +
|
|
393
|
-
report.lines.join("\n");
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
- [ ] **Step 6: Build the plugin**
|
|
398
|
-
|
|
399
|
-
Run: `cd platform && npm run build:admin`
|
|
400
|
-
Expected: exit 0, no TypeScript errors.
|
|
401
|
-
|
|
402
|
-
- [ ] **Step 7: Commit**
|
|
403
|
-
|
|
404
|
-
```bash
|
|
405
|
-
git add maxy-code/platform/plugins/admin/mcp/src/specialist-registry.ts maxy-code/platform/plugins/admin/mcp/src/__tests__/specialist-registry.test.ts maxy-code/platform/plugins/admin/mcp/src/index.ts
|
|
406
|
-
git commit -m "$(cat <<'EOF'
|
|
407
|
-
fix(2016): system-status reports a disabled agent as disabled, not missing
|
|
408
|
-
|
|
409
|
-
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
410
|
-
Session: a2b8f556-8297-4184-bf57-59b078a8015d
|
|
411
|
-
EOF
|
|
412
|
-
)"
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
---
|
|
416
|
-
|
|
417
|
-
### Task 3: Disable and enable reconcile the routing prose immediately
|
|
418
|
-
|
|
419
|
-
**Files:**
|
|
420
|
-
- Modify: `platform/ui/server/routes/admin/agents.ts`
|
|
421
|
-
- Modify: `platform/ui/server/routes/admin/__tests__/agents.test.ts`
|
|
422
|
-
- Modify: `platform/plugins/docs/references/admin-ui.md:209-241`
|
|
423
|
-
|
|
424
|
-
**Interfaces:**
|
|
425
|
-
- Consumes: `bash <PLATFORM_ROOT>/scripts/lib/agents-md-bootstrap.sh <accountDir>` from Task 1.
|
|
426
|
-
- Produces: `POST /disable` returns `{ok, moved, routingReconciled}`; `POST /enable` returns `{ok, restored, routingReconciled}`.
|
|
427
|
-
|
|
428
|
-
- [ ] **Step 1: Write the failing test**
|
|
429
|
-
|
|
430
|
-
In `platform/ui/server/routes/admin/__tests__/agents.test.ts`, the harness already builds a temp `platformRoot` in its `beforeEach`. Add, after the existing `templates/specialists/agents` setup in that same `beforeEach`, a copy of the real bootstrap script so the route execs shipped code rather than a stub:
|
|
431
|
-
|
|
432
|
-
```ts
|
|
433
|
-
// Task 2016 — the disable path execs the real bootstrap out of PLATFORM_ROOT.
|
|
434
|
-
// Five levels up from __tests__ is maxy-code/platform, the same walk
|
|
435
|
-
// no-retired-opus-string.test.ts:18 uses.
|
|
436
|
-
mkdirSync(resolve(platformRoot, 'scripts', 'lib'), { recursive: true })
|
|
437
|
-
cpSync(
|
|
438
|
-
resolve(__dirname, '..', '..', '..', '..', '..', 'scripts', 'lib', 'agents-md-bootstrap.sh'),
|
|
439
|
-
resolve(platformRoot, 'scripts', 'lib', 'agents-md-bootstrap.sh'),
|
|
440
|
-
)
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
Add `cpSync` to the `node:fs` import list at the top of the file.
|
|
444
|
-
|
|
445
|
-
Then add this case at the end of the existing `describe('disable and enable (Task 1996)', …)` block, so it reuses that block's `writeShipped`, `disable` and `enable` helpers (which already target `/api/admin/agents/<slug>/…?accountId=acct-B`):
|
|
446
|
-
|
|
447
|
-
```ts
|
|
448
|
-
// Task 2016 — the routing prose has to follow the file. Before this, the
|
|
449
|
-
// append-only bootstrap kept advertising a disabled specialist and the admin
|
|
450
|
-
// dispatched to a file that was no longer there.
|
|
451
|
-
it('drops the routing line on disable and restores it on enable', async () => {
|
|
452
|
-
writeShipped('citation-auditor', { name: 'citation-auditor', description: 'Audits citations.', tools: 'Read' })
|
|
453
|
-
writeShipped('librarian', { name: 'librarian', description: 'Keeps the library.', tools: 'Read' })
|
|
454
|
-
mkdirSync(resolve(accountDir, 'agents', 'admin'), { recursive: true })
|
|
455
|
-
writeFileSync(
|
|
456
|
-
resolve(accountDir, 'agents', 'admin', 'AGENTS.md'),
|
|
457
|
-
'# Installed Roles\n\n- **specialists:citation-auditor**: Audits citations.\n- **specialists:librarian**: Keeps the library.\n',
|
|
458
|
-
)
|
|
459
|
-
const agentsMd = () => readFileSync(resolve(accountDir, 'agents', 'admin', 'AGENTS.md'), 'utf-8')
|
|
460
|
-
|
|
461
|
-
const off = await disable('citation-auditor')
|
|
462
|
-
expect(off.status).toBe(200)
|
|
463
|
-
expect(await off.json()).toMatchObject({ ok: true, moved: true, routingReconciled: true })
|
|
464
|
-
expect(agentsMd()).not.toContain('- **specialists:citation-auditor**:')
|
|
465
|
-
expect(agentsMd()).toContain('- **specialists:librarian**:')
|
|
466
|
-
|
|
467
|
-
const on = await enable('citation-auditor')
|
|
468
|
-
expect(on.status).toBe(200)
|
|
469
|
-
expect(await on.json()).toMatchObject({ ok: true, restored: true, routingReconciled: true })
|
|
470
|
-
expect(agentsMd()).toContain('- **specialists:citation-auditor**:')
|
|
471
|
-
})
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
- [ ] **Step 2: Run the test to verify it fails**
|
|
475
|
-
|
|
476
|
-
Run: `cd platform/ui && npx vitest run server/routes/admin/__tests__/agents.test.ts -t 'routing prose'`
|
|
477
|
-
Expected: FAIL — the response has no `routingReconciled` field and AGENTS.md still contains the `citation-auditor` line.
|
|
478
|
-
|
|
479
|
-
- [ ] **Step 3: Implement the reconcile call**
|
|
480
|
-
|
|
481
|
-
In `platform/ui/server/routes/admin/agents.ts`, add to the imports:
|
|
482
|
-
|
|
483
|
-
```ts
|
|
484
|
-
import { execFileSync } from 'node:child_process'
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
Add this helper immediately above the `POST /:slug/disable` handler:
|
|
488
|
-
|
|
489
|
-
```ts
|
|
490
|
-
/** Re-derive the admin's routing prose from disk after a disable or an enable.
|
|
491
|
-
*
|
|
492
|
-
* `agents-md-bootstrap.sh` is the single implementation of what belongs in
|
|
493
|
-
* AGENTS.md, so the route calls it rather than editing the file itself. It runs
|
|
494
|
-
* after the move, so enable needs no restore path of its own: the file is live
|
|
495
|
-
* again and the script's append loop puts the line back.
|
|
496
|
-
*
|
|
497
|
-
* A failure here does not fail the request. The move and the store write already
|
|
498
|
-
* happened and dispatch is already stopped, so reporting the whole disable as
|
|
499
|
-
* failed would be false. The caller is told the truth instead, through
|
|
500
|
-
* `routingReconciled`. */
|
|
501
|
-
function reconcileRoutingProse(accountDir: string, accountId: string, op: string, slug: string): boolean {
|
|
502
|
-
const script = resolve(PLATFORM_ROOT, 'scripts', 'lib', 'agents-md-bootstrap.sh')
|
|
503
|
-
try {
|
|
504
|
-
execFileSync('bash', [script, accountDir], { encoding: 'utf-8', timeout: 10_000 })
|
|
505
|
-
return true
|
|
506
|
-
} catch (err) {
|
|
507
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
508
|
-
console.error(`[admin/agents] op=agents-md-reconcile source=${op} accountId=${accountId.slice(0, 8)} slug=${slug} outcome=failed reason="${msg}"`)
|
|
509
|
-
return false
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
In the disable handler, replace the two closing lines (currently `console.log(… outcome=ok moved=${moved})` and `return c.json({ ok: true, moved })`) with:
|
|
515
|
-
|
|
516
|
-
```ts
|
|
517
|
-
const routingReconciled = reconcileRoutingProse(resolved.accountDir, resolved.accountId, 'disable', slug)
|
|
518
|
-
console.log(`[admin/agents] op=disable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} outcome=ok moved=${moved} routingReconciled=${routingReconciled}`)
|
|
519
|
-
return c.json({ ok: true, moved, routingReconciled })
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
In the enable handler, replace its two closing lines the same way:
|
|
523
|
-
|
|
524
|
-
```ts
|
|
525
|
-
const routingReconciled = reconcileRoutingProse(resolved.accountDir, resolved.accountId, 'enable', slug)
|
|
526
|
-
console.log(`[admin/agents] op=enable accountId=${resolved.accountId.slice(0, 8)} slug=${slug} outcome=ok restored=${restored} routingReconciled=${routingReconciled}`)
|
|
527
|
-
return c.json({ ok: true, restored, routingReconciled })
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
- [ ] **Step 4: Run the tests to verify they pass**
|
|
531
|
-
|
|
532
|
-
Run: `cd platform/ui && npx vitest run server/routes/admin/__tests__/agents.test.ts`
|
|
533
|
-
Expected: PASS, including every pre-existing case in that file.
|
|
534
|
-
|
|
535
|
-
- [ ] **Step 5: Update the reference doc**
|
|
536
|
-
|
|
537
|
-
In `platform/plugins/docs/references/admin-ui.md`, after the paragraph ending `a corrupt file is not evidence that an agent was disabled.` (line 224), add:
|
|
538
|
-
|
|
539
|
-
```markdown
|
|
540
|
-
Disabling also reconciles the admin's routing prose. `agents/admin/AGENTS.md`
|
|
541
|
-
carries one `- **specialists:<name>**:` line per dispatchable specialist, and
|
|
542
|
-
`agents-md-bootstrap.sh` used to only ever append, so a disabled agent stayed
|
|
543
|
-
advertised and the admin dispatched to a file that was no longer there. The
|
|
544
|
-
script now withholds a line for every name in the store, and both routes exec it
|
|
545
|
-
after the move, so the prose is right immediately rather than after the next
|
|
546
|
-
provisioning run. It emits `[agents-md] op=specialist-lines account=<id8>
|
|
547
|
-
kept=<n> withheld=<comma-list|none|store-unreadable>`.
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
In the same file, replace the last sentence of the disable row (`Same \`accountId\` contract as delete.`) with:
|
|
551
|
-
|
|
552
|
-
```markdown
|
|
553
|
-
Returns `routingReconciled`, which is `false` when the AGENTS.md reconcile threw — the move and the store write already happened, so the request is not failed, and the caller is told rather than left assuming the prose is correct. Same `accountId` contract as delete.
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
And append to the enable row, after `404 only when neither directory nor store knows the agent.`:
|
|
557
|
-
|
|
558
|
-
```markdown
|
|
559
|
-
Carries the same `routingReconciled` field; the restored file is what puts the routing line back.
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
- [ ] **Step 6: Commit**
|
|
563
|
-
|
|
564
|
-
```bash
|
|
565
|
-
git add maxy-code/platform/ui/server/routes/admin/agents.ts maxy-code/platform/ui/server/routes/admin/__tests__/agents.test.ts maxy-code/platform/plugins/docs/references/admin-ui.md
|
|
566
|
-
git commit -m "$(cat <<'EOF'
|
|
567
|
-
fix(2016): disable and enable reconcile AGENTS.md immediately
|
|
568
|
-
|
|
569
|
-
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
570
|
-
Session: a2b8f556-8297-4184-bf57-59b078a8015d
|
|
571
|
-
EOF
|
|
572
|
-
)"
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
---
|
|
576
|
-
|
|
577
|
-
### Task 4: Whole-surface verification
|
|
578
|
-
|
|
579
|
-
**Files:** none modified. This task runs the checks that prove the three surfaces agree.
|
|
580
|
-
|
|
581
|
-
- [ ] **Step 1: Shell suite**
|
|
582
|
-
|
|
583
|
-
Run: `bash platform/scripts/__tests__/agents-md-bootstrap.test.sh`
|
|
584
|
-
Expected: `FAIL=0`.
|
|
585
|
-
|
|
586
|
-
- [ ] **Step 2: Admin MCP suite and type check**
|
|
587
|
-
|
|
588
|
-
Run: `cd platform && npm run build:admin && cd plugins/admin/mcp && npx vitest run`
|
|
589
|
-
Expected: build exit 0, every test file passes.
|
|
590
|
-
|
|
591
|
-
- [ ] **Step 3: Route suite and type check**
|
|
592
|
-
|
|
593
|
-
Run: `cd platform/ui && npx tsc --noEmit && npx vitest run server/routes/admin/__tests__/agents.test.ts`
|
|
594
|
-
Expected: 0 type errors, all tests pass.
|
|
595
|
-
|
|
596
|
-
- [ ] **Step 4: End-to-end observability check on a temp account**
|
|
597
|
-
|
|
598
|
-
```bash
|
|
599
|
-
ACCT=$(mktemp -d)
|
|
600
|
-
mkdir -p "$ACCT/specialists/agents" "$ACCT/agents/admin"
|
|
601
|
-
printf -- '---\nname: citation-auditor\ndescription: "Audits citations."\n---\n' > "$ACCT/specialists/agents/citation-auditor.md"
|
|
602
|
-
printf -- '---\nname: librarian\ndescription: "Keeps the library."\n---\n' > "$ACCT/specialists/agents/librarian.md"
|
|
603
|
-
bash platform/scripts/lib/agents-md-bootstrap.sh "$ACCT"
|
|
604
|
-
mkdir -p "$ACCT/specialists/agents-disabled"
|
|
605
|
-
mv "$ACCT/specialists/agents/citation-auditor.md" "$ACCT/specialists/agents-disabled/"
|
|
606
|
-
printf '{"disabled":["citation-auditor.md"]}\n' > "$ACCT/agents-disabled.json"
|
|
607
|
-
bash platform/scripts/lib/agents-md-bootstrap.sh "$ACCT"
|
|
608
|
-
grep -c 'specialists:citation-auditor' "$ACCT/agents/admin/AGENTS.md" || echo "no routing line — correct"
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
Expected: the second bootstrap prints `withheld=citation-auditor kept=1`, and the grep finds no line.
|
|
612
|
-
|
|
613
|
-
- [ ] **Step 5: Commit nothing**
|
|
614
|
-
|
|
615
|
-
This task changes no files. If any check failed, fix it in the task that owns the file and re-run.
|
|
616
|
-
|
|
617
|
-
---
|
|
618
|
-
|
|
619
|
-
## Self-review
|
|
620
|
-
|
|
621
|
-
- Spec section "Surface 1" → Task 1. "Surface 2" → Task 2. "Surface 3" → Task 3. "Tests" → the test steps inside Tasks 1-3 plus Task 4. "Out of scope" → nothing implemented against it.
|
|
622
|
-
- Success criterion 1 (disabling removes the line) → Task 1 Step 1 case 7, Task 3 Step 1. Criterion 2 (re-enabling restores it) → Task 1 case 8, Task 3 Step 1. Criterion 3 (`system-status` separates the two) → Task 2 Step 1.
|
|
623
|
-
- Names used consistently across tasks: `classifySpecialists`, `SpecialistRegistryReport`, `reconcileRoutingProse`, `routingReconciled`, `op=specialist-lines`, `op=agents-md-reconcile`.
|
|
624
|
-
- No placeholders: every code step carries the code, every run step carries the command and the expected result.
|