Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b1d4261
feat(autoskills): foundation for opt-in markdown scanner + LLM mode
krlosflipdev Apr 23, 2026
146948c
feat(scanner): add markdown-scanner with JSON fence support + test he…
krlosflipdev Apr 23, 2026
ab1a711
feat(scanner): add shell/yaml/toml/ruby fences + stack headings with …
krlosflipdev Apr 23, 2026
b0d0fa0
feat(lib): add markdown source loader + merger, scanner edge-case tests
krlosflipdev Apr 23, 2026
8d0c234
feat(cli): wire --from-spec/--scan-docs + add cli-json serializers
krlosflipdev Apr 23, 2026
1276881
feat(cli): add list+prompt subcommands, ship skill-selection.md
krlosflipdev Apr 23, 2026
cff1b15
feat(subcommands): add install --only with fuzzy suggest + DI installer
krlosflipdev Apr 23, 2026
afb8faa
- main.ts: dispatch list/prompt/install subcommands before default …
krlosflipdev Apr 23, 2026
03f9e76
docs: document markdown scanner + LLM subcommands + refresh helpers/refs
krlosflipdev Apr 23, 2026
e9be97e
feat: Updated format examples for doc parsing
krlosflipdev Apr 24, 2026
56994c9
feat(scanner): flexible headings + numbered/comma/tables under stack
krlosflipdev Apr 24, 2026
6b6c34a
feat(scan-docs): include README.md in auto-scan whitelist
krlosflipdev Apr 24, 2026
375460a
refactor(scanner): drop Tecnologías keyword + document flexible formats
krlosflipdev Apr 24, 2026
986fd99
feat(cli): add --copy-prompt + rewrite prompt for spec-doc workflow
krlosflipdev Apr 24, 2026
7452cc7
feat(cli)!: rename copy/show prompt flags + drop `prompt` subcommand
krlosflipdev Apr 24, 2026
8731171
refactor: Updated commands for LLM in package README.md
krlosflipdev Apr 24, 2026
4ba0fa5
Merge branch 'main' into feat/markdown-scanner-llm-mode
krlosflipdev Apr 24, 2026
5da478a
Merge branch 'main' into feat/markdown-scanner-llm-mode
krlosflipdev Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ assert.ok(value);
assert.strictEqual(a, b);
```

- Use the shared helpers from `tests/helpers.mjs` (`useTmpDir`, `writePackageJson`, `writeJson`, `writeFile`, `addWorkspace`) to avoid duplicating filesystem setup logic in tests.
- Use the shared helpers from `tests/helpers.ts` (`useTmpDir`, `writePackageJson`, `writeJson`, `writeFile`, `addWorkspace`, `writeMarkdown`, `readFixtureSpec`, `parseJsonOutput`, `buildMarkdownFromParts`, `mockInstaller`) to avoid duplicating filesystem setup, CLI-output parsing, and installer-stubbing logic in tests.

## Output helpers

- **Never use `console.log` or `process.stdout.write` directly** in the CLI package (`packages/autoskills`). Use the `log` and `write` helpers exported from `colors.mjs` instead.
- **Never use `console.log` or `process.stdout.write` directly** in the CLI package (`packages/autoskills`). Use the `log` and `write` helpers exported from `colors.ts` instead.

```js
// ✅ Correct
import { log, write } from "./colors.mjs";
import { log, write } from "./colors.ts";

log("hello");
write("raw output\n");
Expand Down
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,57 @@ This keeps the package small while avoiding live downloads from third-party skil
## Options

```
-y, --yes Skip confirmation prompt
--dry-run Show what would be installed without installing
-h, --help Show help message
-y, --yes Skip confirmation prompt
--dry-run Show what would be installed without installing
--json Emit structured JSON (with --dry-run or subcommands)
--from-spec <path> Detect tech from a markdown spec file (any extension)
--scan-docs Auto-scan CLAUDE.md / AGENTS.md / README.md in the project
--show-specgen-prompt Print the shipped spec-generator prompt to stdout
--copy-specgen-prompt Copy the shipped spec-generator prompt to the OS clipboard
-h, --help Show help message
```

