@aacombarro89/praxis 0.1.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/dist/anchors.d.ts +36 -0
- package/dist/anchors.js +197 -0
- package/dist/anchors.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -0
- package/dist/emit.d.ts +51 -0
- package/dist/emit.js +253 -0
- package/dist/emit.js.map +1 -0
- package/dist/external.d.ts +61 -0
- package/dist/external.js +155 -0
- package/dist/external.js.map +1 -0
- package/dist/init.d.ts +45 -0
- package/dist/init.js +123 -0
- package/dist/init.js.map +1 -0
- package/dist/manifest.d.ts +36 -0
- package/dist/manifest.js +63 -0
- package/dist/manifest.js.map +1 -0
- package/dist/merge-json.d.ts +83 -0
- package/dist/merge-json.js +207 -0
- package/dist/merge-json.js.map +1 -0
- package/dist/merge.d.ts +53 -0
- package/dist/merge.js +89 -0
- package/dist/merge.js.map +1 -0
- package/dist/packages.d.ts +47 -0
- package/dist/packages.js +121 -0
- package/dist/packages.js.map +1 -0
- package/dist/permissions.d.ts +54 -0
- package/dist/permissions.js +83 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugins.d.ts +21 -0
- package/dist/plugins.js +88 -0
- package/dist/plugins.js.map +1 -0
- package/dist/program.d.ts +2 -0
- package/dist/program.js +201 -0
- package/dist/program.js.map +1 -0
- package/dist/sync.d.ts +37 -0
- package/dist/sync.js +45 -0
- package/dist/sync.js.map +1 -0
- package/package.json +40 -0
- package/packages/external/drawio/package.yaml +7 -0
- package/packages/external/drawio/plugins.yaml +13 -0
- package/packages/external/ponytail/package.yaml +7 -0
- package/packages/external/ponytail/plugins.yaml +13 -0
- package/packages/layer1/instruction-upkeep/commands/audit.md +41 -0
- package/packages/layer1/instruction-upkeep/package.yaml +8 -0
- package/packages/layer1/instruction-upkeep/rules.md +21 -0
- package/packages/layer1/karpathy-claude/package.yaml +5 -0
- package/packages/layer1/karpathy-claude/rules.md +35 -0
- package/packages/layer1/safe-permissions/package.yaml +5 -0
- package/packages/layer1/safe-permissions/permissions.yaml +26 -0
- package/packages/layer1/session-handoff/commands/handoff.md +185 -0
- package/packages/layer1/session-handoff/package.yaml +10 -0
- package/packages/layer1/session-handoff/rules.md +20 -0
- package/packages/layer1/upkeep/commands/upkeep.md +31 -0
- package/packages/layer1/upkeep/package.yaml +10 -0
- package/packages/layer1/upkeep/rules.md +12 -0
- package/packages/layer1/wiki-memory/commands/wiki.md +103 -0
- package/packages/layer1/wiki-memory/package.yaml +10 -0
- package/packages/layer1/wiki-memory/rules.md +49 -0
- package/packages/layer2/node-recipes/package.yaml +10 -0
- package/packages/layer2/node-recipes/rules.md +33 -0
- package/packages/layer2/node-testing/package.yaml +10 -0
- package/packages/layer2/node-testing/rules.md +33 -0
- package/packages/layer2/python-backend-recipes/package.yaml +10 -0
- package/packages/layer2/python-backend-recipes/rules.md +37 -0
- package/packages/layer2/python-testing/package.yaml +10 -0
- package/packages/layer2/python-testing/rules.md +33 -0
- package/packages/layer2/react-components/package.yaml +10 -0
- package/packages/layer2/react-components/rules.md +40 -0
- package/packages/layer2/react-testing/package.yaml +10 -0
- package/packages/layer2/react-testing/rules.md +35 -0
package/dist/external.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { resolvePackages } from "./packages.js";
|
|
6
|
+
/**
|
|
7
|
+
* External installer packages (docs/wiki/external-packages.md, the bounded
|
|
8
|
+
* execution relaxation — see docs/wiki/decisions.md). A "rules"/"permissions"/
|
|
9
|
+
* "commands" package ships tool-neutral source that the *pure* emit pipeline
|
|
10
|
+
* (src/emit.ts) translates and writes. An "external" package ships no source
|
|
11
|
+
* at all — only a pinned install command that some other tool (npx/git) runs
|
|
12
|
+
* to drop its own already-tool-shaped files into the methodology layer (e.g.
|
|
13
|
+
* `.claude/skills/…`).
|
|
14
|
+
*
|
|
15
|
+
* That's side-effecting and untranslatable, so it never becomes an `EmitOp`.
|
|
16
|
+
* This module is the separate, explicit seam: `planExternal` decides *what*
|
|
17
|
+
* would happen (pure, no I/O — same plan/apply split as src/emit.ts +
|
|
18
|
+
* src/sync.ts), and `executeAction` is the one place that actually shells out,
|
|
19
|
+
* with the runner injected so tests never really execute a command.
|
|
20
|
+
*/
|
|
21
|
+
const LOCK_FILE = "praxis-external.lock";
|
|
22
|
+
// One row per (package, target) pair Praxis has installed. No timestamps —
|
|
23
|
+
// the pinned spec is the whole identity, so the lock is stable in git and
|
|
24
|
+
// re-syncing is deterministic: same spec ⇒ same action.
|
|
25
|
+
const lockEntrySchema = z.strictObject({
|
|
26
|
+
name: z.string().min(1),
|
|
27
|
+
spec: z.array(z.string().min(1)).min(1),
|
|
28
|
+
targets: z.array(z.string().min(1)).min(1),
|
|
29
|
+
});
|
|
30
|
+
const lockSchema = z.strictObject({
|
|
31
|
+
version: z.literal(1),
|
|
32
|
+
installed: z.array(lockEntrySchema).default([]),
|
|
33
|
+
});
|
|
34
|
+
export const EMPTY_LOCK = { version: 1, installed: [] };
|
|
35
|
+
/**
|
|
36
|
+
* Decide what `sync` would do with every selected external package, against
|
|
37
|
+
* the current lock. Pure: the lock is injected so this is testable without
|
|
38
|
+
* disk, and it performs no execution itself.
|
|
39
|
+
*
|
|
40
|
+
* - `install`: no lock entry for this package yet.
|
|
41
|
+
* - `reinstall`: lock entry exists but its spec/targets no longer match the
|
|
42
|
+
* package's current declaration (the manifest changed under it).
|
|
43
|
+
* - `skip`: lock entry matches exactly — idempotent no-op.
|
|
44
|
+
* - `orphan`: a lock entry exists for a package no longer selected (or with
|
|
45
|
+
* no targets left after quarantine) — offer to remove, never silently.
|
|
46
|
+
*/
|
|
47
|
+
export function planExternal(manifest, lock) {
|
|
48
|
+
const packages = resolvePackages(manifest.packages).filter((p) => p.provides.includes("external"));
|
|
49
|
+
const manifestTargets = new Set(manifest.targets);
|
|
50
|
+
const actions = [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const pkg of packages) {
|
|
53
|
+
const block = pkg.external;
|
|
54
|
+
if (!block)
|
|
55
|
+
continue; // schema guarantees this is unreachable; guard for type-narrowing only
|
|
56
|
+
const targets = block.targets.filter((t) => manifestTargets.has(t));
|
|
57
|
+
seen.add(pkg.name);
|
|
58
|
+
const existing = lock.installed.find((e) => e.name === pkg.name);
|
|
59
|
+
if (targets.length === 0) {
|
|
60
|
+
// Fully quarantined out for this manifest's targets: nothing to run. If a
|
|
61
|
+
// prior sync installed it for targets no longer selected, that's an orphan.
|
|
62
|
+
if (existing) {
|
|
63
|
+
actions.push({ kind: "orphan", name: pkg.name, targets: existing.targets, command: block.uninstall });
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!existing) {
|
|
68
|
+
actions.push({ kind: "install", name: pkg.name, targets, command: block.command });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const specMatches = arraysEqual(existing.spec, block.command) && arraysEqual([...existing.targets].sort(), [...targets].sort());
|
|
72
|
+
actions.push(specMatches
|
|
73
|
+
? { kind: "skip", name: pkg.name, targets }
|
|
74
|
+
: { kind: "reinstall", name: pkg.name, targets, command: block.command });
|
|
75
|
+
}
|
|
76
|
+
// Lock entries for packages no longer selected at all are orphans too.
|
|
77
|
+
for (const entry of lock.installed) {
|
|
78
|
+
if (seen.has(entry.name))
|
|
79
|
+
continue;
|
|
80
|
+
const uninstall = lookupUninstall(entry.name);
|
|
81
|
+
actions.push({ kind: "orphan", name: entry.name, targets: entry.targets, command: uninstall });
|
|
82
|
+
}
|
|
83
|
+
return actions;
|
|
84
|
+
}
|
|
85
|
+
/** Look up a previously-installed package's declared uninstall argv, if the
|
|
86
|
+
* package is still discoverable (it may have been removed entirely from
|
|
87
|
+
* `packages/external/`, in which case removal stays warn-only). */
|
|
88
|
+
function lookupUninstall(name) {
|
|
89
|
+
try {
|
|
90
|
+
return resolvePackages([name])[0]?.external?.uninstall;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function arraysEqual(a, b) {
|
|
97
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
98
|
+
}
|
|
99
|
+
/** Runs one argv in `cwd`. The real implementation for `executeAction`'s
|
|
100
|
+
* injected runner — uses `execFileSync` (argv form, no shell string) so
|
|
101
|
+
* there is no pipe/shell-injection surface from a pinned command. */
|
|
102
|
+
export function runCommand(command, cwd) {
|
|
103
|
+
if (command.length === 0)
|
|
104
|
+
throw new Error("runCommand: empty argv");
|
|
105
|
+
const [bin, ...args] = command;
|
|
106
|
+
execFileSync(bin, args, { cwd, stdio: "inherit" });
|
|
107
|
+
}
|
|
108
|
+
/** Execute one planned action via the injected runner. Tests pass a stub
|
|
109
|
+
* runner so they never actually shell out. No-op for `skip`, and for
|
|
110
|
+
* `orphan` when the package declared no `uninstall` (Praxis doesn't own
|
|
111
|
+
* those files and never deletes them on the user's behalf). */
|
|
112
|
+
export function executeAction(action, cwd, runner = runCommand) {
|
|
113
|
+
if (action.kind === "skip")
|
|
114
|
+
return;
|
|
115
|
+
if (action.kind === "orphan" && !action.command)
|
|
116
|
+
return; // warn-only; caller reports it
|
|
117
|
+
if (!action.command)
|
|
118
|
+
return;
|
|
119
|
+
runner(action.command, cwd);
|
|
120
|
+
}
|
|
121
|
+
/** Read `praxis-external.lock` from `cwd`, or the empty lock if absent. */
|
|
122
|
+
export function readLock(cwd) {
|
|
123
|
+
const path = join(cwd, LOCK_FILE);
|
|
124
|
+
if (!existsSync(path))
|
|
125
|
+
return EMPTY_LOCK;
|
|
126
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
127
|
+
return lockSchema.parse(raw);
|
|
128
|
+
}
|
|
129
|
+
/** Apply a list of executed actions to a lock, returning the next lock.
|
|
130
|
+
* Pure — the caller decides whether/when to persist it. */
|
|
131
|
+
export function applyActionsToLock(lock, actions) {
|
|
132
|
+
let installed = lock.installed;
|
|
133
|
+
for (const action of actions) {
|
|
134
|
+
if (action.kind === "install" || action.kind === "reinstall") {
|
|
135
|
+
installed = [
|
|
136
|
+
...installed.filter((e) => e.name !== action.name),
|
|
137
|
+
{ name: action.name, spec: action.command ?? [], targets: action.targets },
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
else if (action.kind === "orphan") {
|
|
141
|
+
installed = installed.filter((e) => e.name !== action.name);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { version: 1, installed };
|
|
145
|
+
}
|
|
146
|
+
/** Persist the lock to `praxis-external.lock` at `cwd`, sorted for a stable
|
|
147
|
+
* diff (deterministic, reviewable — no timestamps anywhere in the file). */
|
|
148
|
+
export function writeLock(cwd, lock) {
|
|
149
|
+
const sorted = {
|
|
150
|
+
version: 1,
|
|
151
|
+
installed: [...lock.installed].sort((a, b) => a.name.localeCompare(b.name)),
|
|
152
|
+
};
|
|
153
|
+
writeFileSync(join(cwd, LOCK_FILE), `${JSON.stringify(sorted, null, 2)}\n`, "utf8");
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=external.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external.js","sourceRoot":"","sources":["../src/external.ts"],"names":[],"mappings":"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"}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Manifest, type Stack, type Target } from "./manifest.js";
|
|
2
|
+
import { type SyncReport } from "./sync.js";
|
|
3
|
+
/**
|
|
4
|
+
* `init` — the first-run install (docs/wiki/interaction-model.md, D17). Minimal by design:
|
|
5
|
+
* detect context → propose a default manifest → preview the emit → write
|
|
6
|
+
* `praxis.yaml` and apply. The richer "Customize" wizard is deferred until Layer 2
|
|
7
|
+
* gives real choices (one package, two targets today — nothing to pick yet).
|
|
8
|
+
*
|
|
9
|
+
* This module is pure logic; the interactive prompts live in src/program.ts.
|
|
10
|
+
*/
|
|
11
|
+
export interface InitContext {
|
|
12
|
+
hasGit: boolean;
|
|
13
|
+
hasClaudeMd: boolean;
|
|
14
|
+
hasAgentsMd: boolean;
|
|
15
|
+
/** A `praxis.yaml` already exists — init should defer to `sync`. */
|
|
16
|
+
manifestExists: boolean;
|
|
17
|
+
/** Stacks detected from project files. Empty for a bare/unrecognised repo. */
|
|
18
|
+
detectedStacks: Stack[];
|
|
19
|
+
}
|
|
20
|
+
export declare function detectContext(cwd: string): InitContext;
|
|
21
|
+
/**
|
|
22
|
+
* The default manifest for a fresh repo: all Layer 1 packages + Layer 2 packages
|
|
23
|
+
* for the detected stacks. Data-driven from availablePackages(); no hardcoded list.
|
|
24
|
+
* Emits to both supported targets.
|
|
25
|
+
*/
|
|
26
|
+
export declare function defaultManifest(ctx: InitContext): Manifest;
|
|
27
|
+
/**
|
|
28
|
+
* Build a manifest from explicit selections (Customize branch). Pure — no I/O, no prompts.
|
|
29
|
+
* Omits `stacks` when the array is empty.
|
|
30
|
+
*/
|
|
31
|
+
export declare function manifestFromSelections(stacks: Stack[], packages: string[], targets: Target[]): Manifest;
|
|
32
|
+
/** Render a manifest as a commented `praxis.yaml`. Hand-written (not yaml.stringify)
|
|
33
|
+
* so the file carries guidance for the human who owns it. */
|
|
34
|
+
export declare function renderManifestYaml(manifest: Manifest): string;
|
|
35
|
+
export interface InitPreview {
|
|
36
|
+
ctx: InitContext;
|
|
37
|
+
manifest: Manifest;
|
|
38
|
+
manifestYaml: string;
|
|
39
|
+
/** Dry-run emit report: what files init would create/update. */
|
|
40
|
+
report: SyncReport;
|
|
41
|
+
}
|
|
42
|
+
/** Compute everything init would do, writing nothing. */
|
|
43
|
+
export declare function previewInit(cwd: string): InitPreview;
|
|
44
|
+
/** Write `praxis.yaml` and apply the methodology files. */
|
|
45
|
+
export declare function applyInit(cwd: string, manifest: Manifest): SyncReport;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { METHODOLOGY_VERSION, STACKS } from "./manifest.js";
|
|
6
|
+
import { availablePackages } from "./packages.js";
|
|
7
|
+
import { applyManifest } from "./sync.js";
|
|
8
|
+
const MANIFEST_FILE = "praxis.yaml";
|
|
9
|
+
export function detectContext(cwd) {
|
|
10
|
+
const has = (rel) => existsSync(join(cwd, rel));
|
|
11
|
+
const stacks = [];
|
|
12
|
+
// python-backend: pyproject.toml, requirements.txt, or setup.py
|
|
13
|
+
if (has("pyproject.toml") || has("requirements.txt") || has("setup.py")) {
|
|
14
|
+
stacks.push("python-backend");
|
|
15
|
+
}
|
|
16
|
+
// node: package.json present
|
|
17
|
+
if (has("package.json")) {
|
|
18
|
+
stacks.push("node");
|
|
19
|
+
// react: package.json has "react" in dependencies or devDependencies
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(join(cwd, "package.json"), "utf8");
|
|
22
|
+
const pkg = JSON.parse(raw);
|
|
23
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
24
|
+
if ("react" in deps) {
|
|
25
|
+
stacks.push("react");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// malformed or missing — no node/react crash; node is already pushed above
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Return in canonical STACKS order for determinism
|
|
33
|
+
const ordered = STACKS.filter((s) => stacks.includes(s));
|
|
34
|
+
return {
|
|
35
|
+
hasGit: has(".git"),
|
|
36
|
+
hasClaudeMd: has("CLAUDE.md"),
|
|
37
|
+
hasAgentsMd: has("AGENTS.md"),
|
|
38
|
+
manifestExists: has(MANIFEST_FILE),
|
|
39
|
+
detectedStacks: ordered,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The default manifest for a fresh repo: all Layer 1 packages + Layer 2 packages
|
|
44
|
+
* for the detected stacks. Data-driven from availablePackages(); no hardcoded list.
|
|
45
|
+
* Emits to both supported targets.
|
|
46
|
+
*/
|
|
47
|
+
export function defaultManifest(ctx) {
|
|
48
|
+
const all = availablePackages();
|
|
49
|
+
const detectedSet = new Set(ctx.detectedStacks);
|
|
50
|
+
const layer1 = [];
|
|
51
|
+
const layer2 = [];
|
|
52
|
+
for (const [name, pkg] of all) {
|
|
53
|
+
if (pkg.layer === "layer1") {
|
|
54
|
+
layer1.push(name);
|
|
55
|
+
}
|
|
56
|
+
else if (pkg.layer === "layer2" && pkg.stack && detectedSet.has(pkg.stack)) {
|
|
57
|
+
layer2.push(name);
|
|
58
|
+
}
|
|
59
|
+
// external and decision: excluded from quick-start
|
|
60
|
+
}
|
|
61
|
+
layer1.sort();
|
|
62
|
+
layer2.sort();
|
|
63
|
+
const manifest = {
|
|
64
|
+
version: 1,
|
|
65
|
+
methodology: METHODOLOGY_VERSION,
|
|
66
|
+
targets: ["claude-code", "agents-md"],
|
|
67
|
+
packages: [...layer1, ...layer2],
|
|
68
|
+
};
|
|
69
|
+
if (ctx.detectedStacks.length > 0) {
|
|
70
|
+
manifest.stacks = ctx.detectedStacks;
|
|
71
|
+
}
|
|
72
|
+
return manifest;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a manifest from explicit selections (Customize branch). Pure — no I/O, no prompts.
|
|
76
|
+
* Omits `stacks` when the array is empty.
|
|
77
|
+
*/
|
|
78
|
+
export function manifestFromSelections(stacks, packages, targets) {
|
|
79
|
+
const manifest = {
|
|
80
|
+
version: 1,
|
|
81
|
+
methodology: METHODOLOGY_VERSION,
|
|
82
|
+
targets,
|
|
83
|
+
packages,
|
|
84
|
+
};
|
|
85
|
+
if (stacks.length > 0) {
|
|
86
|
+
manifest.stacks = stacks;
|
|
87
|
+
}
|
|
88
|
+
return manifest;
|
|
89
|
+
}
|
|
90
|
+
/** Render a manifest as a commented `praxis.yaml`. Hand-written (not yaml.stringify)
|
|
91
|
+
* so the file carries guidance for the human who owns it. */
|
|
92
|
+
export function renderManifestYaml(manifest) {
|
|
93
|
+
const lines = [
|
|
94
|
+
"# praxis.yaml — the methodology installed in this repo (the declarative truth).",
|
|
95
|
+
"# Edit this, then run `praxis sync`. `praxis check` reports drift.",
|
|
96
|
+
`version: ${manifest.version}`,
|
|
97
|
+
`methodology: "${manifest.methodology}"`,
|
|
98
|
+
];
|
|
99
|
+
if (manifest.stacks?.length)
|
|
100
|
+
lines.push(`stacks: [${manifest.stacks.join(", ")}]`);
|
|
101
|
+
lines.push(`targets: [${manifest.targets.join(", ")}]`);
|
|
102
|
+
lines.push(`packages: [${manifest.packages.join(", ")}]`);
|
|
103
|
+
return `${lines.join("\n")}\n`;
|
|
104
|
+
}
|
|
105
|
+
/** Compute everything init would do, writing nothing. */
|
|
106
|
+
export function previewInit(cwd) {
|
|
107
|
+
const ctx = detectContext(cwd);
|
|
108
|
+
const manifest = defaultManifest(ctx);
|
|
109
|
+
return {
|
|
110
|
+
ctx,
|
|
111
|
+
manifest,
|
|
112
|
+
manifestYaml: renderManifestYaml(manifest),
|
|
113
|
+
report: applyManifest(manifest, cwd, false),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Write `praxis.yaml` and apply the methodology files. */
|
|
117
|
+
export function applyInit(cwd, manifest) {
|
|
118
|
+
const path = join(cwd, MANIFEST_FILE);
|
|
119
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
120
|
+
writeFileSync(path, renderManifestYaml(manifest), "utf8");
|
|
121
|
+
return applyManifest(manifest, cwd, true);
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=init.js.map
|
package/dist/init.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The manifest — `praxis.yaml` (docs/wiki/interaction-model.md, decision D4). The visible,
|
|
4
|
+
* committed, hand-editable declarative truth: what methodology a repo has
|
|
5
|
+
* installed. `sync` reconciles the repo to it; this module reads and validates it.
|
|
6
|
+
*
|
|
7
|
+
* Enums are restricted to what Praxis can actually honor today. Accepting config
|
|
8
|
+
* we can't act on (an unimplemented target/stack) would be a silent lie — better
|
|
9
|
+
* a clear validation error. The lists grow as emitters and stacks land.
|
|
10
|
+
*/
|
|
11
|
+
export declare const METHODOLOGY_VERSION = "0.1.0";
|
|
12
|
+
declare const TARGETS: readonly ["claude-code", "agents-md"];
|
|
13
|
+
export declare const STACKS: readonly ["python-backend", "node", "react"];
|
|
14
|
+
export declare const manifestSchema: z.ZodObject<{
|
|
15
|
+
version: z.ZodLiteral<1>;
|
|
16
|
+
methodology: z.ZodString;
|
|
17
|
+
stacks: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
18
|
+
"python-backend": "python-backend";
|
|
19
|
+
node: "node";
|
|
20
|
+
react: "react";
|
|
21
|
+
}>>>;
|
|
22
|
+
targets: z.ZodArray<z.ZodEnum<{
|
|
23
|
+
"claude-code": "claude-code";
|
|
24
|
+
"agents-md": "agents-md";
|
|
25
|
+
}>>;
|
|
26
|
+
packages: z.ZodArray<z.ZodString>;
|
|
27
|
+
}, z.core.$strict>;
|
|
28
|
+
export type Manifest = z.infer<typeof manifestSchema>;
|
|
29
|
+
export type Target = (typeof TARGETS)[number];
|
|
30
|
+
export type Stack = (typeof STACKS)[number];
|
|
31
|
+
/** Parse + validate manifest text. Throws an Error with a readable, multi-line
|
|
32
|
+
* message naming each problem and where it is — written to be acted on. */
|
|
33
|
+
export declare function parseManifest(yamlText: string): Manifest;
|
|
34
|
+
/** Read and validate the manifest at `path`. */
|
|
35
|
+
export declare function loadManifest(path: string): Manifest;
|
|
36
|
+
export {};
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { parse as parseYaml } from "yaml";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* The manifest — `praxis.yaml` (docs/wiki/interaction-model.md, decision D4). The visible,
|
|
6
|
+
* committed, hand-editable declarative truth: what methodology a repo has
|
|
7
|
+
* installed. `sync` reconciles the repo to it; this module reads and validates it.
|
|
8
|
+
*
|
|
9
|
+
* Enums are restricted to what Praxis can actually honor today. Accepting config
|
|
10
|
+
* we can't act on (an unimplemented target/stack) would be a silent lie — better
|
|
11
|
+
* a clear validation error. The lists grow as emitters and stacks land.
|
|
12
|
+
*/
|
|
13
|
+
// The version of methodology content Praxis currently ships. `init` stamps new
|
|
14
|
+
// manifests with it; `sync` offers to bump a repo to it (D6).
|
|
15
|
+
export const METHODOLOGY_VERSION = "0.1.0";
|
|
16
|
+
// Emit targets with an implemented emitter. Grows as emitters are added.
|
|
17
|
+
const TARGETS = ["claude-code", "agents-md"];
|
|
18
|
+
// Stacks Praxis knows. Grows as Layer 2 stacks are added.
|
|
19
|
+
export const STACKS = ["python-backend", "node", "react"];
|
|
20
|
+
export const manifestSchema = z.strictObject({
|
|
21
|
+
version: z.literal(1),
|
|
22
|
+
methodology: z.string().min(1),
|
|
23
|
+
// Optional: Layer 1 is stack-agnostic, so a repo installing only general
|
|
24
|
+
// methodology (like Praxis itself) declares no stacks. Present only when Layer 2
|
|
25
|
+
// recipes are wanted — and a repo can be more than one stack at once (e.g. a
|
|
26
|
+
// React frontend with a Node backend), so it is a list.
|
|
27
|
+
stacks: z.array(z.enum(STACKS)).optional(),
|
|
28
|
+
targets: z.array(z.enum(TARGETS)).min(1),
|
|
29
|
+
// Package names are free strings until a package registry exists to validate
|
|
30
|
+
// them against; the loader (a later phase) resolves requires/conflicts.
|
|
31
|
+
packages: z.array(z.string().min(1)).min(1),
|
|
32
|
+
});
|
|
33
|
+
/** Parse + validate manifest text. Throws an Error with a readable, multi-line
|
|
34
|
+
* message naming each problem and where it is — written to be acted on. */
|
|
35
|
+
export function parseManifest(yamlText) {
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
raw = parseYaml(yamlText);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
throw new Error(`praxis.yaml is not valid YAML: ${err.message}`);
|
|
42
|
+
}
|
|
43
|
+
const result = manifestSchema.safeParse(raw);
|
|
44
|
+
if (!result.success) {
|
|
45
|
+
const issues = result.error.issues
|
|
46
|
+
.map((i) => ` - ${i.path.map(String).join(".") || "(root)"}: ${i.message}`)
|
|
47
|
+
.join("\n");
|
|
48
|
+
throw new Error(`praxis.yaml is invalid:\n${issues}`);
|
|
49
|
+
}
|
|
50
|
+
return result.data;
|
|
51
|
+
}
|
|
52
|
+
/** Read and validate the manifest at `path`. */
|
|
53
|
+
export function loadManifest(path) {
|
|
54
|
+
let text;
|
|
55
|
+
try {
|
|
56
|
+
text = readFileSync(path, "utf8");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new Error(`No praxis.yaml found at ${path}. Run \`praxis\` to create one.`);
|
|
60
|
+
}
|
|
61
|
+
return parseManifest(text);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AAEH,+EAA+E;AAC/E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAE3C,yEAAyE;AACzE,MAAM,OAAO,GAAG,CAAC,aAAa,EAAE,WAAW,CAAU,CAAC;AAEtD,0DAA0D;AAC1D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AAEnE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,YAAY,CAAC;IAC3C,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,yEAAyE;IACzE,iFAAiF;IACjF,6EAA6E;IAC7E,wDAAwD;IACxD,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC1C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,6EAA6E;IAC7E,wEAAwE;IACxE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAC5C,CAAC,CAAC;AAMH;4EAC4E;AAC5E,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kCAAmC,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3E,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,2BAA2B,IAAI,iCAAiC,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-aware managed merge for `.claude/settings.json` (docs/wiki/merge-engine.md,
|
|
3
|
+
* decisions D10/D11/D18/D19). The markdown engine in `src/merge.ts` owns spans of *text*
|
|
4
|
+
* delimited by HTML-comment markers; that trick has **no JSON equivalent** — JSON
|
|
5
|
+
* has no comments. So this module is the structured-config analogue: Praxis owns
|
|
6
|
+
* only the **individual entries** it emits — permission rule strings, marketplace
|
|
7
|
+
* declarations, enabled-plugin keys — deep-merged into the host file, and records
|
|
8
|
+
* exactly which entries are its own in a self-contained top-level `_praxis` key
|
|
9
|
+
* (the JSON-native marker; D11 — no separate lockfile). One reconcile pass owns
|
|
10
|
+
* the whole file so two concerns (permissions, plugins) never compose into two
|
|
11
|
+
* separate writes of the same path (the same-file compose bug).
|
|
12
|
+
*
|
|
13
|
+
* Invariants (mirrors the markdown engine, verified by conformance):
|
|
14
|
+
* - User-added entries in the same structures survive untouched (per-entry
|
|
15
|
+
* ownership).
|
|
16
|
+
* - Entries Praxis previously emitted but no longer ships are removed on update.
|
|
17
|
+
* - If the user edited/removed one of Praxis's managed entries, that group is a
|
|
18
|
+
* **conflict, never a clobber** (D10): left exactly as-is, surfaced.
|
|
19
|
+
* - Idempotent: re-merging identical input is a byte-for-byte no-op.
|
|
20
|
+
* - Every other key in the file (model, env, defaultMode, …) is preserved.
|
|
21
|
+
*
|
|
22
|
+
* The `_praxis` key is a valid top-level addition: the Claude Code settings schema
|
|
23
|
+
* sets top-level `additionalProperties: true` (verified against
|
|
24
|
+
* json.schemastore.org/claude-code-settings.json), while `permissions` itself is
|
|
25
|
+
* `additionalProperties: false` — hence the marker lives as a top-level sibling of
|
|
26
|
+
* `permissions`/`extraKnownMarketplaces`/`enabledPlugins`, never inside them.
|
|
27
|
+
*/
|
|
28
|
+
/** The three permission buckets Praxis manages, in stable emit order. */
|
|
29
|
+
export declare const BUCKETS: readonly ["allow", "ask", "deny"];
|
|
30
|
+
export type Bucket = (typeof BUCKETS)[number];
|
|
31
|
+
/** A set of concrete, already-tool-translated rule strings per bucket. */
|
|
32
|
+
export type RuleSet = Record<Bucket, string[]>;
|
|
33
|
+
/** A Claude Code plugin marketplace source (today: github repo, optionally
|
|
34
|
+
* pinned by ref/sha — see src/plugins.ts for the validated package shape). */
|
|
35
|
+
export interface MarketplaceSource {
|
|
36
|
+
source: "github";
|
|
37
|
+
repo: string;
|
|
38
|
+
ref?: string;
|
|
39
|
+
sha?: string;
|
|
40
|
+
}
|
|
41
|
+
/** One marketplace declaration, keyed by its name. */
|
|
42
|
+
export interface MarketplaceEntry {
|
|
43
|
+
name: string;
|
|
44
|
+
source: MarketplaceSource;
|
|
45
|
+
}
|
|
46
|
+
/** The desired plugin-marketplace state: marketplaces to declare and
|
|
47
|
+
* "<plugin>@<marketplace>" keys to enable. */
|
|
48
|
+
export interface PluginsDesired {
|
|
49
|
+
marketplaces: MarketplaceEntry[];
|
|
50
|
+
enable: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface JsonReconcileResult {
|
|
53
|
+
/** The resulting file text (pretty-printed JSON, trailing newline). */
|
|
54
|
+
text: string;
|
|
55
|
+
/** Whether any byte changed. */
|
|
56
|
+
changed: boolean;
|
|
57
|
+
/** Group ids (`permissions.allow`, `plugins.marketplaces`, `plugins.enable`)
|
|
58
|
+
* the user edited; left untouched. */
|
|
59
|
+
conflicts: string[];
|
|
60
|
+
}
|
|
61
|
+
/** Settings this reconcile pass should bring the host file in line with. Either
|
|
62
|
+
* may be omitted — a sync that emits only permissions (or only plugins) leaves
|
|
63
|
+
* the other concern's previously-managed entries alone, neither adding nor
|
|
64
|
+
* removing them, since "omitted" here means "this op didn't run," not "empty." */
|
|
65
|
+
export interface DesiredSettings {
|
|
66
|
+
permissions?: RuleSet;
|
|
67
|
+
plugins?: PluginsDesired;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Bring `.claude/settings.json` in line with the desired permissions and/or
|
|
71
|
+
* plugin-marketplace state in a single pass — the only function that may write
|
|
72
|
+
* this file, so permissions and plugins compose into one write instead of two
|
|
73
|
+
* operations racing on the same path. Splices only the entries Praxis owns;
|
|
74
|
+
* preserves user entries and every unrelated key; refuses to overwrite a group
|
|
75
|
+
* the user has edited (conflict). Pure; no I/O.
|
|
76
|
+
*
|
|
77
|
+
* @param existing the destination file's current text ("" if it does not exist)
|
|
78
|
+
* @param desired the permissions and/or plugins state Praxis wants to own
|
|
79
|
+
*/
|
|
80
|
+
export declare function reconcileSettings(existing: string, desired: DesiredSettings): JsonReconcileResult;
|
|
81
|
+
/** Permissions-only convenience wrapper over `reconcileSettings` — kept because
|
|
82
|
+
* most call sites and tests only ever reconcile permissions. */
|
|
83
|
+
export declare function reconcilePermissions(existing: string, desired: RuleSet): JsonReconcileResult;
|