origem 0.2.2 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +170 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,6 +27,19 @@ session hooks. Running it again is how you sync.
27
27
 
28
28
  `origem help` lists every option.
29
29
 
30
+ ## Slash commands in Claude Code
31
+
32
+ `connect` also installs these, so you do not have to remember the CLI:
33
+
34
+ | Command | What it does |
35
+ |---|---|
36
+ | `/origem:sync` | Pull the newest harness revision |
37
+ | `/origem:spec` | Open specs and what is missing to close a stage |
38
+ | `/origem:doctor` | Check drift between harness and repo |
39
+ | `/origem:propose` | Suggest a permanent change |
40
+ | `/origem:capture` | Turn this session's decisions into proposals |
41
+ | `/origem:bootstrap` | Propose a whole harness from the repo |
42
+
30
43
  ## What a harness is
31
44
 
32
45
  A complete Claude Code setup: instructions, rules, boundaries, memory, skills,
package/dist/index.js CHANGED
@@ -1402,6 +1402,20 @@ the project and you edit them normally.
1402
1402
  | \`npx origem doctor\` | Compares the harness against this repository and reports what drifted. |
1403
1403
  | \`npx origem bootstrap\` | Reads the repository and proposes a whole harness (for a project without one). |
1404
1404
 
1405
+ ## Slash commands available in this repository
1406
+
1407
+ The user can type these directly \u2014 prefer pointing them at one instead of
1408
+ explaining a raw \`npx\` invocation:
1409
+
1410
+ | Command | What it does |
1411
+ |---|---|
1412
+ | \`/origem:sync\` | Pull the newest harness revision |
1413
+ | \`/origem:spec\` | Open specs and what is missing to close a stage |
1414
+ | \`/origem:doctor\` | Check drift between harness and repo |
1415
+ | \`/origem:propose\` | Suggest a permanent change |
1416
+ | \`/origem:capture\` | Turn this session's decisions into proposals |
1417
+ | \`/origem:bootstrap\` | Propose a whole harness from the repo |
1418
+
1405
1419
  ## At the start of a task
1406
1420
 
1407
1421
  Run \`npx origem spec\`. If there is an open spec related to what the user asked, use its
@@ -1465,6 +1479,148 @@ function installOrigemSkill(baseDir, lock) {
1465
1479
  ok(`Origem skill installed at ${SKILL_PATH}`);
1466
1480
  }
1467
1481
 
1482
+ // src/commands-install.ts
1483
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
1484
+ import { dirname as dirname6, join as join7 } from "node:path";
1485
+ var COMMANDS = [
1486
+ {
1487
+ file: "sync.md",
1488
+ content: `---
1489
+ description: Pull the newest harness revision from Origem and rewrite the local config files
1490
+ allowed-tools: Bash(npx origem:*)
1491
+ ---
1492
+
1493
+ Run \`npx origem status\` first and show me what would change.
1494
+
1495
+ If any managed block was edited locally, stop and tell me before applying \u2014 the
1496
+ apply overwrites it.
1497
+
1498
+ Otherwise run \`npx origem connect\` and report what changed.
1499
+ `
1500
+ },
1501
+ {
1502
+ file: "spec.md",
1503
+ content: `---
1504
+ description: Show the open specs and which stage each one is in
1505
+ allowed-tools: Bash(npx origem:*)
1506
+ ---
1507
+
1508
+ Run \`npx origem spec\`.
1509
+
1510
+ Then tell me, in one short paragraph: which spec is closest to done, what is
1511
+ missing to close its current stage, and whether anything we did in this session
1512
+ already satisfies one of the acceptance criteria.
1513
+ `
1514
+ },
1515
+ {
1516
+ file: "doctor.md",
1517
+ content: `---
1518
+ description: Check whether the harness still matches this repository (drift)
1519
+ allowed-tools: Bash(npx origem:*)
1520
+ ---
1521
+
1522
+ Run \`npx origem doctor\`.
1523
+
1524
+ For each finding, say whether fixing it is worth it and what the fix would be.
1525
+ Do not change anything yet \u2014 drift is fixed in the harness, not in local files.
1526
+ If I agree with a fix, use \`/origem:propose\` to send it.
1527
+ `
1528
+ },
1529
+ {
1530
+ file: "propose.md",
1531
+ content: `---
1532
+ description: Suggest a permanent change to the harness (stays pending until accepted)
1533
+ allowed-tools: Bash(npx origem:*)
1534
+ ---
1535
+
1536
+ Propose this change to the harness: $ARGUMENTS
1537
+
1538
+ Before proposing:
1539
+ 1. Read the current harness pieces so you do not propose something already there.
1540
+ 2. Pick the right compartment \u2014 \`rule\`, \`boundary\`, \`memory\`, \`knowledge\`,
1541
+ \`skill\`, \`agent\`, \`prompt\`, \`instruction\`, \`harness\`, \`mcp\`, \`integration\`.
1542
+ 3. Write \`--why\` citing what happened in THIS session. That sentence is what
1543
+ the reviewer reads to decide \u2014 a generic justification gets rejected.
1544
+
1545
+ Then run \`npx origem propose\` with \`--type\`, \`--title\`, \`--content\` and \`--why\`.
1546
+
1547
+ Propose one thing only. If there are two changes, ask me which one first.
1548
+ `
1549
+ },
1550
+ {
1551
+ file: "capture.md",
1552
+ content: `---
1553
+ description: Turn what we just decided in this conversation into a harness proposal
1554
+ allowed-tools: Bash(npx origem:*)
1555
+ ---
1556
+
1557
+ Look back over this session and find decisions that should hold in FUTURE
1558
+ sessions \u2014 a convention we settled on, a correction I made more than once, a
1559
+ command that turned out to be wrong.
1560
+
1561
+ Ignore anything specific to the task at hand. A harness piece is only worth it
1562
+ if it would still be true next week.
1563
+
1564
+ List what you found, one line each, and ask me which to send. For each one I
1565
+ approve, run \`npx origem propose\` with \`--why\` citing the moment in this
1566
+ session that justifies it.
1567
+
1568
+ If you find nothing that durable, say so \u2014 proposing noise is worse than
1569
+ proposing nothing.
1570
+ `
1571
+ },
1572
+ {
1573
+ file: "bootstrap.md",
1574
+ content: `---
1575
+ description: Read this repository and propose a whole harness for it
1576
+ allowed-tools: Bash(npx origem:*)
1577
+ ---
1578
+
1579
+ Run \`npx origem bootstrap --dry-run\` and show me what it would propose.
1580
+
1581
+ Tell me what it got right and what is missing \u2014 it reads \`package.json\`,
1582
+ \`Makefile\`, the lockfile, docs and the commit history, so it will miss anything
1583
+ that only lives in someone's head.
1584
+
1585
+ If I approve, run \`npx origem bootstrap --yes\`, then remind me the pieces are
1586
+ PROPOSALS: nothing enters the harness until I accept the batch in Origem.
1587
+ `
1588
+ }
1589
+ ];
1590
+ var COMMANDS_DIR = join7(".claude", "commands", "origem");
1591
+ function installOrigemCommands(baseDir, lock) {
1592
+ const fromHarness = new Set(
1593
+ (lock?.files ?? []).map((f) => f.path.replace(/\\/g, "/"))
1594
+ );
1595
+ let written = 0;
1596
+ let skipped = 0;
1597
+ for (const cmd of COMMANDS) {
1598
+ const rel = join7(COMMANDS_DIR, cmd.file);
1599
+ if (fromHarness.has(rel.replace(/\\/g, "/"))) {
1600
+ skipped++;
1601
+ continue;
1602
+ }
1603
+ const abs = join7(baseDir, rel);
1604
+ if (existsSync8(abs) && readFileSync6(abs, "utf8") === cmd.content) continue;
1605
+ mkdirSync6(dirname6(abs), { recursive: true });
1606
+ writeFileSync6(abs, cmd.content, "utf8");
1607
+ written++;
1608
+ }
1609
+ if (written > 0) {
1610
+ ok(`${written} slash command(s) installed in ${COMMANDS_DIR}`);
1611
+ info(
1612
+ color.dim(
1613
+ ` /origem:sync /origem:spec /origem:doctor /origem:propose /origem:capture /origem:bootstrap`
1614
+ )
1615
+ );
1616
+ } else {
1617
+ info(color.dim(`slash commands already up to date in ${COMMANDS_DIR}`));
1618
+ }
1619
+ if (skipped > 0) {
1620
+ info(color.dim(` ${skipped} kept as the harness provides them`));
1621
+ }
1622
+ }
1623
+
1468
1624
  // src/connect.ts
