projectstore-claude 0.30.0 → 0.30.1
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/bin/projectstore-claude.mjs +1 -1
- package/node_modules/projectstore/.claude-plugin/marketplace.json +1 -1
- package/node_modules/projectstore/.claude-plugin/plugin.json +1 -1
- package/node_modules/projectstore/README.md +92 -0
- package/node_modules/projectstore/commands/bind.md +5 -5
- package/node_modules/projectstore/commands/statusline.md +8 -8
- package/node_modules/projectstore/docs/getting-started.md +33 -11
- package/node_modules/projectstore/docs/harnesses.md +6 -0
- package/node_modules/projectstore/harnesses/claude-code.json +24 -3
- package/node_modules/projectstore/package.json +1 -1
- package/node_modules/projectstore/scripts/agents-change.mjs +84 -0
- package/node_modules/projectstore/scripts/binding.mjs +40 -0
- package/node_modules/projectstore/scripts/cli.mjs +86 -85
- package/node_modules/projectstore/scripts/configure.mjs +908 -0
- package/node_modules/projectstore/scripts/harness.mjs +8 -0
- package/node_modules/projectstore/scripts/install-harness.mjs +29 -11
- package/node_modules/projectstore/scripts/lib.mjs +41 -5
- package/package.json +2 -2
|
@@ -59,7 +59,6 @@ import {
|
|
|
59
59
|
projectRootDeclared,
|
|
60
60
|
childEnv,
|
|
61
61
|
harnessIds,
|
|
62
|
-
harnessForOverlay,
|
|
63
62
|
pinPluginRoot,
|
|
64
63
|
invokedAs,
|
|
65
64
|
shellHarnessId,
|
|
@@ -72,7 +71,6 @@ import {
|
|
|
72
71
|
readConfigAt,
|
|
73
72
|
readOverlayAt,
|
|
74
73
|
resolveAgentModel,
|
|
75
|
-
writeOverlayAt,
|
|
76
74
|
overlayId,
|
|
77
75
|
layoutRoster,
|
|
78
76
|
commandForm,
|
|
@@ -105,6 +103,7 @@ import {
|
|
|
105
103
|
DEFAULT_LANGUAGE,
|
|
106
104
|
} from "./binding.mjs";
|
|
107
105
|
import { planScaffold, applyScaffold, scaffoldResult, renderScaffoldPlan } from "./scaffold.mjs";
|
|
106
|
+
import { planAgentsChange, applyAgentsChange } from "./agents-change.mjs";
|
|
108
107
|
import {
|
|
109
108
|
planBand,
|
|
110
109
|
applyBand,
|
|
@@ -201,6 +200,14 @@ const UNINSTALL_OPTS = [
|
|
|
201
200
|
JSON_OPT,
|
|
202
201
|
];
|
|
203
202
|
|
|
203
|
+
// configure's own --harness: it names the overlay agents.* keys write, and
|
|
204
|
+
// is no confirmation — naming a change is (its spec, contracts 1 and 8).
|
|
205
|
+
const CONFIGURE_OPTS = [
|
|
206
|
+
opt("set", "<key>=<value>", "change one setting; without a terminal, naming it is the confirmation", true),
|
|
207
|
+
opt("harness", "<id>", "the overlay agents.* keys write; other keys ignore it", true),
|
|
208
|
+
opt("dry-run", false, "the plan; nothing is written"),
|
|
209
|
+
];
|
|
210
|
+
|
|
204
211
|
// setup's own options (the setup story, rule 1): seven, beside the globals.
|
|
205
212
|
// `--vault <path>` takes a value here where doctor's `--vault` is a flag —
|
|
206
213
|
// the per-verb parse's own case.
|
|
@@ -237,6 +244,20 @@ export const VERBS = Object.freeze([
|
|
|
237
244
|
options: [...SETUP_OPTS, JSON_OPT],
|
|
238
245
|
run: runSetupVerb,
|
|
239
246
|
}),
|
|
247
|
+
Object.freeze({
|
|
248
|
+
verb: "configure",
|
|
249
|
+
summary: "Change this project's settings after setup, behind one plan.",
|
|
250
|
+
usage: NO_FORMS,
|
|
251
|
+
module: "./configure.mjs",
|
|
252
|
+
wraps: "new",
|
|
253
|
+
how: "import",
|
|
254
|
+
output: "envelope",
|
|
255
|
+
writes: true,
|
|
256
|
+
requiresBinding: false,
|
|
257
|
+
mcp: Object.freeze([]),
|
|
258
|
+
options: [...CONFIGURE_OPTS, JSON_OPT],
|
|
259
|
+
run: runConfigureVerb,
|
|
260
|
+
}),
|
|
240
261
|
Object.freeze({
|
|
241
262
|
verb: "doctor",
|
|
242
263
|
summary: "Check the install wiring and the vault's consistency.",
|
|
@@ -599,7 +620,7 @@ export const PLANNED_VERBS = Object.freeze([]);
|
|
|
599
620
|
// setup, START is setup and uninstall, and install and bind are ADVANCED's
|
|
600
621
|
// (contract 10; the setup story, rule 1).
|
|
601
622
|
const HELP_GROUPS = [
|
|
602
|
-
["START", ["setup", "uninstall"]],
|
|
623
|
+
["START", ["setup", "configure", "uninstall"]],
|
|
603
624
|
["VAULT", ["status", "search", "show", "graph", "codemap"]],
|
|
604
625
|
["CHECK AND REPAIR", ["doctor", "reconcile", "upgrade"]],
|
|
605
626
|
];
|
|
@@ -634,6 +655,12 @@ const EXAMPLES = {
|
|
|
634
655
|
"{cmd} setup{fixed} --dry-run # the questions and the plan; nothing is written or fetched",
|
|
635
656
|
"{cmd} setup{h} --vault ~/vaults/my-project # without a terminal: --harness confirms, --vault names the vault",
|
|
636
657
|
],
|
|
658
|
+
configure: [
|
|
659
|
+
"{cmd} configure # at a terminal: the settings, then what to change",
|
|
660
|
+
"{cmd} configure --set statusline=on",
|
|
661
|
+
"{cmd} configure --set agents.critic=fable{h} # an agent's model, in that harness's overlay",
|
|
662
|
+
"{cmd} configure --json # the settings, for scripts and agents",
|
|
663
|
+
],
|
|
637
664
|
install: ["{cmd} install{h}", "{cmd} plan{h} # the same plan; nothing is written"],
|
|
638
665
|
upgrade: [
|
|
639
666
|
"{cmd}@<version> upgrade{h} # the version you name is the version that runs",
|
|
@@ -792,7 +819,7 @@ export function usage(env = process.env, verbs = VERBS, { caps: c = term.PLAIN,
|
|
|
792
819
|
lines.push(
|
|
793
820
|
"",
|
|
794
821
|
term.heading(c, "Tips"),
|
|
795
|
-
prose("setup, install, upgrade and uninstall show a plan, then ask
|
|
822
|
+
prose("setup, configure, install, upgrade and uninstall show a plan, then ask."),
|
|
796
823
|
prose(
|
|
797
824
|
`Without a terminal, ${shell ? "the verb is its own" : "--harness is the"} confirmation ${term.icon(c, "dash")} there is no --yes.`,
|
|
798
825
|
),
|
|
@@ -829,7 +856,9 @@ export function verbHelp(row, env = process.env, { caps: c = term.PLAIN } = {})
|
|
|
829
856
|
lines.push(
|
|
830
857
|
"",
|
|
831
858
|
term.wrap(
|
|
832
|
-
|
|
859
|
+
row.verb === "configure"
|
|
860
|
+
? `${shell} names the harness itself: its overlay is the one agents.* keys write.`
|
|
861
|
+
: `${shell} names the harness itself: without a terminal, the verb is its own confirmation.`,
|
|
833
862
|
c.live ? c.width : 0,
|
|
834
863
|
" ",
|
|
835
864
|
),
|
|
@@ -1165,20 +1194,7 @@ export async function run(
|
|
|
1165
1194
|
}
|
|
1166
1195
|
const project = resolveProject({ project: values.project, env, cwd });
|
|
1167
1196
|
const cfg = readConfigAt(project);
|
|
1168
|
-
|
|
1169
|
-
// form, the directory in the lead (the story "User-facing text says where to
|
|
1170
|
-
// run and names no project path and no shell", rules 1, 4 and 6).
|
|
1171
|
-
if (row.requiresBinding && !cfg)
|
|
1172
|
-
return fail(
|
|
1173
|
-
[
|
|
1174
|
-
`${project} is not bound to a vault. Bind it in a session, or ${fromTerminalIn(project)}, where init creates the vault too:`,
|
|
1175
|
-
commandForm("bind", { args: "<vault>", env }),
|
|
1176
|
-
packageCommand([], "bind", { args: "<vault>" }),
|
|
1177
|
-
packageCommand([], "init", { args: "<vault>" }),
|
|
1178
|
-
].join("\n"),
|
|
1179
|
-
3,
|
|
1180
|
-
{ project },
|
|
1181
|
-
);
|
|
1197
|
+
if (row.requiresBinding && !cfg) return fail(notBoundText(project, env), 3, { project });
|
|
1182
1198
|
try {
|
|
1183
1199
|
return await row.run({
|
|
1184
1200
|
row,
|
|
@@ -1486,70 +1502,17 @@ async function runAgents(ctx) {
|
|
|
1486
1502
|
);
|
|
1487
1503
|
if (!values.reset && values.default === undefined && !(values.agent || []).length)
|
|
1488
1504
|
return usage("agents configure takes --default <model>, --agent <name>=<model> (repeatable) or --reset");
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
// A name outside the roster would be written, listed, and never run.
|
|
1501
|
-
if (roster && !roster.includes(m[1]))
|
|
1502
|
-
return usage(`no agent named ${m[1]} in the ${cfg.layout} roster — known: ${roster.join(", ")}`);
|
|
1503
|
-
if (m[2]) next.per_agent[m[1]] = m[2];
|
|
1504
|
-
else delete next.per_agent[m[1]];
|
|
1505
|
-
}
|
|
1506
|
-
// The clerk transcribes; a strong default must not lift it (commands/agents.md,
|
|
1507
|
-
// ADR-008). WHICH model it is pinned to is the harness's vocabulary, so the
|
|
1508
|
-
// manifest names it and this file does not: pinning every harness to
|
|
1509
|
-
// "sonnet" wrote an Anthropic model name into a Codex overlay, which is a
|
|
1510
|
-
// model that does not exist there. A manifest with no cheap model declared
|
|
1511
|
-
// does not pin, and the preview says so rather than inventing one.
|
|
1512
|
-
// No fallback model here, deliberately, and it is not the same call as
|
|
1513
|
-
// SOURCE_WRITE_TOOLS_FALLBACK: a missing manifest cannot reach this line.
|
|
1514
|
-
// harnessIds() derives from the manifests, so with none loadable `--harness`
|
|
1515
|
-
// is refused as unknown and `overlayId(env)` is null, and configure has
|
|
1516
|
-
// already returned "no harness detected and none named". A literal here would
|
|
1517
|
-
// be the deleted `"sonnet"` restored for a case that cannot happen.
|
|
1518
|
-
let pinnedClerk = false;
|
|
1519
|
-
let clerkPinUnavailable = false;
|
|
1520
|
-
const clerkAsked = (values.agent || []).some((s) => s.startsWith("clerk="));
|
|
1521
|
-
if (next.default && !next.per_agent.clerk && !clerkAsked) {
|
|
1522
|
-
const cheap = harnessForOverlay(harness)?.agent_translation?.cheap_model || null;
|
|
1523
|
-
if (cheap) {
|
|
1524
|
-
next.per_agent.clerk = cheap;
|
|
1525
|
-
pinnedClerk = true;
|
|
1526
|
-
} else clerkPinUnavailable = true;
|
|
1527
|
-
}
|
|
1528
|
-
// Keys inside the agents block that the allowlist rejected (an `effort`, a
|
|
1529
|
-
// stray shape) are rewritten out by the write and announced here; keys
|
|
1530
|
-
// outside the block are kept, and stay doctor's to name.
|
|
1531
|
-
const dropped = before.rejected.filter((k) => k.startsWith("agents."));
|
|
1532
|
-
const same =
|
|
1533
|
-
!dropped.length &&
|
|
1534
|
-
JSON.stringify({ d: before.agents.default, p: before.agents.per_agent }) ===
|
|
1535
|
-
JSON.stringify({ d: next.default, p: next.per_agent });
|
|
1536
|
-
const preview = [
|
|
1537
|
-
`agents configure — ${harness} — ${before.path}`,
|
|
1538
|
-
` default: ${before.agents.default || "—"} → ${next.default || "—"}`,
|
|
1539
|
-
];
|
|
1540
|
-
const names = new Set([...Object.keys(before.agents.per_agent), ...Object.keys(next.per_agent)]);
|
|
1541
|
-
for (const n of [...names].sort())
|
|
1542
|
-
preview.push(
|
|
1543
|
-
` ${n}: ${before.agents.per_agent[n] || "—"} → ${next.per_agent[n] || "—"}${pinnedClerk && n === "clerk" ? " (pinned: the clerk stays cheap under a strong default)" : ""}`,
|
|
1544
|
-
);
|
|
1545
|
-
// Silence here would read as "the clerk is fine", and it is not: it takes the
|
|
1546
|
-
// strong default, which is the one thing ADR-008 says must not happen to it.
|
|
1547
|
-
if (clerkPinUnavailable)
|
|
1548
|
-
preview.push(
|
|
1549
|
-
` clerk: takes the default — ${harness} declares no cheap model to pin it to, so name one with --agent clerk=<model>`,
|
|
1550
|
-
);
|
|
1551
|
-
if (dropped.length)
|
|
1552
|
-
preview.push(` drops from the agents block (an overlay carries only models): ${dropped.join(", ")}`);
|
|
1505
|
+
const change = planAgentsChange({
|
|
1506
|
+
before,
|
|
1507
|
+
harness,
|
|
1508
|
+
layout: cfg && cfg.layout,
|
|
1509
|
+
roster,
|
|
1510
|
+
defaultModel: values.default,
|
|
1511
|
+
agents: values.agent || [],
|
|
1512
|
+
reset: Boolean(values.reset),
|
|
1513
|
+
});
|
|
1514
|
+
if (change.usage) return usage(change.usage);
|
|
1515
|
+
const { next, pinnedClerk, clerkPinUnavailable, dropped, same, preview } = change;
|
|
1553
1516
|
if (same)
|
|
1554
1517
|
return emit(
|
|
1555
1518
|
"agents configure",
|
|
@@ -1572,8 +1535,7 @@ async function runAgents(ctx) {
|
|
|
1572
1535
|
{ harness, path: before.path, wrote: false, declined: true, agents: next, dropped },
|
|
1573
1536
|
preview.join("\n") + "\n nothing written.\n",
|
|
1574
1537
|
);
|
|
1575
|
-
const path =
|
|
1576
|
-
const after = readOverlayAt(project, harness);
|
|
1538
|
+
const { path, after } = applyAgentsChange(project, harness, next);
|
|
1577
1539
|
return emit(
|
|
1578
1540
|
"agents configure",
|
|
1579
1541
|
true,
|
|
@@ -1851,6 +1813,45 @@ async function runReconcile({ values, project, env, stdin, stdout, stderr, ask }
|
|
|
1851
1813
|
return failed ? 1 : 0;
|
|
1852
1814
|
}
|
|
1853
1815
|
|
|
1816
|
+
// The not-bound refusal, for every verb that needs a binding and for configure.
|
|
1817
|
+
// Each form alone on its line after the lead, the terminal's in the core's
|
|
1818
|
+
// form, the directory in the lead (the story "User-facing text says where to
|
|
1819
|
+
// run and names no project path and no shell", rules 1, 4 and 6).
|
|
1820
|
+
export function notBoundText(project, env = process.env) {
|
|
1821
|
+
return [
|
|
1822
|
+
`${project} is not bound to a vault. Bind it in a session, or ${fromTerminalIn(project)}, where init creates the vault too:`,
|
|
1823
|
+
commandForm("bind", { args: "<vault>", env }),
|
|
1824
|
+
packageCommand([], "bind", { args: "<vault>" }),
|
|
1825
|
+
packageCommand([], "init", { args: "<vault>" }),
|
|
1826
|
+
].join("\n");
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// configure (its spec): loaded on first use, like setup. The module returns a
|
|
1830
|
+
// usage error, an unbound project, a refusal's message, or a result; the bin
|
|
1831
|
+
// writes the line, the envelope and the exit code.
|
|
1832
|
+
async function runConfigureVerb(ctx) {
|
|
1833
|
+
const { runConfigure } = await import("./configure.mjs");
|
|
1834
|
+
const { values, env, stdout, stderr, project } = ctx;
|
|
1835
|
+
const r = await runConfigure({
|
|
1836
|
+
...ctx,
|
|
1837
|
+
root: PACKAGE_ROOT,
|
|
1838
|
+
ownEnv: (p) => ownEnv(env, p),
|
|
1839
|
+
nearest,
|
|
1840
|
+
});
|
|
1841
|
+
if (r.usage) return usageFail(Object.assign(new Error(r.usage), { code: "USAGE" }), { verb: "configure", ...ctx });
|
|
1842
|
+
if (r.unbound) {
|
|
1843
|
+
const text = notBoundText(project, env);
|
|
1844
|
+
if (values.json)
|
|
1845
|
+
stdout.write(JSON.stringify(envelope("configure", project, false, { error: text, exit: 3 }), null, 2) + "\n");
|
|
1846
|
+
else stderr.write(text + "\n");
|
|
1847
|
+
return 3;
|
|
1848
|
+
}
|
|
1849
|
+
if (r.message && !values.json) stderr.write(r.message + "\n");
|
|
1850
|
+
if (values.json && r.result)
|
|
1851
|
+
stdout.write(JSON.stringify(envelope("configure", project, r.ok, r.result), null, 2) + "\n");
|
|
1852
|
+
return r.code;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1854
1855
|
// setup (the setup story): its own module, imported lazily as the install
|
|
1855
1856
|
// family is, run with this package as its root and in the environment every
|
|
1856
1857
|
// installer run gets (ownEnv, for the project setup settles on). The envelope
|