skissue 0.1.18 → 0.1.20

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.
Files changed (3) hide show
  1. package/README.md +1 -2
  2. package/dist/entry.js +138 -69
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -18,7 +18,6 @@
18
18
  <a href="https://www.npmjs.com/package/skissue"><img src="https://img.shields.io/npm/v/skissue?style=flat-square&logo=npm&color=CB3837" alt="npm version"></a>
19
19
  <a href="https://www.npmjs.com/package/skissue"><img src="https://img.shields.io/npm/dm/skissue?style=flat-square&logo=npm&color=CB3837" alt="npm downloads"></a>
20
20
  <a href="https://github.com/midyan/skill-issue/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow?style=flat-square" alt="License MIT"></a>
21
- <a href="https://github.com/midyan/skill-issue"><img src="https://img.shields.io/github/stars/midyan/skill-issue?style=flat-square&logo=github&color=8B5CF6" alt="GitHub stars"></a>
22
21
  <img src="https://img.shields.io/badge/node-%3E%3D24-brightgreen?style=flat-square&logo=nodedotjs" alt="Node 24+">
23
22
  <img src="https://img.shields.io/badge/TypeScript-5.7-blue?style=flat-square&logo=typescript" alt="TypeScript">
24
23
  </p>
@@ -136,7 +135,7 @@ Skills are in `.agents/skills/`. Your agents can find them. Commit the folder. S
136
135
 
137
136
  A skill registry is just a Git repo with a specific layout. Here's how to build one from scratch.
138
137
 
139
- **Shortcut:** run `npx skissue init-registry` to create `registry.json`, `registry/<sample-skill>/SKILL.md`, and optionally `git init` in the current directory or a path you choose.
138
+ **Shortcut:** run `npx skissue init-registry` to create `registry.json`, `registry/<skill-id>/SKILL.md` (default skill id `validate-links`), and optionally `git init` in the current directory or a path you choose.
140
139
 
141
140
  ### Registry structure
142
141
 
package/dist/entry.js CHANGED
@@ -9,7 +9,7 @@ import { resolve as resolve4 } from "node:path";
9
9
  // src/commands/init.ts
10
10
  import * as p2 from "@clack/prompts";
11
11
  import chalk2 from "chalk";
12
- import { existsSync as existsSync4 } from "node:fs";
12
+ import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
13
13
  import { mkdir as mkdir4 } from "node:fs/promises";
14
14
  import { relative, resolve as resolve3 } from "node:path";
15
15
 
@@ -357,6 +357,7 @@ function requiredTrimmed(v) {
357
357
  }
358
358
 
359
359
  // src/commands/init-registry.ts