1469
1625
  var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
1470
1626
  function base64url(buf) {
@@ -1662,7 +1818,9 @@ async function runConnect(opts) {
1662
1818
  dryRun: false,
1663
1819
  yes: opts.yes
1664
1820
  });
1665
- installOrigemSkill(process.cwd(), readLock(process.cwd()));
1821
+ const lock = readLock(process.cwd());
1822
+ installOrigemSkill(process.cwd(), lock);
1823
+ installOrigemCommands(process.cwd(), lock);
1666
1824
  if (opts.noHooks) {
1667
1825
  info(color.dim("hooks skipped (--no-hooks): the session will not report turns to Origem."));
1668
1826
  } else {
@@ -1682,7 +1840,7 @@ async function runConnect(opts) {
1682
1840
  }
1683
1841
 
1684
1842
  // src/propose.ts
1685
- import { readFileSync as readFileSync6 } from "node:fs";
1843
+ import { readFileSync as readFileSync7 } from "node:fs";
1686
1844
  var PROPOSABLE = [
1687
1845
  "instruction",
1688
1846
  "rule",
@@ -1703,7 +1861,7 @@ async function runPropose(opts) {
1703
1861
  if (!opts.rationale?.trim()) {
1704
1862
  throw new Error("--why is required: say why you are proposing it \u2014 that is what the reviewer reads.");
1705
1863
  }
1706
- const content = opts.file ? readFileSync6(opts.file, "utf8") : opts.content ?? "";
1864
+ const content = opts.file ? readFileSync7(opts.file, "utf8") : opts.content ?? "";
1707
1865
  if (!content.trim()) {
1708
1866
  throw new Error("Empty content. Pass --content <text> or --file <path>.");
1709
1867
  }
@@ -1771,8 +1929,8 @@ ${specs.length} open spec(s). When you close a stage, it is reported by \`origem
1771
1929
 
1772
1930
  // src/repo.ts
1773
1931
  import { execFileSync } from "node:child_process";
1774
- import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync2, statSync } from "node:fs";
1775
- import { basename as basename2, extname, join as join7, relative } from "node:path";
1932
+ import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync } from "node:fs";
1933
+ import { basename as basename2, extname, join as join8, relative } from "node:path";
1776
1934
  var SKIP_DIRS = /* @__PURE__ */ new Set([
1777
1935
  "node_modules",
1778
1936
  ".git",
@@ -1793,7 +1951,7 @@ var DOC_EXT = /* @__PURE__ */ new Set([".md", ".mdx"]);
1793
1951
  var MAX_FILE_CHARS = 2e4;
1794
1952
  function read(path) {
1795
1953
  try {
1796
- return readFileSync7(path, "utf8");
1954
+ return readFileSync8(path, "utf8");
1797
1955
  } catch {
1798
1956
  return null;
1799
1957
  }
@@ -1809,7 +1967,7 @@ function walk(dir, base, depth = 0, out = []) {
1809
1967
  for (const name of entries) {
1810
1968
  if (name.startsWith(".") && name !== ".github") continue;
1811
1969
  if (SKIP_DIRS.has(name)) continue;
1812
- const full = join7(dir, name);
1970
+ const full = join8(dir, name);
1813
1971
  let st;
1814
1972
  try {
1815
1973
  st = statSync(full);
@@ -1825,7 +1983,7 @@ function readRepo(baseDir) {
1825
1983
  const files = walk(baseDir, baseDir);
1826
1984
  const scripts = [];
1827
1985
  const languages = /* @__PURE__ */ new Set();
1828
- const pkgRaw = read(join7(baseDir, "package.json"));
1986
+ const pkgRaw = read(join8(baseDir, "package.json"));
1829
1987
  if (pkgRaw) {
1830
1988
  try {
1831
1989
  const pkg = JSON.parse(pkgRaw);
@@ -1835,14 +1993,14 @@ function readRepo(baseDir) {
1835
1993
  } catch {
1836
1994
  }
1837
1995
  }
1838
- const makefile = read(join7(baseDir, "Makefile"));
1996
+ const makefile = read(join8(baseDir, "Makefile"));
1839
1997
  if (makefile) {
1840
1998
  for (const line of makefile.split("\n")) {
1841
1999
  const m = /^([a-zA-Z][\w-]*):(?!=)/.exec(line);
1842
2000
  if (m) scripts.push({ name: m[1], command: `make ${m[1]}`, from: "Makefile" });
1843
2001
  }
1844
2002
  }
1845
- const pyproject = read(join7(baseDir, "pyproject.toml"));
2003
+ const pyproject = read(join8(baseDir, "pyproject.toml"));
1846
2004
  if (pyproject) languages.add("python");
1847
2005
  for (const f of files) {
1848
2006
  const ext = extname(f);
@@ -1855,8 +2013,8 @@ function readRepo(baseDir) {
1855
2013
  else if (ext === ".java") languages.add("java");
1856
2014
  else if (ext === ".cs") languages.add("csharp");
1857
2015
  }
1858
- const packageManager = existsSync8(join7(baseDir, "pnpm-lock.yaml")) ? "pnpm" : existsSync8(join7(baseDir, "yarn.lock")) ? "yarn" : existsSync8(join7(baseDir, "bun.lockb")) ? "bun" : existsSync8(join7(baseDir, "package-lock.json")) ? "npm" : null;
1859
- const docs = files.filter((f) => DOC_EXT.has(extname(f)) && (!f.includes("/") || f.startsWith("docs/"))).slice(0, 12).map((f) => ({ path: f, content: (read(join7(baseDir, f)) ?? "").slice(0, MAX_FILE_CHARS) })).filter((d) => d.content.trim());
2016
+ const packageManager = existsSync9(join8(baseDir, "pnpm-lock.yaml")) ? "pnpm" : existsSync9(join8(baseDir, "yarn.lock")) ? "yarn" : existsSync9(join8(baseDir, "bun.lockb")) ? "bun" : existsSync9(join8(baseDir, "package-lock.json")) ? "npm" : null;
2017
+ const docs = files.filter((f) => DOC_EXT.has(extname(f)) && (!f.includes("/") || f.startsWith("docs/"))).slice(0, 12).map((f) => ({ path: f, content: (read(join8(baseDir, f)) ?? "").slice(0, MAX_FILE_CHARS) })).filter((d) => d.content.trim());
1860
2018
  return {
1861
2019
  name: basename2(baseDir),
1862
2020
  scripts,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origem",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Connect a repository to your Origem harness: authorise in the browser, write CLAUDE.md and .claude/* to disk, and keep them in sync.",
5
5
  "type": "module",
6
6
  "bin": {