rahman-resources 1.13.2 → 1.13.3
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/bin/cli.js +45 -6
- package/lib/contract-tools.test.ts +52 -0
- package/lib/contract-types.ts +8 -0
- package/lib/contract-validate.ts +23 -1
- package/lib/contract.ts +2 -0
- package/lib/manifest.json +396 -1414
- package/lib/slice-schema.json +4 -0
- package/lib/starter/_README.md +29 -6
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -869,8 +869,12 @@ async function installSkill(slug, target) {
|
|
|
869
869
|
const dest = path.join(target, ".claude", "skills", slug);
|
|
870
870
|
process.stdout.write(` ${kleur.cyan(slug.padEnd(20))} ${kleur.dim(`→ .claude/skills/${slug}/`)} ... `);
|
|
871
871
|
if (skill.source === "anthropics") {
|
|
872
|
-
const
|
|
873
|
-
await
|
|
872
|
+
const source = `${SKILLS_REPO}/${skill.path}`;
|
|
873
|
+
await cloneWithRetry(
|
|
874
|
+
() => tiged(source, { cache: false, force: true, verbose: false }),
|
|
875
|
+
dest,
|
|
876
|
+
source,
|
|
877
|
+
);
|
|
874
878
|
} else if (skill.source === "rahman") {
|
|
875
879
|
// Future: ship rahman-authored skills inside this repo. For now, scaffold a stub.
|
|
876
880
|
mkdirSync(dest, { recursive: true });
|
|
@@ -1372,14 +1376,49 @@ async function checkGhInstalled() {
|
|
|
1372
1376
|
|
|
1373
1377
|
// ─── helpers ──────────────────────────────────────────────────────────────
|
|
1374
1378
|
|
|
1379
|
+
// tiged surfaces raw git/network errors with no recovery hint. Translate the
|
|
1380
|
+
// common failure modes into actionable messages, and retry once on transient
|
|
1381
|
+
// network errors before giving up.
|
|
1382
|
+
async function cloneWithRetry(makeEmitter, dest, source) {
|
|
1383
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
1384
|
+
try {
|
|
1385
|
+
await makeEmitter().clone(dest);
|
|
1386
|
+
return;
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
const msg = String(err?.message ?? err);
|
|
1389
|
+
const transient = /ETIMEDOUT|ECONNRESET|EAI_AGAIN|socket hang up|50[234]/i.test(msg);
|
|
1390
|
+
if (transient && attempt === 1) continue; // one silent retry
|
|
1391
|
+
if (/ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ETIMEDOUT|ECONNRESET/i.test(msg)) {
|
|
1392
|
+
throw new Error(
|
|
1393
|
+
`Network error fetching ${source}: ${msg}\n → check your internet connection and retry.`,
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
if (/could not find|not found|ENOENT|404/i.test(msg)) {
|
|
1397
|
+
throw new Error(
|
|
1398
|
+
`Could not fetch ${source}: ${msg}\n → verify the slug exists (run 'list') and the repo/branch is public.`,
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
throw new Error(`Failed to fetch ${source}: ${msg}`);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1375
1406
|
async function pull(repoPath, dest) {
|
|
1376
|
-
const
|
|
1377
|
-
await
|
|
1407
|
+
const source = `${REPO}/${repoPath}#${BRANCH}`;
|
|
1408
|
+
await cloneWithRetry(
|
|
1409
|
+
() => tiged(source, { cache: false, force: true, verbose: false }),
|
|
1410
|
+
dest,
|
|
1411
|
+
source,
|
|
1412
|
+
);
|
|
1378
1413
|
}
|
|
1379
1414
|
|
|
1380
1415
|
async function pullFromRepo(repo, subPath, branch, dest) {
|
|
1381
|
-
const
|
|
1382
|
-
await
|
|
1416
|
+
const source = `${repo}/${subPath}#${branch}`;
|
|
1417
|
+
await cloneWithRetry(
|
|
1418
|
+
() => tiged(source, { cache: false, force: true, verbose: false }),
|
|
1419
|
+
dest,
|
|
1420
|
+
source,
|
|
1421
|
+
);
|
|
1383
1422
|
}
|
|
1384
1423
|
|
|
1385
1424
|
function detectPM(target) {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { defineSliceContract } from "./contract";
|
|
3
|
+
|
|
4
|
+
const base = {
|
|
5
|
+
id: "foo",
|
|
6
|
+
version: "1.0.0",
|
|
7
|
+
requires: {},
|
|
8
|
+
provides: {},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe("defineSliceContract — provides.tools (agentic surface)", () => {
|
|
12
|
+
it("accepts tool names prefixed with the slice id", () => {
|
|
13
|
+
expect(() =>
|
|
14
|
+
defineSliceContract({
|
|
15
|
+
...base,
|
|
16
|
+
provides: { tools: ["foo.bar", "foo.baz.qux"] },
|
|
17
|
+
}),
|
|
18
|
+
).not.toThrow();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("rejects tool names not prefixed with the slice id", () => {
|
|
22
|
+
expect(() =>
|
|
23
|
+
defineSliceContract({
|
|
24
|
+
...base,
|
|
25
|
+
provides: { tools: ["bar.baz"] },
|
|
26
|
+
}),
|
|
27
|
+
).toThrow(/must be prefixed with "foo\."/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("rejects non-string / empty tool entries", () => {
|
|
31
|
+
expect(() =>
|
|
32
|
+
defineSliceContract({
|
|
33
|
+
...base,
|
|
34
|
+
provides: { tools: [""] as string[] },
|
|
35
|
+
}),
|
|
36
|
+
).toThrow(/non-empty strings/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("allows tools to be omitted (non-agentic slices)", () => {
|
|
40
|
+
expect(() => defineSliceContract({ ...base })).not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("allows a conflict to reference a tool name", () => {
|
|
44
|
+
expect(() =>
|
|
45
|
+
defineSliceContract({
|
|
46
|
+
...base,
|
|
47
|
+
provides: { tools: ["foo.bar"] },
|
|
48
|
+
conflicts: ["other:tools.foo.bar"],
|
|
49
|
+
}),
|
|
50
|
+
).not.toThrow();
|
|
51
|
+
});
|
|
52
|
+
});
|
package/lib/contract-types.ts
CHANGED
|
@@ -88,6 +88,14 @@ export interface SliceContractProvides {
|
|
|
88
88
|
events?: string[];
|
|
89
89
|
/** Public component exports — e.g. `["DokuCheckoutButton"]`. */
|
|
90
90
|
components?: string[];
|
|
91
|
+
/**
|
|
92
|
+
* Fully-qualified AI tool names the slice exposes as a `ToolCollection`
|
|
93
|
+
* (`@/shared/agentic`) — e.g. `["image-editor.layer.add"]`. Every entry
|
|
94
|
+
* MUST be prefixed with the slice id so one agent can aggregate tools from
|
|
95
|
+
* many slices without collision. A slice is "agentic-ready" iff this is
|
|
96
|
+
* non-empty.
|
|
97
|
+
*/
|
|
98
|
+
tools?: string[];
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
// ---------------------------------------------------------------------------
|
package/lib/contract-validate.ts
CHANGED
|
@@ -18,7 +18,7 @@ const KEBAB_CASE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
|
18
18
|
const SEMVER =
|
|
19
19
|
/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
20
20
|
const PREFIX = /^[a-z][a-z0-9_]*_$/;
|
|
21
|
-
const CONFLICT = /^[a-z][a-z0-9-]*:(routes|hooks|tables|events|components)\.[A-Za-z0-9_
|
|
21
|
+
const CONFLICT = /^[a-z][a-z0-9-]*:(routes|hooks|tables|events|components|tools)\.[A-Za-z0-9_\-\/.]+$/;
|
|
22
22
|
const PERMISSION = /^[^.]+\.[^.]+$/;
|
|
23
23
|
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
@@ -131,6 +131,28 @@ export function validateGeneralization(c: SliceContract): void {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
export function validateTools(c: SliceContract): void {
|
|
135
|
+
const tools = c.provides.tools;
|
|
136
|
+
if (tools === undefined) return;
|
|
137
|
+
if (!Array.isArray(tools)) {
|
|
138
|
+
throw new Error(`defineSliceContract(${c.id}): provides.tools must be an array`);
|
|
139
|
+
}
|
|
140
|
+
for (const t of tools) {
|
|
141
|
+
if (typeof t !== "string" || t.length === 0) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`defineSliceContract(${c.id}): provides.tools entries must be non-empty strings`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
// Every tool name MUST be namespaced to the slice id so one agent can
|
|
147
|
+
// aggregate collections from many slices without collision.
|
|
148
|
+
if (!t.startsWith(`${c.id}.`)) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`defineSliceContract(${c.id}): provides.tools entry "${t}" must be prefixed with "${c.id}."`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
134
156
|
export function validateConflicts(c: SliceContract): void {
|
|
135
157
|
if (!c.conflicts) return;
|
|
136
158
|
if (!Array.isArray(c.conflicts)) {
|
package/lib/contract.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
validateConvex,
|
|
16
16
|
validateGeneralization,
|
|
17
17
|
validateConflicts,
|
|
18
|
+
validateTools,
|
|
18
19
|
} from "./contract-validate";
|
|
19
20
|
|
|
20
21
|
// Re-export the full type surface so existing imports keep working.
|
|
@@ -62,5 +63,6 @@ export function defineSliceContract(c: SliceContract): SliceContract {
|
|
|
62
63
|
validateConvex(c);
|
|
63
64
|
validateGeneralization(c);
|
|
64
65
|
validateConflicts(c);
|
|
66
|
+
validateTools(c);
|
|
65
67
|
return c;
|
|
66
68
|
}
|