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.
- package/README.md +13 -0
- package/dist/index.js +176 -13
- 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
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
1770
|
-
import { basename as basename2, extname, join as
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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:
|
|
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