sshlg-skills 0.78.0 → 0.80.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/CHANGELOG.md +177 -0
- package/README.md +9 -8
- package/bin/sshlg-skills.js +28 -3
- package/hooks/repo-gate.js +4 -1
- package/lib/apply.js +27 -14
- package/lib/cursor.js +21 -1
- package/package.json +1 -1
- package/skills.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,182 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.80.0 — a nine-repository audit, and the ten rows it closed
|
|
4
|
+
|
|
5
|
+
**131 findings, three of them blockers**, from nine parallel read-only audits — one per
|
|
6
|
+
repository, against a fixed eight-dimension brief, every finding carrying `file:line` or a
|
|
7
|
+
command and its output. The full set is committed at `docs/evidence/audit-2026-08-16/`;
|
|
8
|
+
what follows is what shipped.
|
|
9
|
+
|
|
10
|
+
### The three blockers
|
|
11
|
+
|
|
12
|
+
**Two were the same module, and the same mistake: a write into the operator's files that
|
|
13
|
+
the backup could not see.** `lib/apply.js`'s `applyCursor` upserted into the `EMPTY_BLOCK`
|
|
14
|
+
constant rather than into the file on disk, so a `--member`-scoped run rebuilt
|
|
15
|
+
`~/.cursor/rules/sshlg-routing.mdc` containing only that member — measured live at **9
|
|
16
|
+
router blocks and 13743 bytes before, 2 and 3233 after**. And `bin/sshlg-skills.js:493`
|
|
17
|
+
wrote the migrated `~/.claude/CLAUDE.md` **before any backup**: the only write to a
|
|
18
|
+
protected file not preceded by `protect()`, reproduced against a scratch HOME with the
|
|
19
|
+
backup directory unwritable — four sections to three lines, no copy anywhere, and the run
|
|
20
|
+
printing *«Файл не изменён»*. `test/apply_test.js` (15 fixtures) is the suite
|
|
21
|
+
`lib/apply.js` never had, which is the gap both lived in; it was watched failing **7 of
|
|
22
|
+
15** against the pre-fix code.
|
|
23
|
+
|
|
24
|
+
**The third was in the family's own standard-keeper.** Both checkers dropped the
|
|
25
|
+
continuation lines of a plain multi-line YAML description, so a description whose real
|
|
26
|
+
length was **1392 characters was measured at 180** and passed the 1024 cap, the 970
|
|
27
|
+
working limit and every `/skill-audit` this family issues. Shipped as `make-skill` v0.20.0
|
|
28
|
+
with `test/checker_parity_test.py`, watched failing 9 of 14.
|
|
29
|
+
|
|
30
|
+
### Ten board rows closed
|
|
31
|
+
|
|
32
|
+
| Row | What it was |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `B-63` | the checker that measured 180 where the file said 1392 |
|
|
35
|
+
| `B-64` | two writes into the operator's files that bypassed `protect()` |
|
|
36
|
+
| `B-65` | `npm test` did not exist in three of nine members |
|
|
37
|
+
| `B-66` | five `SKILL.md` bodies over the 5000-token budget |
|
|
38
|
+
| `B-67` | nine of nine graph reports described a different build |
|
|
39
|
+
| `B-68` | «every graph at HEAD», false in the tree that published it |
|
|
40
|
+
| `B-69` | lightweight release tags reporting members up to seven releases stale |
|
|
41
|
+
| `B-70` | seven of eight verification ledgers behind their shipped version |
|
|
42
|
+
| `B-72` | 246 kB of someone else's bytecode in a published npm tarball |
|
|
43
|
+
| `B-73` `B-74` `B-77` | the missing CAPI contract, a `CONFIRMED` row 30 days from harm, six stale umbrella claims |
|
|
44
|
+
|
|
45
|
+
**No body in the family is over 5000 tokens now**: `task-pipeline` 6685 → **4735**,
|
|
46
|
+
`sheleg-design` 6203 → **4590**, `seo-aeo-audit` 5885 → **4996**, `stripe-billing` 5367 →
|
|
47
|
+
**4748**, `ad-tracking` 5273 → **4643** — split rather than trimmed wherever a file already
|
|
48
|
+
owned the material, and **every routed trigger survived**, checked after each pass.
|
|
49
|
+
|
|
50
|
+
**Six members released**: `make-skill` 0.20.0, `agent-sync` 1.12.0, `seo-aeo-audit` 0.22.0,
|
|
51
|
+
`sheleg-dev` 0.6.0, `sheleg-design` 1.38.0, `task-pipeline` 1.68.0.
|
|
52
|
+
|
|
53
|
+
### Two findings that reversed
|
|
54
|
+
|
|
55
|
+
**The audit was wrong about `task-pipeline`'s description.** It read the member as breaking
|
|
56
|
+
the house rule that a description must open with `Use when …`. That repository's own
|
|
57
|
+
validator refuses exactly that opening, citing Anthropic's guidance and their
|
|
58
|
+
capability-first example — and it is the **shared rule that is wrong**. The correction was
|
|
59
|
+
written and reverted rather than shipped: it flags **22 of 24** family skills, and each
|
|
60
|
+
carries routed phrases that must survive a rewrite verbatim. Filed as `B-76`, unresolved,
|
|
61
|
+
because it is a family decision and not a member's.
|
|
62
|
+
|
|
63
|
+
**And it was wrong about the graphify model.** `F-umbrella-14` claimed the v0.79.0 note
|
|
64
|
+
named a model the machine was not using; the note was right about what built the graphs.
|
|
65
|
+
The real defect was narrower and elsewhere — `~/.config/graphify/env` contradicts itself,
|
|
66
|
+
its comment naming `deepseek-v4-pro` at `$1.17/$2.34` while its export sets
|
|
67
|
+
`deepseek-v4-flash` at `$0.06/$0.12`.
|
|
68
|
+
|
|
69
|
+
### What kept happening
|
|
70
|
+
|
|
71
|
+
**A negative self-test pinned to a literal stops landing the moment the fact it guards is
|
|
72
|
+
reworded** — and then reads green while proving nothing. It happened five times: a date, a
|
|
73
|
+
count, a line break, and twice a guard looking where content used to be. It refused
|
|
74
|
+
`seo-aeo-audit`'s release three times, correctly, and **after the tag was public** — the
|
|
75
|
+
one moment the release workflow cannot recover from. So the check moved to where it is
|
|
76
|
+
read before the tag, and the guard added for it did not see `perl`-form plants, and its
|
|
77
|
+
first widening skipped a file with a comment claiming a loop checked it when no such loop
|
|
78
|
+
existed. All three are fixed and each was watched refusing.
|
|
79
|
+
|
|
80
|
+
### The tag went out pinning a commit that existed only on this machine
|
|
81
|
+
|
|
82
|
+
`v0.80.0`'s first CI run failed at **checkout**, not at a test: `skills/agent-stack`
|
|
83
|
+
was pinned at a chore commit that had been made here and never pushed, and
|
|
84
|
+
`actions/checkout` refused it with *upload-pack: not our ref*. Every consumer of that
|
|
85
|
+
hub commit would have failed the same way.
|
|
86
|
+
|
|
87
|
+
Nothing local could see it. `npm test` was green, and
|
|
88
|
+
`git submodule status | grep -c '^+'` returned **0** — because the pointer matched the
|
|
89
|
+
submodule's **local** head, which is exactly what a forgotten push looks like.
|
|
90
|
+
`task-pipeline`'s stage 10 already states the order (*push the submodule, then commit
|
|
91
|
+
the pointer*) and names the second half as the one that gets forgotten; nothing
|
|
92
|
+
enforced it.
|
|
93
|
+
|
|
94
|
+
`check_no_member_holds_a_commit_the_remote_does_not()` fails the gate when a member
|
|
95
|
+
holds a commit its upstream does not, and discloses instead where no upstream ref
|
|
96
|
+
resolves. Watched refusing a planted local commit.
|
|
97
|
+
|
|
98
|
+
### Also
|
|
99
|
+
|
|
100
|
+
- `test/hooks_e2e_test.js` discovers all nine hooks instead of listing six. The three it
|
|
101
|
+
omitted included `repo-gate.js`, wired from a committed `.claude/settings.json` into
|
|
102
|
+
every clone, where a throw breaks every Bash call in the project. Watched catching a
|
|
103
|
+
planted throw in `statusline.js`.
|
|
104
|
+
- The umbrella requires `scripts.test` of every member, and discloses each member's ledger
|
|
105
|
+
lag, graph-report disagreement and lightweight newest tag.
|
|
106
|
+
- Ratchets recounted by running the command: **32 suites, 562 fixtures**. `24/469` had been
|
|
107
|
+
wrong by 73 before any of this.
|
|
108
|
+
|
|
109
|
+
**A second Claude Code session committed here mid-run**, between 21:46 and 21:55, holding
|
|
110
|
+
no lease and invisible to `agent_sync status`. Nothing was lost and its three commits were
|
|
111
|
+
correct. Filed as `B-75`: the `fs` backend is advisory across processes, and the decision
|
|
112
|
+
is whether it moves.
|
|
113
|
+
|
|
114
|
+
## v0.79.0 — the graphs are current, and a live key was one command from being published
|
|
115
|
+
|
|
116
|
+
**B-51 resolved as *provision*, and the graphs are rebuilt.** All nine, with semantic
|
|
117
|
+
extraction: every one now sits **at HEAD**, where this repository's was 31 commits behind,
|
|
118
|
+
`super-ux` 33, `seo-aeo-audit` 19, `sheleg-design` 12, `task-pipeline` 10.
|
|
119
|
+
|
|
120
|
+
> **Corrected 2026-08-17 (B-68), and the sentence above is left as it shipped.** Two of
|
|
121
|
+
> its numbers do not survive being recomputed. *"Every one now sits at HEAD"* was already
|
|
122
|
+
> false in the tree that published it: `npm test` in that same checkout printed eight of
|
|
123
|
+
> nine **one commit behind**, and for the members that commit `graphify-out` it is false
|
|
124
|
+
> **by construction** — the commit carrying the graph advances HEAD past the commit the
|
|
125
|
+
> graph was built at, so only the umbrella, which gitignores it, can ever read zero. The
|
|
126
|
+
> honest form is the per-member lag the gate already prints, not a blanket claim. And the
|
|
127
|
+
> edge total was **11,894**, not 11,884: summing `len(links)` over the nine `graph.json`
|
|
128
|
+
> files gives 10,140 nodes and 11,894 links, which is also what the concurrent session's
|
|
129
|
+
> own model table recorded an hour later. A release note is a record, so this stands as a
|
|
130
|
+
> correction beneath it rather than a quiet edit of what was published.
|
|
131
|
+
|
|
132
|
+
**Measured cost for the family: $1.05, eleven minutes.** My pre-flight estimate was $0.44
|
|
133
|
+
and it was low by 2.4× — it priced raw input tokens and ignored the per-file prompt
|
|
134
|
+
overhead and the JSON output at $1.60/1M. The number that matters came from one clean run
|
|
135
|
+
on the smallest member measured against OpenRouter's own accounting before and after, then
|
|
136
|
+
extrapolated; the estimate is recorded here beside the measurement rather than quietly
|
|
137
|
+
replaced by it.
|
|
138
|
+
|
|
139
|
+
**A live API key was sitting untracked-but-unignored at this repository's root**, mode 644,
|
|
140
|
+
while a loop that runs `git add -A` after every cycle had been working here all day. It was
|
|
141
|
+
never committed — `git log --all -- .env` is empty — but nothing would have stopped the
|
|
142
|
+
next commit from publishing it. `.env` and `.env.*` are gitignored now in **all nine**
|
|
143
|
+
repositories, not just the one that had the file, because the hole was open in all nine.
|
|
144
|
+
|
|
145
|
+
**Where a key belongs on this machine, and why it is not `.env`.** graphify reads
|
|
146
|
+
`os.environ` only — it has no config file and does not load `.env`, so a key there was
|
|
147
|
+
never going to be read in the first place. It lives in `~/.config/graphify/env`, mode 600,
|
|
148
|
+
sourced from `~/.zshrc` behind a `[ -r … ]` guard so a deleted secrets file cannot break
|
|
149
|
+
the shell. One home per secret.
|
|
150
|
+
|
|
151
|
+
**Two things were measured rather than assumed along the way.** `deepseek/deepseek-chat`
|
|
152
|
+
answers a short prompt through OpenRouter and returns *empty or filtered* on graphify's
|
|
153
|
+
real chunks — twice, 2 of 2 — while `openai/gpt-4.1-mini` completed the same corpus; the
|
|
154
|
+
model is pinned with that reason next to it. And OpenRouter's real rate for that route is
|
|
155
|
+
**$0.40 in / $1.30 out per 1M**, not DeepSeek's direct $0.14/$0.28, which happens to land
|
|
156
|
+
close to graphify's hardcoded `openai` pricing — so `cost.json` is roughly honest here by
|
|
157
|
+
coincidence rather than by design.
|
|
158
|
+
|
|
159
|
+
**Three models were tried before one was pinned, and the two cheap ones failed in
|
|
160
|
+
different ways.**
|
|
161
|
+
|
|
162
|
+
| model | outcome |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `deepseek/deepseek-chat` | *empty or filtered response* on 2 of 2 real chunks, twice, while answering a short prompt perfectly. DeepSeek v4 runs reasoning by default and graphify ships `GRAPHIFY_DISABLE_THINKING` for exactly this. |
|
|
165
|
+
| `deepseek/deepseek-v4-pro` | **$0.35 and 25+ minutes on the smallest member**, no graph written when it was stopped. ~$8 and hours for the family. |
|
|
166
|
+
| `deepseek/deepseek-v4-flash` | cheapest on paper at $0.06/$0.12 and unusable here: completions **truncate at `max_completion_tokens`** on graphify's extraction schema, so the JSON arrives cut off, graphify halves the chunk and retries at depth 0, 1, 2 — and a single `SKILL.md` still truncated alone. |
|
|
167
|
+
| `openai/gpt-4.1-mini` | all nine repositories, **10,140 nodes and 11,894 edges, $1.05, eleven minutes**. |
|
|
168
|
+
|
|
169
|
+
The failure that looked like malformed output was truncation, and the distinction
|
|
170
|
+
mattered: "the model returns bad JSON" suggests a different model, while "the model
|
|
171
|
+
cannot finish this response" suggests a smaller schema or a bigger window. Reading the
|
|
172
|
+
first 200 characters graphify prints is what separated them — the JSON was well-formed
|
|
173
|
+
right up to where it stopped.
|
|
174
|
+
|
|
175
|
+
Total spend for all of it, experiments included: **$1.52**.
|
|
176
|
+
|
|
177
|
+
The staleness disclosure now names its remedy instead of only the lack, and retires itself:
|
|
178
|
+
zero occurrences of its tail once a key is in the shell.
|
|
179
|
+
|
|
3
180
|
## v0.78.0 — an unqualified landing page reaches both crafts
|
|
4
181
|
|
|
5
182
|
**B-57: `сделай лендинг` reached no route at all — and neither did `build a landing page`,
|
package/README.md
CHANGED
|
@@ -34,12 +34,12 @@ scripts. No services, no telemetry, no API keys.
|
|
|
34
34
|
| Skill | Version | What it does |
|
|
35
35
|
|---|---|---|
|
|
36
36
|
| **[super-ux](https://github.com/ssheleg/super-ux)** | 0.41.5 | Scenario-driven UI development. A versioned design chain in `docs/ux/` — the product vision → personas and jobs → user flows → a screens-and-states map with Figma frames → traced scenarios → evidence-backed audits → fix plans, plus `docs/brand/` for how the product speaks. One `/ux` entry point that reaches every skill, two doc-drift linters and a contract doctor. |
|
|
37
|
-
| **[task-pipeline](https://github.com/ssheleg/task-pipeline)** | 1.
|
|
38
|
-
| **[agent-sync](https://github.com/ssheleg/agent-sync)** | 1.
|
|
39
|
-
| **[make-skill](https://github.com/ssheleg/make-skill)** | 0.
|
|
40
|
-
| **[sheleg-design](https://github.com/ssheleg/sheleg-design-skill)** | 1.
|
|
41
|
-
| **[seo-aeo-audit](https://github.com/ssheleg/seo-aeo-audit)** | 0.
|
|
42
|
-
| **[sheleg-dev](https://github.com/ssheleg/sheleg-dev)** | 0.
|
|
37
|
+
| **[task-pipeline](https://github.com/ssheleg/task-pipeline)** | 1.68.0 | Full-cycle delivery orchestrator. An intake grill interrogates the request into a complete brief, then **ten gated stages** carry it — docs, brainstorm and decompose, spec, plan, build, tests, deploy, post-deploy, wiki, acceptance — refusing to advance until each gate passes. Documentation is a deliverable with its own portable gate, and the retrospective it leaves behind is traceable to the commit that earned each lesson. |
|
|
38
|
+
| **[agent-sync](https://github.com/ssheleg/agent-sync)** | 1.12.0 | Several agents, one repository, no collisions. Leases with a TTL so two agents cannot claim the same work, race-free id reservation, a run journal and a generated board — over a pluggable knowledge cloud. The answer to "two sessions just committed over each other". |
|
|
39
|
+
| **[make-skill](https://github.com/ssheleg/make-skill)** | 0.20.0 | A skill that builds skills. Create, retrofit, audit and publish agent skills and Claude Code plugins: conformance to the [Agent Skills](https://agentskills.io/specification) open standard, [Anthropic's platform rules](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) (per-surface runtime limits, the Skills API, evals) and the [Claude Code plugin reference](https://code.claude.com/docs/en/plugins-reference), marketplace layout, version sync, validator + CI, every distribution channel, the review checklist for third-party skills, and what a *skill author* must know about MCP and A2A — the protocols themselves live in `agent-stack`. |
|
|
40
|
+
| **[sheleg-design](https://github.com/ssheleg/sheleg-design-skill)** | 1.38.0 | The taste layer. Cinematic scroll-driven landing pages — one scroll clock, motion that degrades to calm, WebGL particle formations — plus product-UI style packs each shipping a ready token layer, and the Figma border: tokens as variables, design to code without hand-copied values. |
|
|
41
|
+
| **[seo-aeo-audit](https://github.com/ssheleg/seo-aeo-audit)** | 0.22.0 | Evidence-first website audit for search **and** answer engines. Ten tracks from crawl access to AI citation mechanics; every finding carries an observation, every recommendation an evidence tier, and the output is a prioritized change plan plus a link-building brief — not a score. |
|
|
42
|
+
| **[sheleg-dev](https://github.com/ssheleg/sheleg-dev)** | 0.6.0 | The integration layer a product reaches once it has users: **money in, tracking, sign-in, speed**. Stripe subscription billing reconciled into your own database — webhook idempotency, renewals, seats and proration, refunds, price drift; crypto payments that survive under-payment, duplicate webhooks and rate drift; GA4, Google Ads, Meta and LinkedIn under Consent Mode v2; Google sign-in with the account pre-hijacking guard, and the server-side auth surface behind it; Core Web Vitals work that moves the score rather than the report. |
|
|
43
43
|
| **[agent-stack](https://github.com/ssheleg/agent-stack)** | 0.11.1 | Production patterns for agent systems, in four skills. **The orchestrator** — tool-calling loops that survive their own context pressure, pipelines with human checkpoints and resume, provider routing with fallback and health checks, four memory layers with confidence decay and conflict resolution, and **the shape of the work decided before the work**: an edge that carries no data is no edge, a plan that declares its dependencies is executed in layers rather than in list order, and a parallel layer gets a checker before the node that consumes it. **The harness** — what the agent is *told*: system prompts at the right altitude, tool descriptions the model can act on, technique choice with a verdict each, workflow versus agent and static versus dynamic, plus a seven-track audit of an agent somebody else built and a scanner for the defects that are mechanically visible. **The evals** — suites that measure whether it actually works: judging trajectories, regression fixtures grown from production, judges calibrated before they are trusted. **The interop layer** — MCP at revision 2026-07-28 and the four features it deprecated, running many servers at once, the registry, A2A 1.0, and the gateway, every reference carrying the spec revision it was read against. Plus the wallet side of reselling LLM access: tiered balances, one markup boundary, two-phase commit against a provider API. |
|
|
44
44
|
|
|
45
45
|
They compose. `task-pipeline` hands user-facing work to `super-ux` and takes its
|
|
@@ -335,7 +335,7 @@ it. They apply only here and only in Claude Code:
|
|
|
335
335
|
| `PreToolUse` on `Bash` | a `git commit` runs `npm test` first and is **refused** while it is red, with the failing tail in the reason |
|
|
336
336
|
| `PostToolUse` on `Edit\|Write` | a `SKILL.md` breaking the Agent Skills front-matter limits is reported in the turn it was written |
|
|
337
337
|
|
|
338
|
-
The commit gate is honest only because of a number: the suite costs
|
|
338
|
+
The commit gate is honest only because of a number: the suite costs **~8.5 s**
|
|
339
339
|
here. At three minutes it would be a gate people route around, and a gate people
|
|
340
340
|
route around is worse than none — it teaches that gates are noise.
|
|
341
341
|
|
|
@@ -362,7 +362,8 @@ get wrong: one channel per agent, exact agent ids, repeated `--agent` flags, ful
|
|
|
362
362
|
```
|
|
363
363
|
skills.json registry — repos, plugin ids, skill names, pins
|
|
364
364
|
skills/* the eight skills as pinned git submodules
|
|
365
|
-
bin/sshlg-skills.js the launcher (install / update / routers / config /
|
|
365
|
+
bin/sshlg-skills.js the launcher (install / update / routers / config / hooks /
|
|
366
|
+
injectors / list / agents)
|
|
366
367
|
lib/routers-registry.js the eight routers — text, table row and required members, in one entry
|
|
367
368
|
lib/routers.js block parsing and rendering; touches no file, by construction
|
|
368
369
|
lib/drift.js your wording vs the packaged one; pure, like routers.js
|
package/bin/sshlg-skills.js
CHANGED
|
@@ -489,13 +489,38 @@ function cmdRouters(f) {
|
|
|
489
489
|
if (!fs.existsSync(file)) continue;
|
|
490
490
|
const src = fs.readFileSync(file, 'utf8');
|
|
491
491
|
const moved = migrate.migrate(src, { fallbacks: packaged });
|
|
492
|
-
|
|
493
|
-
|
|
492
|
+
const wants = moved.text !== src && decision === 'yes' && !f.dryRun;
|
|
493
|
+
// Nothing to write counts as nothing left undone; only a wanted write can
|
|
494
|
+
// fail to happen.
|
|
495
|
+
let wrote = !wants;
|
|
496
|
+
if (wants) {
|
|
497
|
+
// Through `protect()`, like every other write to a file the operator owns
|
|
498
|
+
// and did not write. For eight releases this one was not, and it was the
|
|
499
|
+
// only write in the pack that could destroy `~/.claude/CLAUDE.md` leaving
|
|
500
|
+
// no copy anywhere: reproduced 2026-08-16 against a scratch HOME with the
|
|
501
|
+
// backup directory unwritable, the file went from four sections to three
|
|
502
|
+
// lines while the run printed «Файл не изменён». The house rule names the
|
|
503
|
+
// shape — there is no second write path — and this was the second one.
|
|
504
|
+
const saved = apply.protect(file, { home });
|
|
505
|
+
if (saved.action === 'backup-failed') {
|
|
506
|
+
log(`${file} — НЕ записан: не удалось сделать резервную копию ` +
|
|
507
|
+
`(${saved.error}). Файл не изменён. Освободи ` +
|
|
508
|
+
`~/.sshlg-skills/backups/ и повтори.`);
|
|
509
|
+
} else {
|
|
510
|
+
fs.writeFileSync(file, moved.text, 'utf8');
|
|
511
|
+
wrote = true;
|
|
512
|
+
}
|
|
494
513
|
}
|
|
495
514
|
// Hand the migrated text on rather than letting apply re-read the file:
|
|
496
515
|
// on a dry run nothing was written, and re-reading would preview the
|
|
497
516
|
// additions without the removals that come with them.
|
|
498
|
-
|
|
517
|
+
//
|
|
518
|
+
// But only what is true of the disk now. A refused write means the
|
|
519
|
+
// operator's headings are still in the file, so passing the migrated text
|
|
520
|
+
// would hand `apply` a baseline in which they had already gone — and it
|
|
521
|
+
// would then compute a diff against a state that does not exist.
|
|
522
|
+
sources[file] = decision === 'yes' && wrote ? moved.text : src;
|
|
523
|
+
if (!wrote) continue;
|
|
499
524
|
Object.assign(packaged, moved.routers);
|
|
500
525
|
Object.assign(superseded, moved.superseded);
|
|
501
526
|
// Whatever migration just moved is the operator's, and this is the only
|
package/hooks/repo-gate.js
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* Agent Skills limits is reported in the same turn it was written. `agent-stack`
|
|
15
15
|
* spent days red on exactly that class, diagnosed from another repository.
|
|
16
16
|
*
|
|
17
|
-
* **The gate is only honest because of a number.** `npm test` costs
|
|
17
|
+
* **The gate is only honest because of a number.** `npm test` costs ~8.5 s here
|
|
18
|
+
* (8.29 / 8.57 / 8.83 s, three runs, 2026-08-16). It was written as 3.3 s and
|
|
19
|
+
* never recomputed; the argument survives the correction, which is the only
|
|
20
|
+
* reason this is a number and not a feeling.
|
|
18
21
|
* A synchronous gate at three minutes is a gate people route around, and this
|
|
19
22
|
* file would then be teaching the habit it exists to prevent.
|
|
20
23
|
*
|
package/lib/apply.js
CHANGED
|
@@ -209,10 +209,19 @@ function apply(opts) {
|
|
|
209
209
|
/**
|
|
210
210
|
* Cursor, whose rules are one file each rather than a block inside a file.
|
|
211
211
|
*
|
|
212
|
-
* The body is assembled by running the ordinary `upsert
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
212
|
+
* The body is assembled by running the ordinary `upsert`, so the map, the
|
|
213
|
+
* router sections and the table come from exactly the same code that writes the
|
|
214
|
+
* other three targets — a second renderer would be a second truth about what
|
|
215
|
+
* the block says.
|
|
216
|
+
*
|
|
217
|
+
* It upserts into the body ALREADY THERE, never into the empty template. The
|
|
218
|
+
* distinction is the whole file: `opts.routers` holds one member's routers on a
|
|
219
|
+
* `--member` run, so building from the constant emits a file containing only
|
|
220
|
+
* that member and silently deletes the rest. Measured 2026-08-16 on a live
|
|
221
|
+
* machine — `npx super-ux --cursor` took `~/.cursor/rules/sshlg-routing.mdc`
|
|
222
|
+
* from nine router blocks and 13743 bytes to two and 3233, and the only
|
|
223
|
+
* surviving copy was the backup taken twenty lines below. The three markdown
|
|
224
|
+
* targets never had the bug because `applyOne` upserts into `before`.
|
|
216
225
|
*
|
|
217
226
|
* Skipped entirely where `~/.cursor/rules` does not exist: the directory is
|
|
218
227
|
* how Cursor says it is installed, and creating it would be this pack deciding
|
|
@@ -224,30 +233,34 @@ function applyCursor(opts) {
|
|
|
224
233
|
const file = path.join(dir, cursor.FILENAME);
|
|
225
234
|
if (!fs.existsSync(dir)) return { file, action: 'agent-absent', agent: 'cursor' };
|
|
226
235
|
|
|
227
|
-
const built = R.upsert(EMPTY_BLOCK, opts.routers || {}, {
|
|
228
|
-
members: opts.members || [],
|
|
229
|
-
remove: opts.remove || [],
|
|
230
|
-
preserve: opts.preserve || [],
|
|
231
|
-
});
|
|
232
|
-
|
|
233
236
|
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;
|
|
234
237
|
if (!cursor.mayWrite(existing, R.BEGIN)) {
|
|
235
238
|
return { file, action: 'foreign-file', agent: 'cursor' };
|
|
236
239
|
}
|
|
237
240
|
|
|
241
|
+
const built = R.upsert(existing === null ? EMPTY_BLOCK : cursor.bodyOf(existing), opts.routers || {}, {
|
|
242
|
+
members: opts.members || [],
|
|
243
|
+
remove: opts.remove || [],
|
|
244
|
+
preserve: opts.preserve || [],
|
|
245
|
+
});
|
|
246
|
+
const removed = built.removed || {};
|
|
247
|
+
|
|
238
248
|
const next = cursor.renderRule(built.text);
|
|
239
|
-
if (existing === next) return { file, action: 'unchanged', agent: 'cursor' };
|
|
249
|
+
if (existing === next) return { file, action: 'unchanged', agent: 'cursor', removed };
|
|
240
250
|
if (opts.dryRun) {
|
|
241
|
-
return { file, action: 'would-write', agent: 'cursor', diff: R.diff(existing || '', next) };
|
|
251
|
+
return { file, action: 'would-write', agent: 'cursor', diff: R.diff(existing || '', next), removed };
|
|
242
252
|
}
|
|
243
253
|
const saved = protect(file, opts);
|
|
244
254
|
if (saved.action === 'backup-failed') {
|
|
245
|
-
return { file, action: 'backup-failed', agent: 'cursor', backup: saved };
|
|
255
|
+
return { file, action: 'backup-failed', agent: 'cursor', backup: saved, removed };
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
fs.mkdirSync(dir, { recursive: true });
|
|
249
259
|
fs.writeFileSync(file, next, 'utf8');
|
|
250
|
-
|
|
260
|
+
// `removed` travels with the record so the caller parks what left the block.
|
|
261
|
+
// Without it a section deleted here was gone for good, while the same section
|
|
262
|
+
// deleted from CLAUDE.md was stashed and restorable.
|
|
263
|
+
return { file, action: existing === null ? 'created' : 'updated', agent: 'cursor', removed, backup: saved };
|
|
251
264
|
}
|
|
252
265
|
|
|
253
266
|
// `protect` is exported so that new code writing to an operator-owned file uses
|
package/lib/cursor.js
CHANGED
|
@@ -41,6 +41,26 @@ function renderRule(body) {
|
|
|
41
41
|
].join('\n');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* The inverse of `renderRule` — the block body out of a rule file.
|
|
46
|
+
*
|
|
47
|
+
* Needed because the writer must upsert into what is already there. The file is
|
|
48
|
+
* ours end to end, so its body IS the block; rebuilding it from the empty
|
|
49
|
+
* template instead is how a `--member` run, which carries only that member's
|
|
50
|
+
* routers, deletes every other member's section.
|
|
51
|
+
*
|
|
52
|
+
* A file with no front-matter fence is returned whole: it still carries the
|
|
53
|
+
* sentinels `mayWrite` checked for, and `upsert` is the thing that decides what
|
|
54
|
+
* a malformed block means — not this function.
|
|
55
|
+
*/
|
|
56
|
+
function bodyOf(text) {
|
|
57
|
+
const s = String(text === null || text === undefined ? '' : text);
|
|
58
|
+
if (s.slice(0, 4) !== '---\n') return s;
|
|
59
|
+
const end = s.indexOf('\n---\n', 3);
|
|
60
|
+
if (end === -1) return s;
|
|
61
|
+
return s.slice(end + 5).replace(/^\n+/, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
/**
|
|
45
65
|
* May this pack write here?
|
|
46
66
|
*
|
|
@@ -54,4 +74,4 @@ function mayWrite(existing, beginMark) {
|
|
|
54
74
|
return String(existing).indexOf(beginMark) !== -1;
|
|
55
75
|
}
|
|
56
76
|
|
|
57
|
-
module.exports = { FILENAME, DESCRIPTION, renderRule, mayWrite };
|
|
77
|
+
module.exports = { FILENAME, DESCRIPTION, renderRule, bodyOf, mayWrite };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sshlg-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.0",
|
|
4
4
|
"description": "One launcher/updater for the ssheleg skill family (super-ux, task-pipeline, agent-sync, make-skill, sheleg-design, seo-aeo-audit, sheleg-dev, agent-stack) across every agent — Claude Code, Cursor, OpenCode, Kilo, Kimi, Hermes, OpenClaw, Codex, and more.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sshlg-skills": "bin/sshlg-skills.js"
|
package/skills.json
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"task-pipeline",
|
|
50
50
|
"evidence-docs"
|
|
51
51
|
],
|
|
52
|
-
"version": "1.
|
|
52
|
+
"version": "1.68.0",
|
|
53
53
|
"entry": "/task-pipeline",
|
|
54
54
|
"role": "how a change reaches the repository",
|
|
55
55
|
"npm": "task-pipeline-skill",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"skillNames": [
|
|
67
67
|
"agent-sync"
|
|
68
68
|
],
|
|
69
|
-
"version": "1.
|
|
69
|
+
"version": "1.12.0",
|
|
70
70
|
"entry": "/agent-sync",
|
|
71
71
|
"role": "who is holding this file right now",
|
|
72
72
|
"npm": "@ssheleg/agent-sync",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"skillNames": [
|
|
84
84
|
"make-skill"
|
|
85
85
|
],
|
|
86
|
-
"version": "0.
|
|
86
|
+
"version": "0.20.0",
|
|
87
87
|
"entry": "/skill-audit",
|
|
88
88
|
"role": "how the skill or plugin itself is built",
|
|
89
89
|
"npm": "@ssheleg/make-skill",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"skillNames": [
|
|
101
101
|
"sheleg-design"
|
|
102
102
|
],
|
|
103
|
-
"version": "1.
|
|
103
|
+
"version": "1.38.0",
|
|
104
104
|
"entry": "/sheleg-design",
|
|
105
105
|
"role": "how it looks and moves",
|
|
106
106
|
"npm": "sheleg-design-skill",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"skillNames": [
|
|
118
118
|
"seo-aeo-audit"
|
|
119
119
|
],
|
|
120
|
-
"version": "0.
|
|
120
|
+
"version": "0.22.0",
|
|
121
121
|
"entry": "/seo-aeo-audit",
|
|
122
122
|
"role": "whether a machine will find it",
|
|
123
123
|
"npm": "@ssheleg/seo-aeo-audit",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"google-auth",
|
|
140
140
|
"frontend-performance"
|
|
141
141
|
],
|
|
142
|
-
"version": "0.
|
|
142
|
+
"version": "0.6.0",
|
|
143
143
|
"entry": null,
|
|
144
144
|
"role": "integrations: money in, tracking, sign-in, speed",
|
|
145
145
|
"npm": "@ssheleg/sheleg-dev",
|