@holt-os/holt 0.10.0 → 0.12.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/README.md CHANGED
@@ -206,20 +206,37 @@ Wiki pages also participate in recall: each page is embedded and indexed like a
206
206
 
207
207
  A skill is a folder with a `SKILL.md`: YAML frontmatter (`name`, `description`) plus Markdown instructions. Same convention as agentskills.io and Claude Code, so skills are portable both ways. Skills are prompt text only; Holt never executes their contents.
208
208
 
209
- Holt looks in two places (workspace wins on a name clash): `./.holt/skills/` for this folder, `~/.holt/skills/` for every folder (`--global`).
209
+ Holt looks in three places, in precedence order (workspace shadows global shadows builtin on a name clash): `./.holt/skills/` for this folder, `~/.holt/skills/` for every folder (`--global`), and a read-only set of **built-in skills** that ship inside Holt and are available in every folder out of the box.
210
210
 
211
211
  ```
212
212
  holt skill list list installed skills
213
213
  holt skill show <name> print a skill
214
214
  holt skill create <name> [--global] scaffold a new skill
215
215
  holt skill search <query> find skills in the registry
216
- holt skill add <src|name> [--global] install from a git URL, path, or registry name
216
+ holt skill add <src|name> [--global] install from a git URL, git-url#subdir, path, or registry name
217
217
  holt skill publish [<name>] prepare a skill for the registry (prints a PR entry)
218
218
  holt skill remove <name> delete a skill
219
219
  ```
220
220
 
221
+ A source may carry a `#<subdir>` suffix (split on the first `#`) that points at one skill folder inside a repo or path: `holt skill add https://github.com/holt-os/registry#skills/pm-prd`. The subdir is a path, relative to the source root, that contains a `SKILL.md`. This lets a single repo hold many skills (one monorepo), which is exactly how the community registry is laid out. The subdir is validated to stay inside the source (absolute paths and `..` traversal are refused), since a registry entry is remote-controlled. Without a `#`, behaviour is unchanged: Holt looks for a `SKILL.md` at the source root or in exactly one immediate subfolder.
222
+
221
223
  In chat, run one with `/skill <name> [your input]`. Available skills are also listed to the brain each turn, so it knows what it can be asked to follow.
222
224
 
225
+ ### Built-in skills
226
+
227
+ Holt ships with a small, curated set of general-purpose skills, available in every folder with no setup. They are read-only (`holt skill remove` will not delete them); to change one, create a workspace or global skill with the same name and it takes over.
228
+
229
+ - **summarize**: condense a document, article, transcript, or pasted text into its key points.
230
+ - **explain**: explain a concept, error message, or piece of jargon in plain language.
231
+ - **action-items**: pull decisions, tasks, and owners out of messy notes or a meeting transcript.
232
+ - **brief**: a short briefing on the state of the current folder, using recalled memory plus your input.
233
+ - **code-review**: review a file or a diff for correctness bugs, edge cases, and simplifications.
234
+ - **explain-code**: walk through what a file or function does and how it fits into the wider system.
235
+ - **commit**: draft a clear commit message from staged changes or a described change.
236
+ - **plan**: break a goal into a concrete, ordered set of steps.
237
+ - **rewrite**: tighten and clarify a piece of text without changing its meaning.
238
+ - **decide**: weigh a decision and give a clear recommendation.
239
+
223
240
  ### Skill registry (`holt skill search` / `publish`)
224
241
 
225
242
  Beyond pasting URLs, Holt can discover and share skills through a **git-based registry**: a single JSON index living in a git repo. No server, no infra, in keeping with the rest of Holt.
@@ -241,7 +258,7 @@ The index is a JSON document:
241
258
  }
242
259
  ```
243
260
 
244
- `source` is anything `holt skill add` already accepts: a git URL (its `SKILL.md` may sit in one subfolder) or a local path. `tags` is optional. Only `name` and `source` are required per entry; malformed rows are skipped rather than failing the whole index.
261
+ `source` is anything `holt skill add` already accepts: a git URL (its `SKILL.md` may sit in one subfolder), a local path, or either of those with a `#<subdir>` suffix pointing at one skill folder inside the repo. That last form lets one repo hold many skills, so the community registry can be a single monorepo (e.g. `"source": "https://github.com/holt-os/registry#skills/pm-prd"`). `tags` is optional. Only `name` and `source` are required per entry; malformed rows are skipped rather than failing the whole index.
245
262
 
246
263
  - **Find:** `holt skill search <query>` fetches the index and lists skills whose name, description, or tags match (case-insensitive substring, name matches first). An empty query lists everything. It prints name, author, description, and source.