360
+ var DEFAULT_REGISTRY_SKILL_ID = "validate-links";
360
361
  function validateSkillId(raw) {
361
362
  const id = raw.trim();
362
363
  if (!id) return "Skill id is required";
@@ -367,6 +368,15 @@ function validateSkillId(raw) {
367
368
  function validateSkillIdPrompt(v) {
368
369
  return validateSkillId(String(v ?? ""));
369
370
  }
371
+ function registryDirectoryExists(root) {
372
+ const regDir = join3(root, "registry");
373
+ if (!existsSync3(regDir)) return false;
374
+ try {
375
+ return statSync2(regDir).isDirectory();
376
+ } catch {
377
+ return false;
378
+ }
379
+ }
370
380
  function registryLayoutExists(root) {
371
381
  const jsonPath = join3(root, "registry.json");
372
382
  const regDir = join3(root, "registry");
@@ -379,10 +389,23 @@ function registryLayoutExists(root) {
379
389
  }
380
390
  }
381
391
  function skillMarkdown(skillId) {
392
+ if (skillId === DEFAULT_REGISTRY_SKILL_ID) {
393
+ return `---
394
+ name: validate-links
395
+ description: Scan markdown for broken relative links across the repo.
396
+ ---
397
+
398
+ # validate-links (soft)
399
+
400
+ Use before large doc moves or when CI reports broken links. A **hard** implementation walks \`*.md\` files and checks that \`[text](relative)\` targets resolve.
401
+
402
+ Add a \`hard/\` directory when you want automated checks (for example a small script or \`npm run check:links\` wired to the same rules).
403
+ `;
404
+ }
382
405
  const title = skillId.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
383
406
  return `---
384
407
  name: ${skillId}
385
- description: Sample skill scaffolded by skissue init-registry. Replace this description.
408
+ description: Starter skill scaffolded by skissue init-registry. Replace this description.
386
409
  ---
387
410
 
388
411
  # ${title}
@@ -391,7 +414,7 @@ A minimal starter skill for your registry.
391
414
 
392
415
  ## When to use
393
416
 
394
- Use when the user works with this sample capability (customize this section).
417
+ Describe when an agent should apply this skill (customize this section).
395
418
 
396
419
  ## Instructions
397
420
 
@@ -448,8 +471,8 @@ async function promptMinimalRegistryScaffold(root) {
448
471
  }
449
472
  }
450
473
  const idRaw = await p.text({
451
- message: "Sample skill id (folder name under registry/)",
452
- initialValue: "sample-skill",
474
+ message: "Skill id (folder name under registry/)",
475
+ initialValue: DEFAULT_REGISTRY_SKILL_ID,
453
476
  validate: validateSkillIdPrompt
454
477
  });
455
478
  if (p.isCancel(idRaw)) {
@@ -574,6 +597,59 @@ Transport: ${mode}
574
597
  Branch: ${r.branch}
575
598
  skillsRoot: ${cfg.skillsRoot}`;
576
599
  }
600
+ async function runLocalBootstrapFlow(cwd, resolvedRoot) {
601
+ const atConsumerRoot = resolvedRoot === resolve3(cwd);
602
+ if (atConsumerRoot && !registryLayoutExists(resolvedRoot)) {
603
+ const okRoot = await p2.confirm({
604
+ message: "This adds registry.json and registry/ at your project root. Continue?",
605
+ initialValue: true
606
+ });
607
+ if (p2.isCancel(okRoot) || !okRoot) {
608
+ p2.cancel("Aborted.");
609
+ return { ok: false, exitCode: 0 };
610
+ }
611
+ }
612
+ const prompted = await promptMinimalRegistryScaffold(resolvedRoot);
613
+ if (!prompted) {
614
+ p2.cancel("Aborted. No files were changed.");
615
+ return { ok: false, exitCode: 0 };
616
+ }
617
+ let result;
618
+ try {
619
+ result = await scaffoldMinimalRegistry({
620
+ root: resolvedRoot,
621
+ skillId: prompted.skillId,
622
+ runGitInit: prompted.runGitInit
623
+ });
624
+ } catch (e) {
625
+ p2.cancel(e instanceof Error ? e.message : String(e));
626
+ return { ok: false, exitCode: 1 };
627
+ }
628
+ if (!prompted.hadGit && !result.gitInitRan) {
629
+ p2.note(
630
+ "This folder is not a git repository. Run `git init` before using it as a local skissue registry.",
631
+ chalk2.yellow("Heads up")
632
+ );
633
+ }
634
+ const branchDefault = await gitBranchShowCurrent(resolvedRoot) ?? "main";
635
+ const branch = await p2.text({
636
+ message: "Branch label (stored in config; installs use git HEAD from that directory)",
637
+ initialValue: branchDefault,
638
+ validate: requiredTrimmed
639
+ });
640
+ if (p2.isCancel(branch)) {
641
+ p2.cancel("Aborted.");
642
+ return { ok: false, exitCode: 0 };
643
+ }
644
+ const cfg = ConfigSchema.parse({
645
+ registry: {
646
+ path: localRegistryPathForConfig(cwd, resolvedRoot),
647
+ branch: String(branch).trim()
648
+ },
649
+ skillsRoot: ".agents/skills"
650
+ });
651
+ return { ok: true, cfg, bootstrappedSkillId: result.skillId };
652
+ }
577
653
  async function runInit(cwd) {
578
654
  p2.intro(chalk2.bold("skissue init"));
579
655
  const existingPath = configPath(cwd);
@@ -646,23 +722,57 @@ async function runInit(cwd) {
646
722
  process.exit(0);
647
723
  }
648
724
  const resolvedExisting = resolve3(cwd, String(regPath).trim());
649
- const branchDefault = await gitBranchShowCurrent(resolvedExisting) ?? "main";
650
- const branch = await p2.text({
651
- message: "Branch label (stored in config; installs use git HEAD from that directory)",
652
- initialValue: branchDefault,
653
- validate: requiredTrimmed
654
- });
655
- if (p2.isCancel(branch)) {
656
- p2.cancel("Aborted.");
657
- process.exit(0);
725
+ if (!existsSync4(resolvedExisting) || !statSync3(resolvedExisting).isDirectory()) {
726
+ p2.cancel(`Path does not exist or is not a directory: ${resolvedExisting}`);
727
+ process.exitCode = 1;
728
+ return;
729
+ }
730
+ if (!registryDirectoryExists(resolvedExisting)) {
731
+ const pathLabel = String(regPath).trim() || ".";
732
+ const bootstrapHere = await p2.confirm({
733
+ message: `${chalk2.yellow("No registry/ directory")} at ${chalk2.cyan(pathLabel)}. Bootstrap a minimal sample registry (registry.json + registry/<skill>) here?`,
734
+ initialValue: true
735
+ });
736
+ if (p2.isCancel(bootstrapHere)) {
737
+ p2.cancel("Aborted.");
738
+ process.exit(0);
739
+ }
740
+ if (!bootstrapHere) {
741
+ p2.cancel(
742
+ "Aborted. Create a registry/ folder in that directory, or run skissue init and choose Bootstrap a minimal sample registry."
743
+ );
744
+ process.exit(0);
745
+ }
746
+ const outcome = await runLocalBootstrapFlow(cwd, resolvedExisting);
747
+ if (!outcome.ok) {
748
+ if (outcome.exitCode === 1) {
749
+ process.exitCode = 1;
750
+ } else {
751
+ process.exit(0);
752
+ }
753
+ return;
754
+ }
755
+ cfg = outcome.cfg;
756
+ bootstrappedSkillId = outcome.bootstrappedSkillId;
757
+ } else {
758
+ const branchDefault = await gitBranchShowCurrent(resolvedExisting) ?? "main";
759
+ const branch = await p2.text({
760
+ message: "Branch label (stored in config; installs use git HEAD from that directory)",
761
+ initialValue: branchDefault,
762
+ validate: requiredTrimmed
763
+ });
764
+ if (p2.isCancel(branch)) {
765
+ p2.cancel("Aborted.");
766
+ process.exit(0);
767
+ }
768
+ cfg = ConfigSchema.parse({
769
+ registry: {
770
+ path: String(regPath).trim(),
771
+ branch: String(branch).trim()
772
+ },
773
+ skillsRoot: ".agents/skills"
774
+ });
658
775
  }
659
- cfg = ConfigSchema.parse({
660
- registry: {
661
- path: String(regPath).trim(),
662
- branch: String(branch).trim()
663
- },
664
- skillsRoot: ".agents/skills"
665
- });
666
776
  } else {
667
777
  const pathRaw = await p2.text({
668
778
  message: "Path for the new registry root (relative to this project or absolute)",
@@ -675,58 +785,17 @@ async function runInit(cwd) {
675
785
  process.exit(0);
676
786
  }
677
787
  const resolvedRoot = resolve3(cwd, String(pathRaw).trim());
678
- const atConsumerRoot = resolvedRoot === resolve3(cwd);
679
- if (atConsumerRoot && !registryLayoutExists(resolvedRoot)) {
680
- const okRoot = await p2.confirm({
681
- message: "This adds registry.json and registry/ at your project root. Continue?",
682
- initialValue: false
683
- });
684
- if (p2.isCancel(okRoot) || !okRoot) {
685
- p2.cancel("Aborted.");
788
+ const outcome = await runLocalBootstrapFlow(cwd, resolvedRoot);
789
+ if (!outcome.ok) {
790
+ if (outcome.exitCode === 1) {
791
+ process.exitCode = 1;
792
+ } else {
686
793
  process.exit(0);
687
794
  }
688
- }
689
- const prompted = await promptMinimalRegistryScaffold(resolvedRoot);
690
- if (!prompted) {
691
- p2.cancel("Aborted. No files were changed.");
692
- process.exit(0);
693
- }
694
- let result;
695
- try {
696
- result = await scaffoldMinimalRegistry({
697
- root: resolvedRoot,
698
- skillId: prompted.skillId,
699
- runGitInit: prompted.runGitInit
700
- });
701
- } catch (e) {
702
- p2.cancel(e instanceof Error ? e.message : String(e));
703
- process.exitCode = 1;
704
795
  return;
705
796
  }
706
- bootstrappedSkillId = result.skillId;
707
- if (!prompted.hadGit && !result.gitInitRan) {
708
- p2.note(
709
- "This folder is not a git repository. Run `git init` before using it as a local skissue registry.",
710
- chalk2.yellow("Heads up")
711
- );
712
- }
713
- const branchDefault = await gitBranchShowCurrent(resolvedRoot) ?? "main";
714
- const branch = await p2.text({
715
- message: "Branch label (stored in config; installs use git HEAD from that directory)",
716
- initialValue: branchDefault,
717
- validate: requiredTrimmed
718
- });
719
- if (p2.isCancel(branch)) {
720
- p2.cancel("Aborted.");
721
- process.exit(0);
722
- }
723
- cfg = ConfigSchema.parse({
724
- registry: {
725
- path: localRegistryPathForConfig(cwd, resolvedRoot),
726
- branch: String(branch).trim()
727
- },
728
- skillsRoot: ".agents/skills"
729
- });
797
+ cfg = outcome.cfg;
798
+ bootstrappedSkillId = outcome.bootstrappedSkillId;
730
799
  }
731
800
  } else {
732
801
  const owner = await p2.text({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skissue",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",