@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.
Files changed (72) hide show
  1. package/dist/anchors.d.ts +36 -0
  2. package/dist/anchors.js +197 -0
  3. package/dist/anchors.js.map +1 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +4 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/emit.d.ts +51 -0
  8. package/dist/emit.js +253 -0
  9. package/dist/emit.js.map +1 -0
  10. package/dist/external.d.ts +61 -0
  11. package/dist/external.js +155 -0
  12. package/dist/external.js.map +1 -0
  13. package/dist/init.d.ts +45 -0
  14. package/dist/init.js +123 -0
  15. package/dist/init.js.map +1 -0
  16. package/dist/manifest.d.ts +36 -0
  17. package/dist/manifest.js +63 -0
  18. package/dist/manifest.js.map +1 -0
  19. package/dist/merge-json.d.ts +83 -0
  20. package/dist/merge-json.js +207 -0
  21. package/dist/merge-json.js.map +1 -0
  22. package/dist/merge.d.ts +53 -0
  23. package/dist/merge.js +89 -0
  24. package/dist/merge.js.map +1 -0
  25. package/dist/packages.d.ts +47 -0
  26. package/dist/packages.js +121 -0
  27. package/dist/packages.js.map +1 -0
  28. package/dist/permissions.d.ts +54 -0
  29. package/dist/permissions.js +83 -0
  30. package/dist/permissions.js.map +1 -0
  31. package/dist/plugins.d.ts +21 -0
  32. package/dist/plugins.js +88 -0
  33. package/dist/plugins.js.map +1 -0
  34. package/dist/program.d.ts +2 -0
  35. package/dist/program.js +201 -0
  36. package/dist/program.js.map +1 -0
  37. package/dist/sync.d.ts +37 -0
  38. package/dist/sync.js +45 -0
  39. package/dist/sync.js.map +1 -0
  40. package/package.json +40 -0
  41. package/packages/external/drawio/package.yaml +7 -0
  42. package/packages/external/drawio/plugins.yaml +13 -0
  43. package/packages/external/ponytail/package.yaml +7 -0
  44. package/packages/external/ponytail/plugins.yaml +13 -0
  45. package/packages/layer1/instruction-upkeep/commands/audit.md +41 -0
  46. package/packages/layer1/instruction-upkeep/package.yaml +8 -0
  47. package/packages/layer1/instruction-upkeep/rules.md +21 -0
  48. package/packages/layer1/karpathy-claude/package.yaml +5 -0
  49. package/packages/layer1/karpathy-claude/rules.md +35 -0
  50. package/packages/layer1/safe-permissions/package.yaml +5 -0
  51. package/packages/layer1/safe-permissions/permissions.yaml +26 -0
  52. package/packages/layer1/session-handoff/commands/handoff.md +185 -0
  53. package/packages/layer1/session-handoff/package.yaml +10 -0
  54. package/packages/layer1/session-handoff/rules.md +20 -0
  55. package/packages/layer1/upkeep/commands/upkeep.md +31 -0
  56. package/packages/layer1/upkeep/package.yaml +10 -0
  57. package/packages/layer1/upkeep/rules.md +12 -0
  58. package/packages/layer1/wiki-memory/commands/wiki.md +103 -0
  59. package/packages/layer1/wiki-memory/package.yaml +10 -0
  60. package/packages/layer1/wiki-memory/rules.md +49 -0
  61. package/packages/layer2/node-recipes/package.yaml +10 -0
  62. package/packages/layer2/node-recipes/rules.md +33 -0
  63. package/packages/layer2/node-testing/package.yaml +10 -0
  64. package/packages/layer2/node-testing/rules.md +33 -0
  65. package/packages/layer2/python-backend-recipes/package.yaml +10 -0
  66. package/packages/layer2/python-backend-recipes/rules.md +37 -0
  67. package/packages/layer2/python-testing/package.yaml +10 -0
  68. package/packages/layer2/python-testing/rules.md +33 -0
  69. package/packages/layer2/react-components/package.yaml +10 -0
  70. package/packages/layer2/react-components/rules.md +40 -0
  71. package/packages/layer2/react-testing/package.yaml +10 -0
  72. package/packages/layer2/react-testing/rules.md +35 -0
@@ -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
@@ -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 {};
@@ -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;