247
264
  - **Install by name:** `holt skill add <name>` resolves the name in the registry to its `source`, then installs through the exact same clone/copy path as `holt skill add <url|path>`. A git URL or an existing local path is still installed directly, unchanged.
package/dist/cli.js CHANGED
@@ -1032,9 +1032,15 @@ async function runApiBrain(brain, prompt, onChunk) {
1032
1032
 
1033
1033
  // src/skills.ts
1034
1034
  import { readFileSync as readFileSync6, readdirSync, existsSync as existsSync6, statSync as statSync2 } from "fs";
1035
- import { join as join6 } from "path";
1035
+ import { join as join6, dirname as dirname2 } from "path";
1036
+ import { fileURLToPath } from "url";
1037
+ function builtinSkillsRoot() {
1038
+ return join6(dirname2(fileURLToPath(import.meta.url)), "..", "skills");
1039
+ }
1036
1040
  function skillsRoot(scope) {
1037
- return scope === "workspace" ? join6(wsHoltDir(), "skills") : join6(GLOBAL_DIR, "skills");
1041
+ if (scope === "workspace") return join6(wsHoltDir(), "skills");
1042
+ if (scope === "builtin") return builtinSkillsRoot();
1043
+ return join6(GLOBAL_DIR, "skills");
1038
1044
  }
1039
1045
  function sanitizeName(name) {
1040
1046
  return (name || "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
@@ -1114,6 +1120,7 @@ function listScope(scope) {
1114
1120
  }
1115
1121
  function listSkills() {
1116
1122
  const byName = /* @__PURE__ */ new Map();
1123
+ for (const s of listScope("builtin")) byName.set(s.name, s);
1117
1124
  for (const s of listScope("global")) byName.set(s.name, s);
1118
1125
  for (const s of listScope("workspace")) byName.set(s.name, s);
1119
1126
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
@@ -3695,14 +3702,14 @@ import {
3695
3702
  cpSync,
3696
3703
  mkdtempSync
3697
3704
  } from "fs";
3698
- import { join as join12, resolve as resolve3 } from "path";
3705
+ import { join as join12, resolve as resolve3, sep, isAbsolute as isAbsolute3 } from "path";
3699
3706
  import { tmpdir } from "os";
3700
3707
  import { spawnSync as spawnSync2 } from "child_process";
3701
3708
 
3702
3709
  // src/registry.ts
3703
3710
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync8, statSync as statSync4 } from "fs";
3704
3711
  import { join as join11, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
3705
- import { pathToFileURL, fileURLToPath } from "url";
3712
+ import { pathToFileURL, fileURLToPath as fileURLToPath2 } from "url";
3706
3713
  var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/holt-os/registry/main/registry.json";
3707
3714
  var REGISTRY_REPO_URL = "https://github.com/holt-os/registry";
3708
3715
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -3767,7 +3774,7 @@ function writeCache(url, registry) {
3767
3774
  function localPathFor(loc) {
3768
3775
  if (loc.startsWith("file://")) {
3769
3776
  try {
3770
- return fileURLToPath(loc);
3777
+ return fileURLToPath2(loc);
3771
3778
  } catch {
3772
3779
  return null;
3773
3780
  }
@@ -3859,7 +3866,7 @@ function usage() {
3859
3866
  " holt skill show <name> print a skill",
3860
3867
  " holt skill create <name> [--global] scaffold a new skill",
3861
3868
  " holt skill search <query> find skills in the registry",
3862
- " holt skill add <src|name> [--global] install from a git URL, path, or registry name",
3869
+ " holt skill add <src|name> [--global] install from a git URL, git-url#subdir, path, or registry name",
3863
3870
  " holt skill publish [<name>] prepare a skill for the registry (prints a PR entry)",
3864
3871
  " holt skill remove <name> delete a skill",
3865
3872
  "",
@@ -3895,6 +3902,20 @@ var SKILL_TEMPLATE = (name) => [
3895
3902
  function isGitUrl(s) {
3896
3903
  return s.includes("://") || s.endsWith(".git") || s.startsWith("git@");
3897
3904
  }
3905
+ function splitSubpath(source) {
3906
+ const i = source.indexOf("#");
3907
+ if (i === -1) return { base: source, subpath: "" };
3908
+ return { base: source.slice(0, i), subpath: source.slice(i + 1) };
3909
+ }
3910
+ function resolveSubpathDir(fetched, subpath) {
3911
+ if (isAbsolute3(subpath)) return null;
3912
+ const segments = subpath.split(/[\\/]/);
3913
+ if (segments.some((s) => s === "..")) return null;
3914
+ const target = resolve3(fetched, subpath);
3915
+ const root = resolve3(fetched);
3916
+ if (target !== root && !target.startsWith(root + sep)) return null;
3917
+ return target;
3918
+ }
3898
3919
  function findSkillDir(root) {
3899
3920
  if (existsSync12(join12(root, "SKILL.md"))) return root;
3900
3921
  let subs = [];
@@ -3921,14 +3942,15 @@ function cmdList() {
3921
3942
  }
3922
3943
  const nameW = Math.max(4, ...skills.map((s) => s.name.length));
3923
3944
  const scopeW = Math.max(5, ...skills.map((s) => s.scope.length));
3924
- console.log("\n" + c.accent("Skills") + c.dim(" (workspace shadows global on name clash)"));
3945
+ console.log("\n" + c.accent("Skills") + c.dim(" (workspace shadows global shadows builtin on name clash)"));
3925
3946
  console.log(
3926
3947
  " " + c.dim("name".padEnd(nameW)) + " " + c.dim("scope".padEnd(scopeW)) + " " + c.dim("description")
3927
3948
  );
3928
3949
  for (const s of skills) {
3929
3950
  const desc = s.description.length > 68 ? s.description.slice(0, 67) + "\u2026" : s.description;
3951
+ const scopeCol = s.scope === "workspace" ? c.cyan(s.scope.padEnd(scopeW)) : s.scope === "builtin" ? c.accent(s.scope.padEnd(scopeW)) : c.dim(s.scope.padEnd(scopeW));
3930
3952
  console.log(
3931
- " " + c.bold(s.name.padEnd(nameW)) + " " + (s.scope === "workspace" ? c.cyan(s.scope.padEnd(scopeW)) : c.dim(s.scope.padEnd(scopeW))) + " " + (desc || c.dim("(no description)"))
3953
+ " " + c.bold(s.name.padEnd(nameW)) + " " + scopeCol + " " + (desc || c.dim("(no description)"))
3932
3954
  );
3933
3955
  }
3934
3956
  console.log(c.dim("\n holt skill show <name> print a skill"));
@@ -3978,14 +4000,15 @@ function cmdCreate(name, global) {
3978
4000
  console.log(c.dim(' Edit it, then run it in chat with "/skill ' + clean + '".\n'));
3979
4001
  }
3980
4002
  function installFromSource(source, scope) {
4003
+ const { base, subpath } = splitSubpath(source);
3981
4004
  let fetched = "";
3982
4005
  let tempDir = "";
3983
4006
  try {
3984
- if (isGitUrl(source)) {
4007
+ if (isGitUrl(base)) {
3985
4008
  tempDir = mkdtempSync(join12(tmpdir(), "holt-skill-"));
3986
4009
  console.log(c.dim(`
3987
- Cloning ${source} ...`));
3988
- const res = spawnSync2("git", ["clone", "--depth", "1", source, tempDir], { stdio: "ignore" });
4010
+ Cloning ${base} ...`));
4011
+ const res = spawnSync2("git", ["clone", "--depth", "1", base, tempDir], { stdio: "ignore" });
3989
4012
  if (res.status !== 0) {
3990
4013
  console.error(c.red(" Clone failed. Check the URL and that git is installed.\n"));
3991
4014
  process.exitCode = 1;
@@ -3993,7 +4016,7 @@ function installFromSource(source, scope) {
3993
4016
  }
3994
4017
  fetched = tempDir;
3995
4018
  } else {
3996
- fetched = resolve3(source);
4019
+ fetched = resolve3(base);
3997
4020
  if (!existsSync12(fetched)) {
3998
4021
  console.error(c.red(`
3999
4022
  No such path: ${fetched}
@@ -4002,9 +4025,31 @@ function installFromSource(source, scope) {
4002
4025
  return false;
4003
4026
  }
4004
4027
  }
4005
- const skillDir = findSkillDir(fetched);
4028
+ let searchRoot = fetched;
4029
+ if (subpath) {
4030
+ const target = resolveSubpathDir(fetched, subpath);
4031
+ if (!target) {
4032
+ console.error(c.red(`
4033
+ Refusing unsafe subpath "${subpath}": it must stay inside the source (no absolute or "..").
4034
+ `));
4035
+ process.exitCode = 1;
4036
+ return false;
4037
+ }
4038
+ if (!existsSync12(target) || !statSync5(target).isDirectory()) {
4039
+ console.error(c.red(`
4040
+ Subpath "${subpath}" does not exist in the source.
4041
+ `));
4042
+ process.exitCode = 1;
4043
+ return false;
4044
+ }
4045
+ searchRoot = target;
4046
+ }
4047
+ const skillDir = findSkillDir(searchRoot);
4006
4048
  if (!skillDir) {
4007
- console.error(c.red("\n Could not find a SKILL.md at the source root or in a single subfolder.\n"));
4049
+ const where = subpath ? `subpath "${subpath}"` : "the source root or in a single subfolder";
4050
+ console.error(c.red(`
4051
+ Could not find a SKILL.md at ${where}.
4052
+ `));
4008
4053
  process.exitCode = 1;
4009
4054
  return false;
4010
4055
  }
@@ -4048,11 +4093,12 @@ function installFromSource(source, scope) {
4048
4093
  }
4049
4094
  async function cmdAdd(source, global, refresh) {
4050
4095
  if (!source) {
4051
- console.log(c.dim("\n Usage: holt skill add <git-url|path|name> [--global]\n"));
4096
+ console.log(c.dim("\n Usage: holt skill add <git-url|git-url#subdir|path|name> [--global]\n"));
4052
4097
  return;
4053
4098
  }
4054
4099
  const scope = global ? "global" : "workspace";
4055
- if (isGitUrl(source) || existsSync12(resolve3(source))) {
4100
+ const { base } = splitSubpath(source);
4101
+ if (isGitUrl(base) || existsSync12(resolve3(base))) {
4056
4102
  installFromSource(source, scope);
4057
4103
  return;
4058
4104
  }
@@ -4181,6 +4227,13 @@ async function cmdRemove(name, ask) {
4181
4227
  const globalDir = join12(skillsRoot("global"), clean);
4182
4228
  const target = existsSync12(wsDir) ? wsDir : existsSync12(globalDir) ? globalDir : "";
4183
4229
  if (!target) {
4230
+ if (existsSync12(join12(skillsRoot("builtin"), clean))) {
4231
+ console.error(c.red(`
4232
+ "${clean}" is a built-in skill and cannot be removed; it ships inside Holt.`));
4233
+ console.error(c.dim(" Override it instead: create a workspace or global skill with the same name.\n"));
4234
+ process.exitCode = 1;
4235
+ return;
4236
+ }
4184
4237
  console.error(c.dim(`
4185
4238
  No skill named "${clean}" found.
4186
4239
  `));
@@ -4248,7 +4301,7 @@ async function skillCmd(sub, rest = []) {
4248
4301
 
4249
4302
  // src/commands/graph.ts
4250
4303
  import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync13 } from "fs";
4251
- import { join as join14, dirname as dirname3, resolve as resolve4 } from "path";
4304
+ import { join as join14, dirname as dirname4, resolve as resolve4 } from "path";
4252
4305
  import { spawn as spawn4 } from "child_process";
4253
4306
 
4254
4307
  // src/communities.ts
@@ -5046,7 +5099,7 @@ function renderGraphHtml(graph2, meta) {
5046
5099
 
5047
5100
  // src/ingest.ts
5048
5101
  import { readdirSync as readdirSync4, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
5049
- import { join as join13, relative, sep, extname, dirname as dirname2, basename as basename2 } from "path";
5102
+ import { join as join13, relative, sep as sep2, extname, dirname as dirname3, basename as basename2 } from "path";
5050
5103
  var CODE_EXT = /* @__PURE__ */ new Set([
5051
5104
  "js",
5052
5105
  "jsx",
@@ -5097,7 +5150,7 @@ var MAX_FILES = 2e3;
5097
5150
  var MAX_FILE_BYTES = 400 * 1024;
5098
5151
  var MAX_TOTAL_BYTES = 64 * 1024 * 1024;
5099
5152
  function toPosix(p) {
5100
- return sep === "/" ? p : p.split(sep).join("/");
5153
+ return sep2 === "/" ? p : p.split(sep2).join("/");
5101
5154
  }
5102
5155
  function ingestFolder(root, want = { code: true, docs: true }) {
5103
5156
  const files = [];
@@ -5286,7 +5339,7 @@ function resolvePySpec(f, spec, byId) {
5286
5339
  return null;
5287
5340
  }
5288
5341
  function toPosixDir(id) {
5289
- const d = toPosix(dirname2(id));
5342
+ const d = toPosix(dirname3(id));
5290
5343
  return d === "" || d === "." ? "." : d;
5291
5344
  }
5292
5345
  var MD_LINK_RE = /\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g;
@@ -5489,7 +5542,7 @@ async function graph(args = []) {
5489
5542
  if (opts.report) {
5490
5543
  const md = renderReport(g, { workspace: workspace() });
5491
5544
  const reportPath = opts.out ? resolve4(opts.out) : join14(workspace(), "GRAPH_REPORT.md");
5492
- mkdirSync10(dirname3(reportPath), { recursive: true });
5545
+ mkdirSync10(dirname4(reportPath), { recursive: true });
5493
5546
  writeFileSync11(reportPath, md, "utf8");
5494
5547
  const communities = new Set(g.nodes.map((n) => n.community ?? "0")).size;
5495
5548
  console.log("\n" + c.accent("Graph report written") + c.dim(" (this folder)"));
@@ -5511,7 +5564,7 @@ async function graph(args = []) {
5511
5564
  edges: g.edges.length
5512
5565
  });
5513
5566
  const outPath = opts.out ? resolve4(opts.out) : join14(wsHoltDir(), "graph.html");
5514
- mkdirSync10(dirname3(outPath), { recursive: true });
5567
+ mkdirSync10(dirname4(outPath), { recursive: true });
5515
5568
  writeFileSync11(outPath, html, "utf8");
5516
5569
  console.log("\n" + c.accent("Memory graph built") + c.dim(" (this folder)"));
5517
5570
  const parts = [`${turns.length} turns`, `${conceptCount} concepts`];
@@ -5800,7 +5853,7 @@ function holtTools() {
5800
5853
  }
5801
5854
 
5802
5855
  // src/version.ts
5803
- var VERSION = "0.10.0";
5856
+ var VERSION = "0.12.0";
5804
5857
 
5805
5858
  // src/commands/mcp.ts
5806
5859
  function printSetup() {
@@ -5847,7 +5900,7 @@ async function mcp(sub, rest = []) {
5847
5900
 
5848
5901
  // src/commands/hook.ts
5849
5902
  import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync14, mkdirSync as mkdirSync11, copyFileSync, realpathSync as realpathSync2 } from "fs";
5850
- import { join as join15, dirname as dirname4 } from "path";
5903
+ import { join as join15, dirname as dirname5 } from "path";
5851
5904
  import { homedir as homedir3 } from "os";
5852
5905
  var EVENT = {
5853
5906
  inject: "UserPromptSubmit",
@@ -5873,7 +5926,7 @@ function readSettings(path) {
5873
5926
  }
5874
5927
  }
5875
5928
  function writeSettings(path, settings) {
5876
- mkdirSync11(dirname4(path), { recursive: true });
5929
+ mkdirSync11(dirname5(path), { recursive: true });
5877
5930
  writeFileSync12(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
5878
5931
  }
5879
5932
  function backup(path) {
@@ -6319,7 +6372,7 @@ async function run(args) {
6319
6372
  // src/commands/schedule.ts
6320
6373
  import { spawnSync as spawnSync4 } from "child_process";
6321
6374
  import { writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync as rmSync6, existsSync as existsSync15 } from "fs";
6322
- import { dirname as dirname5 } from "path";
6375
+ import { dirname as dirname6 } from "path";
6323
6376
 
6324
6377
  // src/scheduler.ts
6325
6378
  import { homedir as homedir4 } from "os";
@@ -6483,7 +6536,7 @@ function parseAdd(rest) {
6483
6536
  }
6484
6537
  function installDarwin(job, holtPath) {
6485
6538
  const path = plistPath(job.id);
6486
- mkdirSync13(dirname5(path), { recursive: true });
6539
+ mkdirSync13(dirname6(path), { recursive: true });
6487
6540
  writeFileSync15(path, buildLaunchdPlist(job, holtPath), "utf8");
6488
6541
  const res = spawnSync4("launchctl", ["load", "-w", path], { encoding: "utf8" });
6489
6542
  if (res.status === 0) {
@@ -6636,7 +6689,7 @@ async function schedule(sub, rest = []) {
6636
6689
  // src/commands/routine.ts
6637
6690
  import { spawnSync as spawnSync5 } from "child_process";
6638
6691
  import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, rmSync as rmSync7, existsSync as existsSync16 } from "fs";
6639
- import { dirname as dirname6, resolve as resolve6 } from "path";
6692
+ import { dirname as dirname7, resolve as resolve6 } from "path";
6640
6693
 
6641
6694
  // src/routines.ts
6642
6695
  import { readFileSync as readFileSync17, writeFileSync as writeFileSync16, mkdirSync as mkdirSync14 } from "fs";
@@ -6717,7 +6770,7 @@ function routineJobId(name) {
6717
6770
  }
6718
6771
  function installDarwin2(job, holtPath) {
6719
6772
  const path = plistPath(job.id);
6720
- mkdirSync15(dirname6(path), { recursive: true });
6773
+ mkdirSync15(dirname7(path), { recursive: true });
6721
6774
  writeFileSync17(path, buildLaunchdPlist(job, holtPath), "utf8");
6722
6775
  const res = spawnSync5("launchctl", ["load", "-w", path], { encoding: "utf8" });
6723
6776
  if (res.status === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holt-os/holt",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "An open-source personal agent OS: any LLM, private memory you can see and walk.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "files": [
34
34
  "dist",
35
+ "skills",
35
36
  "README.md",
36
37
  "LICENSE"
37
38
  ],
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: action-items
3
+ description: Pull decisions, tasks, and owners out of messy notes or a meeting transcript. Use to turn a raw log into a clean follow-up list.
4
+ ---
5
+
6
+ # action-items
7
+
8
+ ## When to use
9
+
10
+ The user pastes meeting notes, a call transcript, a chat log, or scattered notes and wants the concrete outcomes extracted so nothing gets lost.
11
+
12
+ ## Instructions
13
+
14
+ 1. Read the whole input and separate the substance from the chit-chat. Ignore greetings, tangents, and thinking-out-loud that led nowhere.
15
+ 2. Sort what remains into three groups: DECISIONS (what was settled), TODOS (what someone will do), and OPEN QUESTIONS (what is unresolved).
16
+ 3. Write each TODO as "owner: task (due date if stated)". If no owner is named, mark it "unassigned". Preserve names and dates exactly as written.
17
+ 4. Write each DECISION as a short statement of what was chosen and, if given, why.
18
+ 5. Keep each item to one line. Do not invent owners, dates, or actions that were not in the source.
19
+ 6. Output the three lists in that order. Omit a list only if it is genuinely empty.
20
+
21
+ ## Example
22
+
23
+ DECISIONS
24
+ - Ship the beta to the waitlist on Monday, not the public.
25
+
26
+ TODOS
27
+ - Sam: send the waitlist export to marketing (by Fri).
28
+ - unassigned: write the launch FAQ.
29
+
30
+ OPEN QUESTIONS
31
+ - Do we gate signups behind an invite code?
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: brief
3
+ description: A short briefing on the state of the current folder, using recalled memory plus the user's input. Use to get oriented fast.
4
+ ---
5
+
6
+ # brief
7
+
8
+ ## When to use
9
+
10
+ The user wants a quick read on what is going on here: what they were working on, what is decided, and what is next. This skill pairs with Holt's memory recall, which surfaces relevant past notes for the current folder.
11
+
12
+ ## Instructions
13
+
14
+ 1. Assume relevant past notes and facts have been injected as context above. Treat them as your source of truth for prior state; combine them with anything new in the user's input.
15
+ 2. Synthesize the state into three threads: what is IN PROGRESS, what is DECIDED, and what is OPEN or NEXT.
16
+ 3. Lead with the single most important thing: the item that most needs attention or that unblocks the rest.
17
+ 4. Keep it scannable: a short intro line, then tight bullets. This is a briefing, not a report.
18
+ 5. If the recalled context is thin or empty, say so plainly and ask the user for the one detail that would make the brief useful, rather than guessing.
19
+ 6. Do not restate the raw notes; distill them into current status.
20
+
21
+ ## Example
22
+
23
+ Most important: the migration is blocked on a schema review that has been open three days.
24
+
25
+ In progress: rewriting the importer; decided: drop the legacy CSV path; open: who signs off on the schema change.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: code-review
3
+ description: Review a file or a diff for correctness bugs, edge cases, and simplifications. Use before merging or when a change feels risky.
4
+ ---
5
+
6
+ # code-review
7
+
8
+ ## When to use
9
+
10
+ The user shares a file, a function, or a diff and wants a focused review. The goal is catching real problems, not restyling working code.
11
+
12
+ ## Instructions
13
+
14
+ 1. Read for real defects first: logic errors, off-by-one and boundary mistakes, unhandled error paths, null or empty cases, security holes (injection, unescaped input, secrets), and resource leaks (unclosed files, sockets, handles).
15
+ 2. Then look for clarity and simplification: duplicated logic, dead code, overly clever constructs, and places a simpler shape would do the same job.
16
+ 3. Then note style issues, but only if they hurt readability. Skip pure nits unless the user asks for them.
17
+ 4. Report each finding as file:line, then what is wrong, then why it matters, then a concrete fix or the corrected snippet.
18
+ 5. Rank findings by severity: bugs and security first, then simplifications, then minor. Put the most important at the top.
19
+ 6. Be concise. If a change is clean, say so plainly instead of manufacturing feedback.
20
+
21
+ ## Example
22
+
23
+ - api.ts:42 (bug): the loop uses <= arr.length, so the last iteration reads arr[arr.length] which is undefined. Change to <.
24
+ - api.ts:88 (simplify): this if/else both return the same object with one field flipped; collapse to one return with a ternary.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: commit
3
+ description: Draft a clear commit message from staged changes or a described change. Use when you want a clean, conventional message.
4
+ ---
5
+
6
+ # commit
7
+
8
+ ## When to use
9
+
10
+ The user has staged changes (or describes a change in words) and wants a well-formed commit message.
11
+
12
+ ## Instructions
13
+
14
+ 1. Write a concise subject line in the imperative mood ("add", "fix", "remove", not "added" or "fixes"). Keep it under about 72 characters and do not end it with a period.
15
+ 2. If the change is trivial, the subject alone is enough. Stop there.
16
+ 3. If the change is non-trivial, add a blank line, then a short body that explains what changed and why, not how. The diff already shows how.
17
+ 4. If unrelated changes are bundled together, note that they would be cleaner as separate commits, but still give a usable message.
18
+ 5. Write a plain message. Do not add tool-attribution footers, "generated by" lines, or co-author trailers unless the user explicitly asks.
19
+ 6. If the intent is ambiguous from the changes, ask one clarifying question before guessing.
20
+
21
+ ## Example
22
+
23
+ Subject: fix off-by-one in pagination cursor
24
+
25
+ Body:
26
+ The cursor advanced past the last page because the bound used <= instead of <, returning an empty final page. Correct the comparison so the last item is included.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: decide
3
+ description: Weigh a decision and give a clear recommendation. Use when the user is stuck between options and wants a reasoned call.
4
+ ---
5
+
6
+ # decide
7
+
8
+ ## When to use
9
+
10
+ The user faces a choice (which tool, which approach, whether to do something) and wants help thinking it through and a straight recommendation, not just a list of considerations.
11
+
12
+ ## Instructions
13
+
14
+ 1. Restate the decision and the options on the table in one line each, so you are answering the real question.
15
+ 2. List the factors that actually matter for this choice: cost, time, risk, reversibility, effort, fit. Drop factors that do not move the decision.
16
+ 3. For each option, give honest pros and cons against those factors. Do not stack the deck; name the real downside of the option you prefer.
17
+ 4. Make a clear recommendation. State the main reason it wins and the biggest risk it carries.
18
+ 5. If the call genuinely hinges on one unknown, do not guess: name that missing fact and ask for it before committing.
19
+ 6. Keep it decisive. The user wants a lean, not a shrug.
20
+
21
+ ## Example
22
+
23
+ Decision: build the export feature now, or ship without it.
24
+
25
+ Build it: pro, unblocks the biggest customer; con, adds a week.
26
+ Ship without: pro, launch on time; con, that customer may churn.
27
+
28
+ Recommendation: build it. Losing the anchor customer outweighs a week's delay. Biggest risk: the week slips to two. Missing fact: has the customer given a hard deadline?
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: explain
3
+ description: Explain a concept, error message, or piece of jargon in plain language. Use when the user wants to understand something unfamiliar.
4
+ ---
5
+
6
+ # explain
7
+
8
+ ## When to use
9
+
10
+ The user asks what something means: a concept, an acronym, an error message, a term of art, or a confusing sentence. They want clarity, not a lecture.
11
+
12
+ ## Instructions
13
+
14
+ 1. Say what it is in one plain sentence, no jargon. If you must use a technical term, define it in the same breath.
15
+ 2. Say why it matters or where it shows up, so the user knows why they should care.
16
+ 3. Give one concrete analogy or a small worked example that makes it click.
17
+ 4. Match the level to the audience. If the user asks for "explain to a beginner" or "to a senior engineer", adjust depth, assumed background, and vocabulary accordingly. When unsure, aim for a smart non-expert.
18
+ 5. Keep it short. Stop once the idea lands; offer to go deeper rather than front-loading everything.
19
+
20
+ ## Example
21
+
22
+ Request: explain what a race condition is, to a beginner.
23
+
24
+ A race condition is when the result of your program depends on which of two things happens first, and you cannot control the order. It matters because it makes bugs that appear only sometimes. Think of two people editing the same shared doc line at the same time: whoever saves last wins, and the other edit silently vanishes.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: explain-code
3
+ description: Walk through what a file or function does and how it fits into the wider system. Use to onboard onto unfamiliar code.
4
+ ---
5
+
6
+ # explain-code
7
+
8
+ ## When to use
9
+
10
+ The user shares a file or function they did not write (or wrote a while ago) and wants to understand what it does, how it works, and how it connects to the rest of the code.
11
+
12
+ ## Instructions
13
+
14
+ 1. Start with a one-line purpose: what this code is for, in plain terms.
15
+ 2. Describe the flow and structure: the main steps or branches, in the order they run. Explain intent, not every line.
16
+ 3. Call out key dependencies and side effects: what it imports or calls, what state it reads or mutates, what it writes to disk, network, or the console.
17
+ 4. Flag any gotchas: assumptions it makes, inputs it does not handle, and non-obvious behavior a reader would trip on.
18
+ 5. Match depth to size. A small helper gets a few sentences; a large module gets a section per responsibility. Do not restate the code line by line.
19
+ 6. If something is genuinely unclear or looks buggy, say so rather than inventing a rationale.
20
+
21
+ ## Example
22
+
23
+ Purpose: parses a config file and returns a typed settings object.
24
+
25
+ Flow: reads the file, tries JSON.parse, falls back to defaults on any error, then validates required keys.
26
+
27
+ Gotcha: it swallows parse errors silently, so a malformed config looks identical to a missing one.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: plan
3
+ description: Break a goal into a concrete, ordered set of steps. Use when a task is big enough to need sequencing before starting.
4
+ ---
5
+
6
+ # plan
7
+
8
+ ## When to use
9
+
10
+ The user states a goal or a task and wants it broken into an actionable, ordered plan they can execute or hand off.
11
+
12
+ ## Instructions
13
+
14
+ 1. Restate the goal in one line so the target is unambiguous.
15
+ 2. List the steps in the order they must happen. Each step gets a clear done-condition: how you will know it is finished.
16
+ 3. Flag dependencies explicitly (step 4 needs step 2 done first) so the sequence is not just a wish list.
17
+ 4. Call out the riskiest or most uncertain step, the one most likely to blow up the timeline, so it can be tackled or de-risked early.
18
+ 5. Keep it actionable, not aspirational. Prefer "write the migration script and run it on a copy" over "improve the database".
19
+ 6. End with the single immediate next action the user should take right now.
20
+
21
+ ## Example
22
+
23
+ Goal: move the app from the old auth library to the new one.
24
+
25
+ 1. Inventory every call site of the old library (done: a list exists).
26
+ 2. Write an adapter matching the old interface (done: tests pass). RISKIEST: the token refresh path differs.
27
+ 3. Swap call sites behind a flag (done: app runs on the flag).
28
+
29
+ Next action: run the inventory grep in step 1.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: rewrite
3
+ description: Tighten and clarify a piece of text without changing its meaning. Use for a neutral clarity pass on text the user provides.
4
+ ---
5
+
6
+ # rewrite
7
+
8
+ ## When to use
9
+
10
+ The user pastes a paragraph, email, or passage and wants it cleaner and clearer while keeping the same meaning. This is a neutral clarity pass, distinct from a voice-anchored writing task: keep the author's own voice unless a tone is requested.
11
+
12
+ ## Instructions
13
+
14
+ 1. Preserve intent and every fact. Do not add claims, drop caveats, or change the point being made.
15
+ 2. Cut filler and hedging: "just", "really", "I think that", "in order to", throat-clearing intros, and redundant phrases.
16
+ 3. Prefer plain words over inflated ones ("use" not "utilize", "help" not "facilitate").
17
+ 4. Vary sentence length so it reads naturally; break up run-ons, and do not make everything staccato.
18
+ 5. Keep the author's voice and register. Only shift tone (more formal, warmer, punchier) if the user asks for it.
19
+ 6. Return only the rewritten text, then one short line noting what you changed (for example: "tightened, cut hedging, split two long sentences").
20
+
21
+ ## Example
22
+
23
+ Before: I just wanted to reach out in order to see if you might possibly have some availability to maybe hop on a quick call sometime.
24
+
25
+ After: Do you have time for a quick call this week?
26
+
27
+ Changed: cut hedging and filler, made it a direct ask.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: summarize
3
+ description: Condense a document, article, transcript, or pasted text into its key points. Use when the input is long and the user wants the gist fast.
4
+ ---
5
+
6
+ # summarize
7
+
8
+ ## When to use
9
+
10
+ The user pastes or points at a document, article, transcript, email thread, or any long block of text and wants a shorter version that keeps what matters.
11
+
12
+ ## Instructions
13
+
14
+ 1. Read the whole input first and identify its core purpose in one sentence (what it is and why it exists).
15
+ 2. Open with a one-line TLDR that captures the single most important takeaway.
16
+ 3. Pull the main points: arguments, decisions, numbers, dates, and named people or systems. Keep these verbatim where accuracy matters.
17
+ 4. Drop filler: greetings, repetition, hedging, and asides that do not change the meaning.
18
+ 5. Scale the output to the input. A few paragraphs get a short bulleted list; a long report gets tight prose grouped by theme. Do not pad.
19
+ 6. Do not add facts, opinions, or conclusions that are not in the source.
20
+
21
+ ## Example
22
+
23
+ TLDR: The team is shipping the payments rewrite next week despite one open risk.
24
+
25
+ - Decision: launch v2 on the 14th, feature-flagged to 10 percent of users.
26
+ - Open risk: the refund path is untested under load.
27
+ - Owner: Priya to run a load test before Friday.