> `--from-spec` and `--scan-docs` parse **code fences** (`json`, `bash`/`sh`/`shell`/`zsh`, `yaml`/`yml`/`toml`, `ruby`/`gemfile`) plus content under **stack headings** (`## Tech Stack`, `## Stack`, `## Dependencies`, `## Built With`, `## Technologies`). Under a heading we accept dash/numbered bullets, GFM tables, and comma-separated inline lists. Decorated headings (`## 2. Tech Stack`, `## 🚀 Stack`, `## **Dependencies**`) are recognized. Markdown tables outside a stack heading are ignored. See [Markdown scanner](./packages/autoskills/README.md#markdown-scanner-opt-in) for details.

## LLM-driven mode

Beyond structural detection, `autoskills` exposes atomic subcommands so an external LLM CLI (Claude Code, Cursor, Codex) can reason over your requirement and produce a parseable spec:

```bash
npx autoskills list --json # full catalog
npx autoskills --show-specgen-prompt # spec-generator prompt to stdout
npx autoskills --copy-specgen-prompt # spec-generator prompt to clipboard
```

**Spec-doc flow:** run `--copy-specgen-prompt` (or `--show-specgen-prompt`), paste it under your requirement in any LLM chat, and the LLM writes `docs/specs-initial.md` for you to feed back via `autoskills --from-spec`.

What you actually type in the LLM chat — describe the project, not the stack. The LLM picks the techs.

If your chat has a `bash` tool (Claude Code, Cursor, Codex), one message is enough:

```text
I'm building an internal task manager for a small remote team.
Users sign in, create tasks, assign them to teammates, and see live
updates when someone changes status. Web + mobile-friendly, free-tier
deploy, end-to-end typed.

Run `autoskills --show-specgen-prompt` and follow the instructions it prints.
```

In any chat (no tools required), paste the prompt under your requirement:

```text
I'm building an internal task manager for a small remote team.
Users sign in, create tasks, assign them to teammates, and see live
updates when someone changes status. We want it on the web and
mobile-friendly. Need to ship a working demo this week, deploy
on a free tier, and keep the codebase typed end-to-end.

<paste the output of `autoskills --copy-specgen-prompt` here>
```

See the [package README](./packages/autoskills/README.md#subcommands-for-llm-integration) for the full workflow.

## Supported Technologies

Built to work across modern frontend, backend, mobile, cloud, and media stacks.
Expand Down
166 changes: 160 additions & 6 deletions packages/autoskills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,166 @@ If `claude-code` is auto-detected or passed with `-a`, `autoskills` writes a `CL

## Options

| Flag | Description |
| ----------------- | ----------------------------------------------------- |
| `-y`, `--yes` | Skip confirmation prompt, install all detected skills |
| `--dry-run` | Show detected skills without installing anything |
| `-v`, `--verbose` | Show install trace and error details |
| `-h`, `--help` | Show help message |
| Flag | Description |
| ----------------------- | --------------------------------------------------------------------------------------------------- |
| `-y`, `--yes` | Skip confirmation prompt, install all detected skills |
| `--dry-run` | Show detected skills without installing |
| `--clear-cache` | Clear downloaded skills cache |
| `--json` | Emit structured JSON (used with `--dry-run` or subcommands; errors return `{error:{code,message}}`) |
| `--from-spec <path>` | Scan a markdown spec file for tech (code fences + Tech Stack headings) |
| `--scan-docs` | Auto-scan `CLAUDE.md` / `AGENTS.md` / `README.md` in the project root |
| `--show-specgen-prompt` | Print the shipped spec-generator prompt to stdout |
| `--copy-specgen-prompt` | Copy the shipped spec-generator prompt to the OS clipboard |
| `-v`, `--verbose` | Show install trace and error details |
| `-a`, `--agent <ids>` | Install for specific IDEs only (e.g. `cursor`, `claude-code`) |
| `-h`, `--help` | Show help message |

## Markdown scanner (opt-in)

Detect tech from structured markdown docs — feature specs, `CLAUDE.md`,
`AGENTS.md`, or `README.md` — without having to populate `package.json`.

`--from-spec <path>` reads the file's contents regardless of extension.
`.md`, `.mdx`, `.markdown`, or `.txt` all work as long as the content uses
markdown code fences and headings.

```bash
# Scan a specific spec file
npx autoskills --from-spec ./docs/feature-spec.md

# Auto-scan CLAUDE.md / AGENTS.md in the current project
npx autoskills --scan-docs

# Combine with default detection (union)
npx autoskills --scan-docs --dry-run
```

The scanner recognizes two structures:

- **Code fences** — `json` (reads `dependencies`/`devDependencies`), `bash`/`sh`/`shell`/`zsh` (extracts `npm|pnpm|yarn|bun add/install` packages), `yaml`/`yml`/`toml`, `ruby`/`gemfile` (`gem '<name>'`).
- **Stack headings** — `## Tech Stack`, `## Stack`, `## Dependencies`, `## Built With`, `## Technologies` (case-insensitive, h1–h3). Content under the heading is matched against technology names and aliases — see "Supported formats under stack headings" below for bullet, table, numbered-list, and inline shapes.

Prose ("we'll use React") outside these structures is ignored to prevent false positives.

### Supported formats under stack headings

**Heading shapes recognized** (h1–h3, case-insensitive, keyword must be exact after stripping decoration):

```markdown
## Tech Stack
## 2. Tech Stack
## 1) Stack
## 🚀 Tech Stack
## **Dependencies**
## __Dependencies__
## [Stack]
## Tech Stack:
## Tech Stack (frontend)
```

Prose narrative headings like `## Why we chose our Stack` are rejected (decoration is stripped but the keyword itself must be the whole title).

Valid keywords (after normalization, case-insensitive): `Tech Stack`, `Stack`, `Dependencies`, `Built With`, `Technologies`.

**Content shapes recognized under the heading:**

1. **Dash / asterisk / plus bullets**

```markdown
## Tech Stack
- Astro
- Tailwind CSS
```

2. **Numbered bullets** (`1.` or `1)`)

```markdown
## Tech Stack
1. Astro
2. Tailwind CSS
```

3. **GFM tables** — first column by default; if the header row contains a column matching `tech|technology|technologies|framework|library|libraries|package|packages|dependency|dependencies|name|stack` (case-insensitive), that column is used instead.

```markdown
## Tech Stack

| Category | Framework | Notes |
| -------- | --------- | ---------- |
| UI | React | renderer |
| Styling | Tailwind | utility |
```

Cell content is normalized: links (`[Astro](https://astro.build)` → `Astro`), bold/italic wrappers, backticks, images, and leading emoji are stripped. Multi-tech cells are split on commas (`| Astro, Tailwind |`).

4. **Comma-separated inline** — a non-bullet line with commas is split and each piece is matched

```markdown
## Tech Stack

Astro, Tailwind CSS, TypeScript
```

Note: prefixes like `"Also: Astro, Tailwind"` are NOT parsed — `normalizeBullet` does not strip colon-prefixed labels. Use plain `"Astro, Tailwind"`.

**Bullet / inline-piece / cell normalization**

After the format-specific extraction, each candidate string goes through:

1. Parenthetical annotations are dropped — `Astro (SSR)` → `Astro`.
2. Em-dash, en-dash, or space-hyphen-space + trailing text is dropped — `Astro — 4.0` → `Astro`; `Astro - 4.0` → `Astro`. (Inline hyphens without surrounding spaces are preserved — `shadcn/ui` is kept intact.)
3. A trailing digit-leading version token is dropped — `Astro 4.0.1` → `Astro`. (Non-digit trailing tokens like `latest` are not stripped yet.)

The resulting string must match a technology `name` or `alias` exactly (run `autoskills list --json` to see the catalog).

Tables, bullets, inline comma-separated lines, and numbered lists **only** parse under a recognized stack heading — free-floating tables or lists elsewhere in the document are ignored.

**Default behavior is unchanged.** No markdown is read unless `--from-spec` or `--scan-docs` is passed.

## Subcommands (for LLM integration)

Atomic subcommands let an external LLM CLI (Claude Code, Cursor, Codex) drive autoskills over prose specs that the structural scanner cannot parse.

```bash
# Full catalog as JSON (LLM uses this to map your requirement to canonical tech names)
npx autoskills list --json

# Spec-generator prompt (LLM guidance for writing docs/specs-initial.md)
npx autoskills --show-specgen-prompt # stdout
npx autoskills --copy-specgen-prompt # OS clipboard
```

The shipped prompt drives a **spec-doc workflow**:

1. Run `autoskills --show-specgen-prompt` (stdout) or `autoskills --copy-specgen-prompt` (clipboard).
2. Open your LLM chat. Describe your project (the problem, not the stack), then attach the prompt below it.
3. The LLM fetches the catalog (`autoskills list --json`), matches techs from your requirement to canonical names, and writes `docs/specs-initial.md` with a `## Tech Stack` section the markdown scanner can parse.
4. The LLM tells you to run `autoskills --from-spec docs/specs-initial.md` in another terminal — it does **not** install anything itself.
5. You run `--from-spec`. autoskills detects + installs deterministically.

#### What you actually type in the LLM chat

**Option A — let the LLM fetch the prompt** (Claude Code, Cursor, Codex — chats with a `bash` tool):

```text
<your project requirement here>

Run `autoskills --show-specgen-prompt` and follow the instructions it prints.
```

**Option B — paste from clipboard** (any chat, no tools required):

```text
<your project requirement here>

<paste the output of `autoskills --copy-specgen-prompt` here>
```

Either way, the LLM ends by telling you to run `autoskills --from-spec docs/specs-initial.md` yourself.

The shipped prompt lives at `prompts/spec-generator-prompt.md` inside the package.

All subcommands emit structured JSON errors when `--json` is passed, for programmatic parsing. Error codes include `unknown-subcommand`, `install-missing-only`, `install-empty-only`, `install-unknown-id` (with fuzzy-match suggestion), `json-requires-subcommand-or-dry-run`, `cli-arg-invalid`, `internal-error`, and `prompt-file-missing`.

## Supported Technologies

Expand Down
104 changes: 104 additions & 0 deletions packages/autoskills/cli-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { Technology, ComboSkill } from "./skills-map.ts";
import { SKILLS_MAP, COMBO_SKILLS_MAP, FRONTEND_BONUS_SKILLS } from "./skills-map.ts";

// ── list ────────────────────────────────────────────────────

export interface ListJsonTechnology {
id: string;
name: string;
aliases: string[];
description?: string;
skills: string[];
}

export interface ListJsonCombo {
id: string;
name: string;
requires: string[];
skills: string[];
}

export interface ListJson {
version: string;
technologies: ListJsonTechnology[];
combos: ListJsonCombo[];
frontend_bonus: string[];
}

export function serializeList(args: { version: string; filter?: string }): ListJson {
const filter = args.filter?.trim().toLowerCase();
const techs = filter
? SKILLS_MAP.filter(t =>
t.id.toLowerCase() === filter ||
t.name.toLowerCase() === filter ||
(t.aliases?.some(a => a.toLowerCase() === filter) ?? false),
)
: SKILLS_MAP;
return {
version: args.version,
technologies: techs.map(t => ({
id: t.id,
name: t.name,
aliases: [...(t.aliases ?? [])],
description: t.description,
skills: [...t.skills],
})),
combos: COMBO_SKILLS_MAP.map(c => ({
id: c.id,
name: c.name,
requires: [...c.requires],
skills: [...c.skills],
})),
frontend_bonus: [...FRONTEND_BONUS_SKILLS],
};
}

// ── dry-run ─────────────────────────────────────────────────

export interface DryRunSkill {
id: string;
path: string;
source_tech: string;
installed: boolean;
}

export interface DryRunJson {
detected_technologies: string[];
detected_combos: string[];
is_frontend: boolean;
skills_resolved: DryRunSkill[];
agents_detected: string[];
}

export function serializeDryRun(data: DryRunJson): DryRunJson {
return data;
}

// ── install ─────────────────────────────────────────────────

export interface InstallJson {
installed: { id: string; path: string }[];
failed: { id: string; error: string }[];
agents: string[];
}

export function serializeInstall(data: InstallJson): InstallJson {
return data;
}

// ── error ───────────────────────────────────────────────────

export interface ErrorEnvelope {
code: string;
message: string;
hint?: string;
details?: Record<string, unknown>;
}

export interface ErrorJson {
error: ErrorEnvelope;
}

export function serializeError(err: ErrorEnvelope): ErrorJson {
return { error: err };
}
Loading