origem 0.2.1 → 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 +176 -13
  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
@@ -636,6 +636,10 @@ function resolveApiUrl(flag) {
636
636
  const fromLock = readLock(process.cwd())?.apiUrl || void 0;
637
637
  return (flag || process.env.ORIGEM_API_URL || fromLock || readConfig().apiUrl || DEFAULT_API).replace(/\/$/, "");
638
638
  }
639
+ function resolveConnectUrl(flag) {
640
+ const fromLock = readLock(process.cwd())?.apiUrl || void 0;
641
+ return (flag || process.env.ORIGEM_API_URL || fromLock || DEFAULT_API).replace(/\/$/, "");
642
+ }
639
643
  function resolveKey(flag) {
640
644
  const key = flag || process.env.ORIGEM_TOKEN || readConfig().token || "";
641
645
  if (!key) {
@@ -1398,6 +1402,20 @@ the project and you edit them normally.
1398
1402
  | \`npx origem doctor\` | Compares the harness against this repository and reports what drifted. |
1399
1403
  | \`npx origem bootstrap\` | Reads the repository and proposes a whole harness (for a project without one). |
1400
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
+
1401
1419
  ## At the start of a task
1402
1420
 
1403
1421
  Run \`npx origem spec\`. If there is an open spec related to what the user asked, use its
@@ -1461,6 +1479,148 @@ function installOrigemSkill(baseDir, lock) {
1461
1479
  ok(`Origem skill installed at ${SKILL_PATH}`);
1462
1480
  }
1463
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
+
1464
1624
  // src/connect.ts
1465
1625
  var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
1466
1626
  function base64url(buf) {
@@ -1626,6 +1786,7 @@ async function runConnect(opts) {
1626
1786
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
1627
1787
  authorizeUrl.searchParams.set("state", state);
1628
1788
  authorizeUrl.searchParams.set("scope", "read write");
1789
+ info(`${color.cyan("\u2192")} connecting to ${color.bold(apiUrl)}`);
1629
1790
  info(`${color.cyan("\u2192")} opening your browser to authorise\u2026`);
1630
1791
  info(color.dim(` if nothing opens, paste this in your browser:
1631
1792
  ${authorizeUrl.toString()}`));
@@ -1657,7 +1818,9 @@ async function runConnect(opts) {
1657
1818
  dryRun: false,
1658
1819
  yes: opts.yes
1659
1820
  });
1660
- installOrigemSkill(process.cwd(), readLock(process.cwd()));
1821
+ const lock = readLock(process.cwd());
1822
+ installOrigemSkill(process.cwd(), lock);
1823
+ installOrigemCommands(process.cwd(), lock);
1661
1824
  if (opts.noHooks) {
1662
1825
  info(color.dim("hooks skipped (--no-hooks): the session will not report turns to Origem."));
1663
1826
  } else {
@@ -1677,7 +1840,7 @@ async function runConnect(opts) {
1677
1840
  }
1678
1841
 
1679
1842
  // src/propose.ts
1680
- import { readFileSync as readFileSync6 } from "node:fs";
1843
+ import { readFileSync as readFileSync7 } from "node:fs";
1681
1844
  var PROPOSABLE = [
1682
1845
  "instruction",
1683
1846
  "rule",
@@ -1698,7 +1861,7 @@ async function runPropose(opts) {
1698
1861
  if (!opts.rationale?.trim()) {
1699
1862
  throw new Error("--why is required: say why you are proposing it \u2014 that is what the reviewer reads.");
1700
1863
  }
1701
- const content = opts.file ? readFileSync6(opts.file, "utf8") : opts.content ?? "";
1864
+ const content = opts.file ? readFileSync7(opts.file, "utf8") : opts.content ?? "";
1702
1865
  if (!content.trim()) {
1703
1866
  throw new Error("Empty content. Pass --content <text> or --file <path>.");
1704
1867
  }
@@ -1766,8 +1929,8 @@ ${specs.length} open spec(s). When you close a stage, it is reported by \`origem
1766
1929
 
1767
1930
  // src/repo.ts
1768
1931
  import { execFileSync } from "node:child_process";
1769
- import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync2, statSync } from "node:fs";
1770
- 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";
1771
1934
  var SKIP_DIRS = /* @__PURE__ */ new Set([
1772
1935
  "node_modules",
1773
1936
  ".git",
@@ -1788,7 +1951,7 @@ var DOC_EXT = /* @__PURE__ */ new Set([".md", ".mdx"]);
1788
1951
  var MAX_FILE_CHARS = 2e4;
1789
1952
  function read(path) {
1790
1953
  try {
1791
- return readFileSync7(path, "utf8");
1954
+ return readFileSync8(path, "utf8");
1792
1955
  } catch {
1793
1956
  return null;
1794
1957
  }
@@ -1804,7 +1967,7 @@ function walk(dir, base, depth = 0, out = []) {
1804
1967
  for (const name of entries) {
1805
1968
  if (name.startsWith(".") && name !== ".github") continue;
1806
1969
  if (SKIP_DIRS.has(name)) continue;
1807
- const full = join7(dir, name);
1970
+ const full = join8(dir, name);
1808
1971
  let st;
1809
1972
  try {
1810
1973
  st = statSync(full);
@@ -1820,7 +1983,7 @@ function readRepo(baseDir) {
1820
1983
  const files = walk(baseDir, baseDir);
1821
1984
  const scripts = [];
1822
1985
  const languages = /* @__PURE__ */ new Set();
1823
- const pkgRaw = read(join7(baseDir, "package.json"));
1986
+ const pkgRaw = read(join8(baseDir, "package.json"));
1824
1987
  if (pkgRaw) {
1825
1988
  try {
1826
1989
  const pkg = JSON.parse(pkgRaw);
@@ -1830,14 +1993,14 @@ function readRepo(baseDir) {
1830
1993
  } catch {
1831
1994
  }
1832
1995
  }
1833
- const makefile = read(join7(baseDir, "Makefile"));
1996
+ const makefile = read(join8(baseDir, "Makefile"));
1834
1997
  if (makefile) {
1835
1998
  for (const line of makefile.split("\n")) {
1836
1999
  const m = /^([a-zA-Z][\w-]*):(?!=)/.exec(line);
1837
2000
  if (m) scripts.push({ name: m[1], command: `make ${m[1]}`, from: "Makefile" });
1838
2001
  }
1839
2002
  }
1840
- const pyproject = read(join7(baseDir, "pyproject.toml"));
2003
+ const pyproject = read(join8(baseDir, "pyproject.toml"));
1841
2004
  if (pyproject) languages.add("python");
1842
2005
  for (const f of files) {
1843
2006
  const ext = extname(f);
@@ -1850,8 +2013,8 @@ function readRepo(baseDir) {
1850
2013
  else if (ext === ".java") languages.add("java");
1851
2014
  else if (ext === ".cs") languages.add("csharp");
1852
2015
  }
1853
- 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;
1854
- 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());
1855
2018
  return {
1856
2019
  name: basename2(baseDir),
1857
2020
  scripts,
@@ -2255,7 +2418,7 @@ async function main() {
2255
2418
  switch (cmd) {
2256
2419
  case "connect": {
2257
2420
  await runConnect({
2258
- apiUrl: resolveApiUrl(flags.apiUrl),
2421
+ apiUrl: resolveConnectUrl(flags.apiUrl),
2259
2422
  scope: flags.scope,
2260
2423
  // `targets` já traz o default (CLAUDE_TARGETS); passar flags.targets aqui
2261
2424
  // faria o apply cair no "todos os alvos" e escrever GEMINI.md, .cursor/…
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origem",
3
- "version": "0.2.1",
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": {