sshlg-skills 1.63.2 → 1.64.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 +63 -0
- package/README.md +32 -1
- package/bin/sshlg-skills.js +53 -15
- package/claude-plugin-pins.json +44 -39
- package/lib/blocks.js +36 -4
- package/lib/blockwriter.js +504 -0
- package/lib/claude-pin-transaction.js +6 -0
- package/lib/claude-pinned.js +20 -9
- package/lib/context.js +67 -0
- package/lib/json-spans.js +1 -1
- package/lib/mcpsecrets.js +12 -7
- package/lib/runtime.js +45 -2
- package/lib/triggers.js +3 -4
- package/lib/wait-rule-text.js +8 -4
- package/lib/wait-rules.js +13 -7
- package/package.json +1 -1
- package/skills.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
+
## v1.64.0 — fewer tokens before the first word; work stops at its finish line
|
|
2
|
+
|
|
3
|
+
- `npx sshlg-skills context` measures what every host loads before the first user word:
|
|
4
|
+
bytes, approximate tokens and lines per instruction file, the hub's blocks against other
|
|
5
|
+
text, and documented caps. It reports a Codex instruction chain over `project_doc_max_bytes`
|
|
6
|
+
(Codex stops adding instruction files there; the default is 32 KiB) as an error and a
|
|
7
|
+
`CLAUDE.md` over Claude Code's 200-line target as a note. Read-only; prints sizes, never text.
|
|
8
|
+
- The routing text may fall and never grow by accident: `routing-registry-total` in DOCMAP is
|
|
9
|
+
checked on every run. The per-prompt routing note is one header line shorter.
|
|
10
|
+
- The wait rule gains the finish line (revision wait-v2): continuing is not polishing — a unit
|
|
11
|
+
ends when its exit checks are green and two rounds found nothing blocking; a finding that
|
|
12
|
+
blocks nothing becomes a board row. It also says a board row addressed to you is a next
|
|
13
|
+
task, never to end a turn on "shall I continue?", to release a lease as soon as the wait
|
|
14
|
+
clears, and lists spend above the limit and the operator's recorded decisions as human gates
|
|
15
|
+
(B-196, B-197). Profiles that applied wait-v1 are upgraded in place, and
|
|
16
|
+
a crash left by a wait-v1 launcher still recovers. A record from a newer launcher names
|
|
17
|
+
`npx --yes sshlg-skills@latest` as the remedy. Launchers up to 1.63.3 cannot read a wait-v2
|
|
18
|
+
receipt; update with `@latest`.
|
|
19
|
+
- task-pipeline is pinned at 1.96.0 (`2674147`): an iteration fixes its finish line at intake,
|
|
20
|
+
triages every finding as blocking, deferred or noise, and stops after two rounds with nothing
|
|
21
|
+
blocking or when its round and release budget is spent; `waves.py finish` computes the verdict.
|
|
22
|
+
- New working rule: [context economy](docs/working-rules/context-economy.md) — what belongs in
|
|
23
|
+
always-loaded instructions, one home per rule, host caps, and stopping by contract.
|
|
24
|
+
- **`blocks install` / `uninstall` / `resolve` — a writer other tools can call for their
|
|
25
|
+
own block (managed-blocks/1).** Preview by default; with `--apply` the host file is
|
|
26
|
+
copied to `~/.sshlg-skills/backups/`, only the named block changes, and every step is
|
|
27
|
+
journaled in `~/.sshlg-skills/managed-blocks/journal.jsonl` (hash-chained, no block
|
|
28
|
+
text). It refuses the hub's own blocks, `SSHLG:REPOSITORY-HANDOFF` (owner undecided,
|
|
29
|
+
B-174), other `SSHLG:` names and an `--owner` that differs from the marker's prefix.
|
|
30
|
+
A `journal.head` beside the journal catches cut-off trailing records; every failure
|
|
31
|
+
answers in one JSON/text shape that names `blocks resolve` when a write may have landed.
|
|
32
|
+
Install adds exactly the block and its line ending (CRLF in CRLF files) and uninstall
|
|
33
|
+
takes exactly those back; a result that would not parse, a read-only file and a
|
|
34
|
+
dropped in-block edit are refused or warned about before the write.
|
|
35
|
+
The convention is `docs/working-rules/managed-blocks.md`. `blocks --json` now names each
|
|
36
|
+
block's registered owner and writer; `OBSERVATORY:AGENT-UPDATES` is registered as
|
|
37
|
+
Observatory's. `blocks --help` and `blocks <verb> --help` answer.
|
|
38
|
+
- **`blocks` ignores markers inside closed Markdown code fences:** an example of a block
|
|
39
|
+
in prose is no longer listed or treated as a stray marker.
|
|
40
|
+
- **The DigitalOcean credential refusal leads with the step that needs no Observatory**
|
|
41
|
+
(a file with mode 600), with the vault named as the alternative. A new suite,
|
|
42
|
+
`test/remedies_test.js`, holds every secret-guard refusal to an Observatory-free step
|
|
43
|
+
before any Observatory step (USR-011); it reads only the remedy part of each refusal, every
|
|
44
|
+
`rule` key, bare or quoted, refusing a spelling it cannot read (a variable, the
|
|
45
|
+
`{ rule }` shorthand), and every deny site of the PreToolUse hook.
|
|
46
|
+
|
|
47
|
+
## v1.63.3 — pinned sources for every Claude member; blocked members no longer stop the run
|
|
48
|
+
|
|
49
|
+
- A Claude member whose installed bytes already equal the pin is now rebound to the hub's
|
|
50
|
+
pinned catalog: its marketplace source moves to the pin, with no install or uninstall, and
|
|
51
|
+
the run verifies that its install records did not change and its bytes still equal the pin.
|
|
52
|
+
A later native update therefore follows the pin, not upstream (B-191).
|
|
53
|
+
- A member the native contract cannot change — installed only at project or local scope,
|
|
54
|
+
in user plus other scopes with different bytes, or a configured plugin — is reported as
|
|
55
|
+
blocked and keeps its installed version, while the other members and every later channel
|
|
56
|
+
(plain skills, runtime, hooks, Codex) proceed; the result is partial and exits nonzero.
|
|
57
|
+
Malformed install records still stop the whole run (B-192).
|
|
58
|
+
- The hook runtime records which bytes the hub installed (`.managed.json`). A library module a
|
|
59
|
+
later package drops is moved to `.retired/<stamp>/` only while it still has those bytes, and
|
|
60
|
+
`update`/`hooks install` print what moved; hook scripts, edited, foreign or older unrecorded
|
|
61
|
+
files stay where they are (B-193).
|
|
62
|
+
- Inserting into a non-empty object of a tab-indented JSON file keeps tabs (B-194).
|
|
63
|
+
|
|
1
64
|
## v1.63.2 — the Claude transaction keeps settings in Claude Code's format
|
|
2
65
|
|
|
3
66
|
- Replacing a marketplace source in `~/.claude/settings.json` wrote the new object on one
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ scripts. No services, no telemetry, no API keys.
|
|
|
44
44
|
| Skill | Version | What it does |
|
|
45
45
|
|---|---|---|
|
|
46
46
|
| **[super-ux](https://github.com/ssheleg/super-ux)** | 0.59.3 | 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. |
|
|
47
|
-
| **[task-pipeline](https://github.com/ssheleg/task-pipeline)** | 1.
|
|
47
|
+
| **[task-pipeline](https://github.com/ssheleg/task-pipeline)** | 1.96.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. Ships `project-audit`: a cold-start audit of a whole project — discovery, probes whose unmet needs read `blind` rather than clean, production evidence including one artefact served under two channels, and an HTML report beside a JSON sidecar that says what moved. |
|
|
48
48
|
| **[agent-sync](https://github.com/ssheleg/agent-sync)** | 1.22.2 | 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". |
|
|
49
49
|
| **[make-skill](https://github.com/ssheleg/make-skill)** | 0.31.2 | 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`. |
|
|
50
50
|
| **[sheleg-design](https://github.com/ssheleg/sheleg-design-skill)** | 1.64.2 | 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. |
|
|
@@ -861,6 +861,17 @@ Credentials go through Project Observatory (`observatory_credentials`, `use_secr
|
|
|
861
861
|
--vault-only`); the family's secret guard names an Observatory-free route in every refusal
|
|
862
862
|
as well. Observatory: <https://github.com/passioncode-ai/project-observatory-dashboard>.
|
|
863
863
|
|
|
864
|
+
### What every agent loads — `context`
|
|
865
|
+
|
|
866
|
+
```bash
|
|
867
|
+
npx sshlg-skills context # per host: bytes, ~tokens, lines, hub-managed vs other text, caps
|
|
868
|
+
npx sshlg-skills context --json
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
Read-only. Exits 1 when a host file is over a documented cap (Codex stops adding instruction
|
|
872
|
+
files at `project_doc_max_bytes`, 32 KiB by default) and notes a `CLAUDE.md` above Claude Code's
|
|
873
|
+
200-line target. The rules behind it: [context economy](docs/working-rules/context-economy.md).
|
|
874
|
+
|
|
864
875
|
## Other commands
|
|
865
876
|
|
|
866
877
|
```bash
|
|
@@ -1028,3 +1039,23 @@ The source must contain one complete matching outer block. Output contains only
|
|
|
1028
1039
|
the selected block's before/after text, so treat that output as local material.
|
|
1029
1040
|
The command does not apply the diff. Unknown/foreign blocks remain inspectable;
|
|
1030
1041
|
REPOSITORY-HANDOFF ownership is still a separate operator decision.
|
|
1042
|
+
|
|
1043
|
+
### Write another tool's block (managed-blocks/1)
|
|
1044
|
+
|
|
1045
|
+
A tool that keeps its own block in a host instruction file — Observatory's
|
|
1046
|
+
`OBSERVATORY:AGENT-UPDATES`, for one — can let the hub write it:
|
|
1047
|
+
|
|
1048
|
+
```bash
|
|
1049
|
+
npx sshlg-skills blocks install OBSERVATORY:AGENT-UPDATES --owner OBSERVATORY --agent claude --from ./block.md
|
|
1050
|
+
npx sshlg-skills blocks install OBSERVATORY:AGENT-UPDATES --owner OBSERVATORY --agent claude --from ./block.md --apply
|
|
1051
|
+
npx sshlg-skills blocks uninstall OBSERVATORY:AGENT-UPDATES --owner OBSERVATORY --agent claude --apply
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
Without `--apply` it previews and writes nothing. With it, the file is copied to
|
|
1055
|
+
`~/.sshlg-skills/backups/` first, only the named block changes, and each step is
|
|
1056
|
+
recorded in `~/.sshlg-skills/managed-blocks/journal.jsonl`. It writes `claude`,
|
|
1057
|
+
`codex`, `gemini` and `pi` files, never installs a host, and refuses the hub's own
|
|
1058
|
+
blocks (they have their own writers), `SSHLG:REPOSITORY-HANDOFF` (no owner decided)
|
|
1059
|
+
and any block whose owner prefix differs from `--owner`. An interrupted write is
|
|
1060
|
+
classified with `npx sshlg-skills blocks resolve <operation-id>`. The convention:
|
|
1061
|
+
[docs/working-rules/managed-blocks.md](docs/working-rules/managed-blocks.md).
|
package/bin/sshlg-skills.js
CHANGED
|
@@ -293,6 +293,7 @@ Usage:
|
|
|
293
293
|
# Pi, OpenCode, Hermes; not Claude/Codex)
|
|
294
294
|
npx sshlg-skills hooks [status] # what is wired, and what holds it
|
|
295
295
|
npx sshlg-skills hooks install [--force] [--dry-run]
|
|
296
|
+
npx sshlg-skills context [--json] # what each host loads before the first word; caps
|
|
296
297
|
npx sshlg-skills hooks recovery inspect [--json]
|
|
297
298
|
npx sshlg-skills hooks recovery recover --expect <digest|absent> [--json]
|
|
298
299
|
npx sshlg-skills hooks remove
|
|
@@ -312,7 +313,7 @@ Usage:
|
|
|
312
313
|
npx sshlg-skills visibility marks [--init --show <owner/repo,…> [--replace] [--apply]]
|
|
313
314
|
# this machine's pack marks
|
|
314
315
|
# (~/.sshlg-skills/pack-marks.json)
|
|
315
|
-
npx sshlg-skills blocks [--agent claude|codex|gemini|pi|cursor] [--json]\n npx sshlg-skills blocks diff OWNER:NAME --from <local-file> --agent <host>\n # read-only markers and selected local diff\n npx sshlg-skills rules [--apply] [--agent codex|gemini|pi|cursor] [--claude-only]\n # opt-in wait rule; preview unless --apply\n npx sshlg-skills rules recovery inspect [--json]\n npx sshlg-skills rules recovery finalize --operation <id> [--apply] [--json]\n # metadata only; never restores host instructions\n npx sshlg-skills domains [--apply | --revert] [--root <dir>] [--no-usage]
|
|
316
|
+
npx sshlg-skills blocks [--agent claude|codex|gemini|pi|cursor] [--json]\n npx sshlg-skills blocks diff OWNER:NAME --from <local-file> --agent <host>\n # read-only markers and selected local diff\n npx sshlg-skills blocks install|uninstall OWNER:NAME --owner OWNER --agent <host> [--from <file>] [--apply]\n npx sshlg-skills blocks resolve <operation-id>\n # managed-blocks/1 writer for a tool's own block; preview unless --apply\n npx sshlg-skills rules [--apply] [--agent codex|gemini|pi|cursor] [--claude-only]\n # opt-in wait rule; preview unless --apply\n npx sshlg-skills rules recovery inspect [--json]\n npx sshlg-skills rules recovery finalize --operation <id> [--apply] [--json]\n # metadata only; never restores host instructions\n npx sshlg-skills domains [--apply | --revert] [--root <dir>] [--no-usage]
|
|
316
317
|
npx sshlg-skills domains --enable|--disable <member> [--project <dir>]
|
|
317
318
|
# xr-dev, web3d-dev, telegram-dev; hook-bearing members stay global
|
|
318
319
|
# load only in projects that call for them
|
|
@@ -407,8 +408,9 @@ function withClaudePlan(steps, f) {
|
|
|
407
408
|
steps.unshift(opres.step('claude-pins', 'home',
|
|
408
409
|
'native immutable source transaction; protected catalogs/settings, scoped repair, rollback on failure, payload readback',
|
|
409
410
|
{ fingerprint: native.fingerprint, profile: native.profile,
|
|
410
|
-
members: native.jobs.map(j => `${j.pluginInstall}: ${j.action} @ ${j.row.commit}; ${j.row.files.length} files`)
|
|
411
|
-
.concat(native.equivalent.map(e => `${e.pluginInstall}: payload-equivalent
|
|
411
|
+
members: native.jobs.filter(j => j.action !== 'rebind').map(j => `${j.pluginInstall}: ${j.action} @ ${j.row.commit}; ${j.row.files.length} files`)
|
|
412
|
+
.concat(native.equivalent.map(e => `${e.pluginInstall}: payload-equivalent (installed ${String(e.installed).slice(0, 7)}, pin ${e.pin.slice(0, 7)}; ${e.files} files equal) — source rebind, no reinstall`))
|
|
413
|
+
.concat(native.blocked.map(b => `${b.pluginInstall}: blocked, left as installed — ${b.reason}`)),
|
|
412
414
|
journal: path.join(native.transactionRoot, 'latest.json') }));
|
|
413
415
|
}
|
|
414
416
|
const result = attachWaitPlan(steps, f);
|
|
@@ -419,26 +421,31 @@ function runClaudePlan(planned, f) {
|
|
|
419
421
|
const saved = claudePlans.get(planned);
|
|
420
422
|
if (!saved) {
|
|
421
423
|
log(`\n== Updating Claude Code plugins ==\n skipped: ${claudeScope(f).skippedBy} (the Claude host is outside this run's scope)`);
|
|
422
|
-
return true;
|
|
424
|
+
return { ok: true, partial: false };
|
|
423
425
|
}
|
|
424
|
-
for (const e of saved.native.equivalent) log(` ${e.member || e.name}: payload equals pin ${e.pin.slice(0, 7)} (${e.files} files);
|
|
426
|
+
for (const e of saved.native.equivalent) log(` ${e.member || e.name}: payload equals pin ${e.pin.slice(0, 7)} (${e.files} files); no reinstall — only its marketplace source moves to the pin catalog`);
|
|
427
|
+
for (const b of saved.native.blocked) log(` ${b.name}: blocked, left as installed — ${b.reason}`);
|
|
428
|
+
const clean = !saved.native.blocked.length;
|
|
425
429
|
if (!saved.native.jobs.length) {
|
|
426
430
|
// Nothing is written; still describe the state at apply time, not at preview.
|
|
427
431
|
let again;
|
|
428
432
|
try { again = require('../lib/claude-pinned').prepare(SKILLS, saved.inventory, { home: os.homedir(), profileRoot: claudeRoot() }); }
|
|
429
|
-
catch (e) { log(` Claude native transaction refused before execution: ${e.message}`); return false; }
|
|
430
|
-
if (again.fingerprint !== saved.native.fingerprint) { log(' Claude native state changed since the preview; run update again'); return false; }
|
|
431
|
-
log(' Claude native transaction: not needed — every member already carries its pinned payload'
|
|
432
|
-
|
|
433
|
+
catch (e) { log(` Claude native transaction refused before execution: ${e.message}`); return { ok: false, partial: false }; }
|
|
434
|
+
if (again.fingerprint !== saved.native.fingerprint) { log(' Claude native state changed since the preview; run update again'); return { ok: false, partial: false }; }
|
|
435
|
+
log(clean ? ' Claude native transaction: not needed — every member already carries its pinned payload'
|
|
436
|
+
: ' Claude native transaction: nothing else to do; blocked members above keep their installed version (partial)');
|
|
437
|
+
return { ok: true, partial: !clean };
|
|
433
438
|
}
|
|
434
439
|
// Admission, version check and all backups happen before any other channel.
|
|
435
440
|
let result;
|
|
436
441
|
try { result = require('../lib/claude-pin-transaction').execute(saved.native, SKILLS, saved.inventory); }
|
|
437
|
-
catch(e) { log(` Claude native transaction refused before execution: ${e.message}`); return false; }
|
|
442
|
+
catch(e) { log(` Claude native transaction refused before execution: ${e.message}`); return { ok: false, partial: false }; }
|
|
438
443
|
log(` Claude native transaction: ${result.ok ? 'verified immutable payloads' : 'failed; inspect recovery receipt'} — ${result.receipt}`);
|
|
439
444
|
for (const r of result.results) log(` ${r.member}: ${r.status}${r.rollback ? '; rollback: ' + r.rollback : ''}`);
|
|
440
445
|
if (result.error) log(` ${result.error}`);
|
|
441
|
-
|
|
446
|
+
if (!clean) log(' Claude: partial — blocked members above keep their installed version');
|
|
447
|
+
// A blocked member makes the result partial; the other channels still run.
|
|
448
|
+
return { ok: result.ok, partial: !clean };
|
|
442
449
|
}
|
|
443
450
|
|
|
444
451
|
/** The same contract for `update` — every subprocess, prune, router and runtime action. */
|
|
@@ -516,8 +523,9 @@ function renderDryRun(planned, mode) {
|
|
|
516
523
|
function cmdInstall(f) {
|
|
517
524
|
const planned = frozenOperations.get(f) || planInstall(f);
|
|
518
525
|
if (f.dryRun) return renderDryRun(planned, 'install');
|
|
519
|
-
|
|
520
|
-
|
|
526
|
+
const claude = runClaudePlan(planned, f);
|
|
527
|
+
if (!claude.ok) return false;
|
|
528
|
+
let ok = !claude.partial;
|
|
521
529
|
if (!f.claudeOnly) {
|
|
522
530
|
const agents = skillsCliAgents(f);
|
|
523
531
|
log(`\n== Installing to agents via skills CLI: ${agents.join(', ')} ==`);
|
|
@@ -590,8 +598,9 @@ function scopedRoutingTargets(f) {
|
|
|
590
598
|
function cmdUpdate(f) {
|
|
591
599
|
const planned = frozenOperations.get(f) || planUpdate(f);
|
|
592
600
|
if (f.dryRun) return renderDryRun(planned, 'update');
|
|
593
|
-
|
|
594
|
-
|
|
601
|
+
const claude = runClaudePlan(planned, f);
|
|
602
|
+
if (!claude.ok) return false;
|
|
603
|
+
let ok = !claude.partial;
|
|
595
604
|
// Count and execute the subprocess entries from the same frozen plan.
|
|
596
605
|
progress.total =
|
|
597
606
|
(!f.claudeOnly && fs.existsSync(path.join(ROOT, '.gitmodules')) ? 1 : 0)
|
|
@@ -659,6 +668,8 @@ function cmdUpdate(f) {
|
|
|
659
668
|
+ `${before.differing.length} changed`
|
|
660
669
|
: ' already level with this package');
|
|
661
670
|
for (const m of before.missing) log(` + ${m}`);
|
|
671
|
+
if (r.retired && r.retired.length) log(` retired ${r.retired.length} file(s) this package dropped (kept under ${root}/.retired/): ${r.retired.join(', ')}`);
|
|
672
|
+
if (r.ownedError) log(` warning: ${r.ownedError}`);
|
|
662
673
|
}
|
|
663
674
|
} catch (e) {
|
|
664
675
|
// Other update steps may finish, but failed runtime refresh makes the aggregate red.
|
|
@@ -3207,6 +3218,30 @@ function cmdMaterialised(argv) {
|
|
|
3207
3218
|
return rows.some((r) => !r.satisfied) ? 2 : 0;
|
|
3208
3219
|
}
|
|
3209
3220
|
|
|
3221
|
+
/** Read-only: sizes of the instruction files each host loads, and documented caps. */
|
|
3222
|
+
function cmdContext(argv) {
|
|
3223
|
+
const json = argv.includes('--json');
|
|
3224
|
+
if (argv.some(a => a !== '--json')) { log('usage: npx sshlg-skills context [--json]'); return 2; }
|
|
3225
|
+
const pathMod = require('path'), C = require('../lib/context.js'), A = require('../lib/apply.js'), home = os.homedir();
|
|
3226
|
+
const hosts = A.TARGETS.map(t => C.host(t.agent, pathMod.join(A.hostRoot(t, home), t.file)));
|
|
3227
|
+
const codex = A.TARGETS.find(t => t.agent === 'codex'), codexRoot = A.hostRoot(codex, home);
|
|
3228
|
+
// Codex reads AGENTS.override.md in place of AGENTS.md when it exists.
|
|
3229
|
+
const override = C.host('codex', pathMod.join(codexRoot, 'AGENTS.override.md'));
|
|
3230
|
+
if (override.text != null || override.error) { const main = hosts.find(h => h.agent === 'codex'); if (main && override.text != null) main.shadowed = true; hosts.push(override); }
|
|
3231
|
+
for (const name of ['sshlg-routing.mdc', 'sshlg-wait.mdc']) hosts.push(C.host('cursor', pathMod.join(home, '.cursor', 'rules', name)));
|
|
3232
|
+
const config = C.read(pathMod.join(codexRoot, 'config.toml'));
|
|
3233
|
+
const r = C.report(hosts, config);
|
|
3234
|
+
if (json) { console.log(JSON.stringify(r)); return r.findings.some(f => f.severity === 'error') ? 1 : 0; }
|
|
3235
|
+
log('Always-loaded instruction files (' + r.estimate + '):');
|
|
3236
|
+
for (const row of r.rows) {
|
|
3237
|
+
if (!row.present) { log(` ${row.agent.padEnd(7)} ${row.error ? 'unreadable (' + row.error + ')' : 'absent'} ${row.file}`); continue; }
|
|
3238
|
+
log(` ${row.agent.padEnd(7)} ${String(row.bytes).padStart(7)} B ~${String(row.approxTokens).padStart(6)} tok ${String(row.lines).padStart(5)} lines hub ${row.managedBytes} B · other ${row.operatorBytes} B${row.shadowed ? ' (shadowed by AGENTS.override.md)' : row.cap ? ` cap ${row.cap} (${row.capSource})` : ''}`);
|
|
3239
|
+
}
|
|
3240
|
+
for (const f of r.findings) log(` ${f.severity}: ${f.message}`);
|
|
3241
|
+
if (!r.findings.length) log(' no host is over a documented cap');
|
|
3242
|
+
return r.findings.some(f => f.severity === 'error') ? 1 : 0;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3210
3245
|
function cmdHooks(argv) {
|
|
3211
3246
|
const fs = require('fs');
|
|
3212
3247
|
const pathMod = require('path');
|
|
@@ -3260,6 +3295,8 @@ function cmdHooks(argv) {
|
|
|
3260
3295
|
function syncRuntime() {
|
|
3261
3296
|
const result = require('../lib/runtime.js').sync(pkgRoot, root, { create: true });
|
|
3262
3297
|
if (result.ok === false) throw new Error(result.reason);
|
|
3298
|
+
if (result.retired && result.retired.length) log(` runtime: retired ${result.retired.length} dropped file(s) to ${root}/.retired/: ${result.retired.join(', ')}`);
|
|
3299
|
+
if (result.ownedError) log(` runtime warning: ${result.ownedError}`);
|
|
3263
3300
|
return root;
|
|
3264
3301
|
}
|
|
3265
3302
|
|
|
@@ -3758,6 +3795,7 @@ function main(argv) {
|
|
|
3758
3795
|
// Also before parseFlags: `hooks` takes a positional subcommand.
|
|
3759
3796
|
if (cmd === 'hooks') { const r = cmdHooks(rest); return typeof r === 'number' ? r : (r ? 0 : 1); }
|
|
3760
3797
|
if (cmd === 'injectors') { cmdInjectors(); return 0; }
|
|
3798
|
+
if (cmd === 'context') return cmdContext(rest);
|
|
3761
3799
|
if (cmd === 'conflicts') { cmdConflicts(); return 0; }
|
|
3762
3800
|
if (cmd === 'toolkit') { return cmdToolkit(argv) || 0; }
|
|
3763
3801
|
if (cmd === 'visibility') return cmdVisibility(argv);
|
package/claude-plugin-pins.json
CHANGED
|
@@ -1033,8 +1033,8 @@
|
|
|
1033
1033
|
{
|
|
1034
1034
|
"name": "task-pipeline",
|
|
1035
1035
|
"repo": "ssheleg/task-pipeline",
|
|
1036
|
-
"commit": "
|
|
1037
|
-
"version": "1.
|
|
1036
|
+
"commit": "2674147ccfc5eab19aab8f9d7334909cc22f64b2",
|
|
1037
|
+
"version": "1.96.0",
|
|
1038
1038
|
"pluginInstall": "task-pipeline@task-pipeline",
|
|
1039
1039
|
"pluginPath": "plugins/task-pipeline",
|
|
1040
1040
|
"entry": {
|
|
@@ -1042,7 +1042,7 @@
|
|
|
1042
1042
|
"displayName": "Task Pipeline",
|
|
1043
1043
|
"source": "./plugins/task-pipeline",
|
|
1044
1044
|
"description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/judgment/manual gates, a frozen requirement spine that closes with evidence, a work board and a verification ledger that outlive a run, an exposure line naming what shipped unconfirmed, a progress rail computed from the project's own config, a loop guard whose review ceiling measures rather than stops, and stage-3 tracks for what a product does, how it sounds and how it looks. Two modes need no task: `checkup` (what is unverified) and `setup` (audit existing docs); a third, `waves`, takes the whole board and closes it in waves that each finish in one pass. Retro insights can publish upstream as issues, opt-in and redacted.",
|
|
1045
|
-
"version": "1.
|
|
1045
|
+
"version": "1.96.0",
|
|
1046
1046
|
"author": {
|
|
1047
1047
|
"name": "ssheleg",
|
|
1048
1048
|
"url": "https://x.com/sshlg93"
|
|
@@ -1065,7 +1065,7 @@
|
|
|
1065
1065
|
"files": [
|
|
1066
1066
|
{
|
|
1067
1067
|
"path": ".claude-plugin/plugin.json",
|
|
1068
|
-
"sha256": "
|
|
1068
|
+
"sha256": "9d60330aed0146b81a89a1cb85859610491796ae9a062a619ecef37140e4a659",
|
|
1069
1069
|
"bytes": 1490
|
|
1070
1070
|
},
|
|
1071
1071
|
{
|
|
@@ -1260,13 +1260,13 @@
|
|
|
1260
1260
|
},
|
|
1261
1261
|
{
|
|
1262
1262
|
"path": "skills/task-pipeline/pipeline.example.json",
|
|
1263
|
-
"sha256": "
|
|
1264
|
-
"bytes":
|
|
1263
|
+
"sha256": "4a67e7259e6414c53a269baa579935026c955355ea1090aea575418be05b0069",
|
|
1264
|
+
"bytes": 25596
|
|
1265
1265
|
},
|
|
1266
1266
|
{
|
|
1267
1267
|
"path": "skills/task-pipeline/pipeline.schema.json",
|
|
1268
|
-
"sha256": "
|
|
1269
|
-
"bytes":
|
|
1268
|
+
"sha256": "4ce237089c9c3c507e5d3fa36f093feca4f5289f6d0867debf2645c4959bd9d3",
|
|
1269
|
+
"bytes": 20061
|
|
1270
1270
|
},
|
|
1271
1271
|
{
|
|
1272
1272
|
"path": "skills/task-pipeline/references/acceptance.md",
|
|
@@ -1285,8 +1285,8 @@
|
|
|
1285
1285
|
},
|
|
1286
1286
|
{
|
|
1287
1287
|
"path": "skills/task-pipeline/references/audit.md",
|
|
1288
|
-
"sha256": "
|
|
1289
|
-
"bytes":
|
|
1288
|
+
"sha256": "d6c772b7eb9dde912338b2dc2ae424d4fc77c2e575cf9f824dfd0a5a9642c3f2",
|
|
1289
|
+
"bytes": 25494
|
|
1290
1290
|
},
|
|
1291
1291
|
{
|
|
1292
1292
|
"path": "skills/task-pipeline/references/backlog.md",
|
|
@@ -1320,8 +1320,8 @@
|
|
|
1320
1320
|
},
|
|
1321
1321
|
{
|
|
1322
1322
|
"path": "skills/task-pipeline/references/continuity.md",
|
|
1323
|
-
"sha256": "
|
|
1324
|
-
"bytes":
|
|
1323
|
+
"sha256": "224ade56b7560b3365fc665b7018cefa9ea9d7f58b0b46cb4d3ba4134eb15f49",
|
|
1324
|
+
"bytes": 29721
|
|
1325
1325
|
},
|
|
1326
1326
|
{
|
|
1327
1327
|
"path": "skills/task-pipeline/references/conventions.md",
|
|
@@ -1340,8 +1340,8 @@
|
|
|
1340
1340
|
},
|
|
1341
1341
|
{
|
|
1342
1342
|
"path": "skills/task-pipeline/references/doctrine-map.md",
|
|
1343
|
-
"sha256": "
|
|
1344
|
-
"bytes":
|
|
1343
|
+
"sha256": "8c2a4ebb23f00068ed351dd890e12cd44eec5df86b5f735f559c1a43cc82e9ea",
|
|
1344
|
+
"bytes": 5187
|
|
1345
1345
|
},
|
|
1346
1346
|
{
|
|
1347
1347
|
"path": "skills/task-pipeline/references/documentation.md",
|
|
@@ -1353,6 +1353,11 @@
|
|
|
1353
1353
|
"sha256": "cdeab4810e18f42dedcef1f91dac17381f26062028de5ccd81dd9e1e0e1ed1d1",
|
|
1354
1354
|
"bytes": 7148
|
|
1355
1355
|
},
|
|
1356
|
+
{
|
|
1357
|
+
"path": "skills/task-pipeline/references/finish-line.md",
|
|
1358
|
+
"sha256": "a63a4e6997451b5ba96aaa140bfaef638349fc657f04c6e18dbbb3b3a7a9139c",
|
|
1359
|
+
"bytes": 6975
|
|
1360
|
+
},
|
|
1356
1361
|
{
|
|
1357
1362
|
"path": "skills/task-pipeline/references/gates.md",
|
|
1358
1363
|
"sha256": "e76d55a8ff93eabf34e12c0e5422c6fb909b87a76b99e86a32d54a7624493ba3",
|
|
@@ -1370,8 +1375,8 @@
|
|
|
1370
1375
|
},
|
|
1371
1376
|
{
|
|
1372
1377
|
"path": "skills/task-pipeline/references/iteration.md",
|
|
1373
|
-
"sha256": "
|
|
1374
|
-
"bytes":
|
|
1378
|
+
"sha256": "c325d6e0981865966019830a01d292762df1b32dfed44485d2b685fb0bab5c68",
|
|
1379
|
+
"bytes": 25480
|
|
1375
1380
|
},
|
|
1376
1381
|
{
|
|
1377
1382
|
"path": "skills/task-pipeline/references/knowledge-graph.md",
|
|
@@ -1390,8 +1395,8 @@
|
|
|
1390
1395
|
},
|
|
1391
1396
|
{
|
|
1392
1397
|
"path": "skills/task-pipeline/references/loop-guard.md",
|
|
1393
|
-
"sha256": "
|
|
1394
|
-
"bytes":
|
|
1398
|
+
"sha256": "1a62dde018bc1783baf6ecd1a808ce46065185007090e275aad8a50e48cbb961",
|
|
1399
|
+
"bytes": 11797
|
|
1395
1400
|
},
|
|
1396
1401
|
{
|
|
1397
1402
|
"path": "skills/task-pipeline/references/model-tiering.md",
|
|
@@ -1405,8 +1410,8 @@
|
|
|
1405
1410
|
},
|
|
1406
1411
|
{
|
|
1407
1412
|
"path": "skills/task-pipeline/references/portability.md",
|
|
1408
|
-
"sha256": "
|
|
1409
|
-
"bytes":
|
|
1413
|
+
"sha256": "eebbe048ca37387810584d5b9ba5315386b4f4480bedc19e81562650cf8da370",
|
|
1414
|
+
"bytes": 11929
|
|
1410
1415
|
},
|
|
1411
1416
|
{
|
|
1412
1417
|
"path": "skills/task-pipeline/references/prioritisation.md",
|
|
@@ -1420,8 +1425,8 @@
|
|
|
1420
1425
|
},
|
|
1421
1426
|
{
|
|
1422
1427
|
"path": "skills/task-pipeline/references/progress.md",
|
|
1423
|
-
"sha256": "
|
|
1424
|
-
"bytes":
|
|
1428
|
+
"sha256": "216f5d195c8b99aa7113d9fd19c6666b88b6b0cadd38eb93c840440fbabbb6eb",
|
|
1429
|
+
"bytes": 25540
|
|
1425
1430
|
},
|
|
1426
1431
|
{
|
|
1427
1432
|
"path": "skills/task-pipeline/references/residue.md",
|
|
@@ -1435,8 +1440,8 @@
|
|
|
1435
1440
|
},
|
|
1436
1441
|
{
|
|
1437
1442
|
"path": "skills/task-pipeline/references/review.md",
|
|
1438
|
-
"sha256": "
|
|
1439
|
-
"bytes":
|
|
1443
|
+
"sha256": "268d1f2394e69a30dafe2050d38a8b9e9f7ae17e30f8a32678f20623c8912942",
|
|
1444
|
+
"bytes": 13073
|
|
1440
1445
|
},
|
|
1441
1446
|
{
|
|
1442
1447
|
"path": "skills/task-pipeline/references/setup.md",
|
|
@@ -1505,8 +1510,8 @@
|
|
|
1505
1510
|
},
|
|
1506
1511
|
{
|
|
1507
1512
|
"path": "skills/task-pipeline/references/stages.md",
|
|
1508
|
-
"sha256": "
|
|
1509
|
-
"bytes":
|
|
1513
|
+
"sha256": "8c75b84bf5ad4978329696dca1fcdaf4a2710b6a3a0e1fdc3380393e9107a506",
|
|
1514
|
+
"bytes": 35794
|
|
1510
1515
|
},
|
|
1511
1516
|
{
|
|
1512
1517
|
"path": "skills/task-pipeline/references/tdd.md",
|
|
@@ -1520,8 +1525,8 @@
|
|
|
1520
1525
|
},
|
|
1521
1526
|
{
|
|
1522
1527
|
"path": "skills/task-pipeline/references/waves.md",
|
|
1523
|
-
"sha256": "
|
|
1524
|
-
"bytes":
|
|
1528
|
+
"sha256": "0540ba5a052c1ab69065d6012ebf8ec69a3494fbd229cf7226ba3517af06bfa1",
|
|
1529
|
+
"bytes": 17641
|
|
1525
1530
|
},
|
|
1526
1531
|
{
|
|
1527
1532
|
"path": "skills/task-pipeline/references/work-graph.md",
|
|
@@ -1545,7 +1550,7 @@
|
|
|
1545
1550
|
},
|
|
1546
1551
|
{
|
|
1547
1552
|
"path": "skills/task-pipeline/scripts/graph.py",
|
|
1548
|
-
"sha256": "
|
|
1553
|
+
"sha256": "f07abf42a455676a7ca8e733bcaab371cecedb29fb16f594895b9c15a1b4b736",
|
|
1549
1554
|
"bytes": 111655
|
|
1550
1555
|
},
|
|
1551
1556
|
{
|
|
@@ -1575,13 +1580,13 @@
|
|
|
1575
1580
|
},
|
|
1576
1581
|
{
|
|
1577
1582
|
"path": "skills/task-pipeline/scripts/waves.py",
|
|
1578
|
-
"sha256": "
|
|
1579
|
-
"bytes":
|
|
1583
|
+
"sha256": "937bd11092e0765cde2bda4f84a4932f056fc8b3cad8233678241e24f3a18223",
|
|
1584
|
+
"bytes": 39837
|
|
1580
1585
|
},
|
|
1581
1586
|
{
|
|
1582
1587
|
"path": "skills/task-pipeline/SKILL.md",
|
|
1583
|
-
"sha256": "
|
|
1584
|
-
"bytes":
|
|
1588
|
+
"sha256": "3054cbd174efd2779df62facb2b199ffda275e34eb0d7d39f3abb834596c19c3",
|
|
1589
|
+
"bytes": 21033
|
|
1585
1590
|
},
|
|
1586
1591
|
{
|
|
1587
1592
|
"path": "skills/task-pipeline/templates/adr.md",
|
|
@@ -1600,8 +1605,8 @@
|
|
|
1600
1605
|
},
|
|
1601
1606
|
{
|
|
1602
1607
|
"path": "skills/task-pipeline/templates/brief.md",
|
|
1603
|
-
"sha256": "
|
|
1604
|
-
"bytes":
|
|
1608
|
+
"sha256": "1338ed76ca9d2a8cc0c180799871fad6771401c415a37064e498b69daa5ea84b",
|
|
1609
|
+
"bytes": 11765
|
|
1605
1610
|
},
|
|
1606
1611
|
{
|
|
1607
1612
|
"path": "skills/task-pipeline/templates/browser-claims.json",
|
|
@@ -1670,8 +1675,8 @@
|
|
|
1670
1675
|
},
|
|
1671
1676
|
{
|
|
1672
1677
|
"path": "skills/task-pipeline/templates/inventory.schema.json",
|
|
1673
|
-
"sha256": "
|
|
1674
|
-
"bytes":
|
|
1678
|
+
"sha256": "2b5e183c6f36aa9f47ceb0fa56c964dc3cc08872b046e07d02e3d23c5bb2a9ea",
|
|
1679
|
+
"bytes": 7613
|
|
1675
1680
|
},
|
|
1676
1681
|
{
|
|
1677
1682
|
"path": "skills/task-pipeline/templates/lesson.schema.json",
|
|
@@ -1710,8 +1715,8 @@
|
|
|
1710
1715
|
},
|
|
1711
1716
|
{
|
|
1712
1717
|
"path": "skills/task-pipeline/templates/run.md",
|
|
1713
|
-
"sha256": "
|
|
1714
|
-
"bytes":
|
|
1718
|
+
"sha256": "9bfe093a68c5836afa268207dbbae3f8cdb8cf5dcbe14368c3e91dd7c643b6b4",
|
|
1719
|
+
"bytes": 13007
|
|
1715
1720
|
},
|
|
1716
1721
|
{
|
|
1717
1722
|
"path": "skills/task-pipeline/templates/stage-coverage.sh",
|
package/lib/blocks.js
CHANGED
|
@@ -4,13 +4,39 @@ const fs=require('fs'),path=require('path'),crypto=require('crypto');
|
|
|
4
4
|
const SCOPE=[['claude','CLAUDE_CONFIG_DIR','.claude','CLAUDE.md'],['codex','CODEX_HOME','.codex','AGENTS.md'],['gemini','GEMINI_CONFIG_DIR','.gemini','GEMINI.md'],['pi','PI_CODING_AGENT_DIR','.pi/agent','AGENTS.md'],['cursor',null,'.cursor/rules','sshlg-routing.mdc'],['cursor',null,'.cursor/rules','sshlg-wait.mdc']];
|
|
5
5
|
const MAX_BYTES=1024*1024;
|
|
6
6
|
function decode(bytes){const s=bytes.toString('utf8');if(!Buffer.from(s,'utf8').equals(bytes))throw Error('INVALID_UTF8');return s;}
|
|
7
|
-
|
|
7
|
+
// managed-blocks/1 ownership (docs/working-rules/managed-blocks.md § Block names). The
|
|
8
|
+
// registry names who writes a block; it never authorizes the hub to write a foreign one.
|
|
9
|
+
const REGISTRY={
|
|
10
|
+
'SSHLG:ROUTERS':{owner:'sshlg-skills',writer:'npx sshlg-skills install/update (lib/apply.js)',dedicated:true},
|
|
11
|
+
'SSHLG:RULE:wait-is-not-a-stop':{owner:'sshlg-skills',writer:'npx sshlg-skills rules (lib/wait-rules.js)',dedicated:true},
|
|
12
|
+
'SSHLG:REPOSITORY-HANDOFF':{owner:null,writer:null,undecided:'B-174: the operator has not decided which tool owns it'},
|
|
13
|
+
'OBSERVATORY:AGENT-UPDATES':{owner:'project-observatory',writer:'agent_updates.py through npx sshlg-skills blocks install --owner OBSERVATORY'},
|
|
14
|
+
};
|
|
15
|
+
function admission(id){if(id==='SSHLG:REPOSITORY-HANDOFF')return 'OPERATOR_DECISION_REQUIRED';if(REGISTRY[id]||subregion(id))return 'REGISTERED_NOT_AUTHORIZED';return 'FOREIGN';}
|
|
16
|
+
function registered(id){const r=REGISTRY[id]||(subregion(id)?REGISTRY['SSHLG:ROUTERS']:null);return r?{owner:r.owner,writer:r.writer}:null;}
|
|
8
17
|
function subregion(id){return /^SSHLG:ROUTER:[a-z0-9-]+$/.test(id)||/^SSHLG:ROUTERS:(MAP|PROTOCOL|TABLE)$/.test(id);}
|
|
18
|
+
// Closed Markdown code fences (``` or ~~~, up to three spaces of indent). A marker inside
|
|
19
|
+
// one is an example in prose, not a block: it is neither listed nor written. An unclosed
|
|
20
|
+
// fence fences nothing, so it can never hide a real block's END.
|
|
21
|
+
function fences(text){
|
|
22
|
+
const out=[];let open=null,at=0;
|
|
23
|
+
for(const line of text.split('\n')){
|
|
24
|
+
const m=/^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line.replace(/\r$/,''));
|
|
25
|
+
if(!open){if(m&&!(m[1][0]==='`'&&m[2].includes('`')))open={ch:m[1][0],len:m[1].length,start:at};}
|
|
26
|
+
else if(m&&m[1][0]===open.ch&&m[1].length>=open.len&&!m[2].trim()){out.push([open.start,at+line.length]);open=null;}
|
|
27
|
+
at+=line.length+1;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
9
31
|
function parse(text){
|
|
10
32
|
const blocks=[],issues=[],stack=[],seen=new Set();const tokens=[];const re=/<!--[\s\S]*?(?:-->|$)/g;let match;
|
|
33
|
+
const fenced=fences(text);let f=0;const inFence=at=>{while(f<fenced.length&&fenced[f][1]<at)f++;return f<fenced.length&&fenced[f][0]<=at&&at<=fenced[f][1];};
|
|
11
34
|
const starts=[0];for(let i=text.indexOf('\n');i!==-1;i=text.indexOf('\n',i+1))starts.push(i+1);
|
|
12
35
|
const line=at=>{let lo=0,hi=starts.length;while(lo<hi){const mid=(lo+hi)>>>1;if(starts[mid]<=at)lo=mid+1;else hi=mid;}return lo;};
|
|
13
36
|
while((match=re.exec(text))){
|
|
37
|
+
// A comment opened inside a fence ends with the fence: resume scanning after it, so an
|
|
38
|
+
// unterminated `<!--` in an example cannot swallow a real marker below.
|
|
39
|
+
if(inFence(match.index)){re.lastIndex=fenced[f][1];continue;}
|
|
14
40
|
const body=match[0].slice(4);if(!/[^\s<>:]+\s*:\s*[^\s<>:]+\s*:\s*(?:BEGIN|END|OPTOUT)\b/i.test(body))continue;
|
|
15
41
|
const m=/^[ \t]*([A-Za-z0-9_.-]+(?::[A-Za-z0-9_.-]+)+):(BEGIN|END|OPTOUT)(?=[ \t\r\n]|-->)/.exec(body);
|
|
16
42
|
if(!m||!match[0].endsWith('-->')||m[1].length>128){issues.push({code:'MALFORMED_MARKER',line:line(match.index)});continue;}
|
|
@@ -22,7 +48,7 @@ function parse(text){
|
|
|
22
48
|
if(t.kind==='BEGIN'||t.kind==='OPTOUT'){
|
|
23
49
|
const nested=stack.length>0;const allowed=nested&&stack.length===1&&stack[0].id==='SSHLG:ROUTERS'&&subregion(t.id)&&t.kind==='BEGIN';
|
|
24
50
|
const bad=seen.has(t.id)||(nested&&!allowed)||(!nested&&subregion(t.id));
|
|
25
|
-
const b={markerId:t.id,range:{startLine:t.line,endLine:t.kind==='OPTOUT'?t.endLine:null},syntaxState:bad?'AMBIGUOUS':t.kind==='OPTOUT'?'OPTOUT':allowed?'NESTED_SUBREGION':'PAIRED',claimedOwner:t.id.split(':')[0],writerAdmission:admission(t.id),lastJournalState:'NOT_READ'};
|
|
51
|
+
const b={markerId:t.id,range:{startLine:t.line,endLine:t.kind==='OPTOUT'?t.endLine:null},syntaxState:bad?'AMBIGUOUS':t.kind==='OPTOUT'?'OPTOUT':allowed?'NESTED_SUBREGION':'PAIRED',claimedOwner:t.id.split(':')[0],registered:registered(t.id),writerAdmission:admission(t.id),lastJournalState:'NOT_READ'};
|
|
26
52
|
// Offsets are private parser data and never part of public JSON.
|
|
27
53
|
Object.defineProperty(b,'offset',{value:{start:t.start,end:t.kind==='OPTOUT'?t.end:null},enumerable:false});
|
|
28
54
|
if(bad)issues.push({code:'AMBIGUOUS_MARKER',line:t.line});seen.add(t.id);blocks.push(b);
|
|
@@ -90,5 +116,11 @@ function localDiff(options){
|
|
|
90
116
|
const current=matches[0];const before=current.r.text.slice(current.b.offset.start,current.b.offset.end),after=proposed.text.slice(pb.offset.start,pb.offset.end);
|
|
91
117
|
return before===after?'No block changes.\n':['--- current selected block','+++ proposed selected block',...before.split('\n').map(x=>'-'+x),...after.split('\n').map(x=>'+'+x),'Read-only: no bytes changed; no write authority inferred.',''].join('\n');
|
|
92
118
|
}
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
const USAGE=`npx sshlg-skills blocks [--agent claude|codex|gemini|pi|cursor] [--json] # list markers, read-only
|
|
120
|
+
npx sshlg-skills blocks diff OWNER:NAME --from <local-file> --agent <host> # selected local diff, read-only
|
|
121
|
+
`;
|
|
122
|
+
function cli(args,options,write){
|
|
123
|
+
if(['install','uninstall','resolve'].includes(args[0]))return require('./blockwriter.js').cli(args,options,write);
|
|
124
|
+
if(args.includes('--help')||args.includes('-h')){write(USAGE+require('./blockwriter.js').USAGE);return 0;}
|
|
125
|
+
const f=flags(args);if(f.diff){write(localDiff({...options,...f}));return 0;}const r=inspect({...options,...f});write(f.json?JSON.stringify(r,null,2)+'\n':r.targets.map(t=>`${t.host} ${t.pathAlias}: ${t.readState}${t.blocks?' ('+t.blocks.length+' markers; '+(t.ok?'syntax valid':'syntax uncertain')+')':''}`).join('\n')+'\nRead-only metadata; marker ownership does not authorize writes.\n');return r.ok?0:1;}
|
|
126
|
+
module.exports={parse,decode,read,inspect,flags,localDiff,cli,targets,REGISTRY};
|