projectstore-claude 0.30.2 → 0.30.4
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/node_modules/projectstore/.claude-plugin/marketplace.json +1 -1
- package/node_modules/projectstore/.claude-plugin/plugin.json +1 -1
- package/node_modules/projectstore/AGENTS.md +7 -5
- package/node_modules/projectstore/README.md +3 -3
- package/node_modules/projectstore/agents/clerk.md +2 -2
- package/node_modules/projectstore/commands/agents.md +43 -28
- package/node_modules/projectstore/commands/bind.md +1 -1
- package/node_modules/projectstore/commands/doctor.md +19 -3
- package/node_modules/projectstore/commands/reconcile.md +2 -2
- package/node_modules/projectstore/commands/review.md +1 -1
- package/node_modules/projectstore/commands/story.md +2 -1
- package/node_modules/projectstore/docs/getting-started.md +1 -1
- package/node_modules/projectstore/docs/harnesses.md +3 -2
- package/node_modules/projectstore/harnesses/claude-code.json +40 -1
- package/node_modules/projectstore/harnesses/codex.json +50 -14
- package/node_modules/projectstore/package.json +1 -1
- package/node_modules/projectstore/scripts/agents-change.mjs +64 -10
- package/node_modules/projectstore/scripts/build-adapters.mjs +28 -13
- package/node_modules/projectstore/scripts/cli.mjs +32 -9
- package/node_modules/projectstore/scripts/configure-menu.mjs +133 -6
- package/node_modules/projectstore/scripts/configure.mjs +156 -19
- package/node_modules/projectstore/scripts/convergence.mjs +56 -1
- package/node_modules/projectstore/scripts/doctor.mjs +134 -35
- package/node_modules/projectstore/scripts/harness.mjs +14 -9
- package/node_modules/projectstore/scripts/install-harness.mjs +182 -14
- package/node_modules/projectstore/scripts/lib.mjs +83 -24
- package/node_modules/projectstore/scripts/menu.mjs +9 -5
- package/node_modules/projectstore/scripts/models.mjs +150 -6
- package/node_modules/projectstore/scripts/setup.mjs +7 -5
- package/node_modules/projectstore/scripts/surfaces.mjs +36 -9
- package/node_modules/projectstore/templates/claude-md-block.md.tmpl +7 -5
- package/package.json +2 -2
|
@@ -56,10 +56,13 @@ import {
|
|
|
56
56
|
AGENTS_BLOCK_OPEN_SRC,
|
|
57
57
|
AGENTS_BLOCK_CLOSE,
|
|
58
58
|
agentsBlockVersion,
|
|
59
|
+
newerBlockWords,
|
|
60
|
+
isNewerBlock,
|
|
59
61
|
findAgentsBlock,
|
|
60
62
|
statusLineLauncherPath,
|
|
61
63
|
LAUNCHER_HEADER,
|
|
62
64
|
installedPluginEntries,
|
|
65
|
+
projectCopies,
|
|
63
66
|
selectCopy,
|
|
64
67
|
takesNoRegister,
|
|
65
68
|
copyUpdateAdvice,
|
|
@@ -98,7 +101,9 @@ import {
|
|
|
98
101
|
configPath as harnessConfigPath,
|
|
99
102
|
packageCommand,
|
|
100
103
|
invocation,
|
|
104
|
+
harnessForOverlay,
|
|
101
105
|
} from "./harness.mjs";
|
|
106
|
+
import { effortSpec } from "./models.mjs";
|
|
102
107
|
|
|
103
108
|
// A remedy used to interpolate the surface's harness variable here. It cannot:
|
|
104
109
|
// measured 2026-09-06, NO harness gives its Bash tool that variable, and a
|
|
@@ -366,7 +371,7 @@ export function checkStatusline(cfg, proj, home = homedir(), harness = speakingH
|
|
|
366
371
|
// A harness without a status line hears nothing about one, whatever the
|
|
367
372
|
// shared binding asks for (generation spec, contract 18).
|
|
368
373
|
if (harness?.surfaces?.statusline?.supported === false) return out;
|
|
369
|
-
const local = hostSettingsPath(proj);
|
|
374
|
+
const local = hostSettingsPath(proj, process.env, harness);
|
|
370
375
|
let cur = null;
|
|
371
376
|
if (existsSync(local)) {
|
|
372
377
|
try {
|
|
@@ -524,10 +529,10 @@ export function checkPendingUpgrade(proj, home = homedir(), root = pluginRoot(),
|
|
|
524
529
|
// spec's 2026-10-03 amendment, case S1).
|
|
525
530
|
let wired = null;
|
|
526
531
|
try {
|
|
527
|
-
wired = JSON.parse(readFileSync(hostSettingsPath(proj), "utf8"))?.statusLine?.command || null;
|
|
532
|
+
wired = JSON.parse(readFileSync(hostSettingsPath(proj, process.env, harness), "utf8"))?.statusLine?.command || null;
|
|
528
533
|
} catch {}
|
|
529
534
|
if (!wired || !statusLineIsOurWiring(wired, proj, home, root)) return [];
|
|
530
|
-
const lp = pickExisting(statusLineLauncherPath(proj), legacyStatusLineLauncherPath(proj));
|
|
535
|
+
const lp = pickExisting(statusLineLauncherPath(proj, harness?.id), legacyStatusLineLauncherPath(proj));
|
|
531
536
|
let text;
|
|
532
537
|
try {
|
|
533
538
|
text = readFileSync(lp, "utf8");
|
|
@@ -564,6 +569,7 @@ export function checkAgentsBlock(proj, { env = process.env, root = pluginRoot()
|
|
|
564
569
|
let unclosedFiles = 0;
|
|
565
570
|
const perFile = {};
|
|
566
571
|
const staleVersions = [];
|
|
572
|
+
const newerVersions = [];
|
|
567
573
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
568
574
|
const p = join(proj, name);
|
|
569
575
|
if (!existsSync(p)) continue;
|
|
@@ -607,7 +613,10 @@ export function checkAgentsBlock(proj, { env = process.env, root = pluginRoot()
|
|
|
607
613
|
}
|
|
608
614
|
for (const m of text.matchAll(AGENT_BLOCK_MARKER)) {
|
|
609
615
|
const v = parseInt(m[1], 10);
|
|
610
|
-
|
|
616
|
+
// A marker above this copy's template is never stale: this copy writes
|
|
617
|
+
// no block down (the install spec, contract 6, as amended 2026-10-14).
|
|
618
|
+
if (isNewerBlock(v, AGENT_BLOCK_VERSION)) newerVersions.push({ file: name, v });
|
|
619
|
+
else if (v !== AGENT_BLOCK_VERSION) staleVersions.push({ file: name, v });
|
|
611
620
|
}
|
|
612
621
|
}
|
|
613
622
|
if (blocks === 0) {
|
|
@@ -664,6 +673,22 @@ export function checkAgentsBlock(proj, { env = process.env, root = pluginRoot()
|
|
|
664
673
|
);
|
|
665
674
|
out.push(refuses ? f : moveRepairs(f, fact));
|
|
666
675
|
}
|
|
676
|
+
// A newer block: an info in the install report only — the startup line
|
|
677
|
+
// counts issues alone and offers none of this — and only for one
|
|
678
|
+
// well-formed block. Its way out is the running copy's own bin from a
|
|
679
|
+
// terminal, never the session's command, whose copy would write its own
|
|
680
|
+
// version. Not folded into the layout move, which leaves it as it is too.
|
|
681
|
+
if (blocks === 1 && !refuses)
|
|
682
|
+
for (const s of newerVersions)
|
|
683
|
+
out.push(
|
|
684
|
+
finding(
|
|
685
|
+
"install",
|
|
686
|
+
"info",
|
|
687
|
+
"agents-block",
|
|
688
|
+
`Agents block in ${s.file} is ${newerBlockWords(s.v, AGENT_BLOCK_VERSION)}: install, upgrade and setup leave it as it is. To write this copy's v${AGENT_BLOCK_VERSION} over it ${fromTerminalIn(proj)}:\nnode "${join(root, "bin", "projectstore.mjs")}" install --harness ${newerReader(proj, s.file, env)} --surface agents_block`,
|
|
689
|
+
s.file,
|
|
690
|
+
),
|
|
691
|
+
);
|
|
667
692
|
// Placement, held to the predicate install plans from (the install spec,
|
|
668
693
|
// contract 6 as amended after the rc.3 tag): one well-formed block, seen by
|
|
669
694
|
// every harness the project uses. rc.1 and rc.2 left a block in an
|
|
@@ -708,6 +733,19 @@ export function checkAgentsBlock(proj, { env = process.env, root = pluginRoot()
|
|
|
708
733
|
return out;
|
|
709
734
|
}
|
|
710
735
|
|
|
736
|
+
// The harness a newer block's way out names: the one this run identified,
|
|
737
|
+
// else one the project uses whose files hold the block, else the source
|
|
738
|
+
// harness — never Claude Code's by default in a Codex-only project, whose
|
|
739
|
+
// install would create CLAUDE.md and overwrite nothing.
|
|
740
|
+
function newerReader(proj, file, env) {
|
|
741
|
+
const identified = identifiedHarnessId(env);
|
|
742
|
+
if (identified) return identified;
|
|
743
|
+
const used = detectHarnesses(proj, { state: true }).map((d) => d.id);
|
|
744
|
+
return (
|
|
745
|
+
used.find((id) => (loadHarness(id)?.surfaces?.agents_block?.files || []).includes(file)) || sourceHarness()?.id
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
|
|
711
749
|
// While the layout move is pending, the findings the move itself repairs fold
|
|
712
750
|
// into it instead of being counted beside it (the layout spec, contract 7 as
|
|
713
751
|
// amended after the rc.3 tag; the upgrade story's O1): the stale block, which
|
|
@@ -891,7 +929,7 @@ export function checkOverlays(cfg, proj, { root = pluginRoot(), home = homedir()
|
|
|
891
929
|
"install",
|
|
892
930
|
"issue",
|
|
893
931
|
"overlay-forbidden-key",
|
|
894
|
-
`${where} carries \`${k}\`, which an overlay may not: only agents.default.model
|
|
932
|
+
`${where} carries \`${k}\`, which an overlay may not: only agents.default.model, agents.default.effort, agents.per_agent.<name>.model and agents.per_agent.<name>.effort are read; the key is ignored.`,
|
|
895
933
|
where,
|
|
896
934
|
),
|
|
897
935
|
);
|
|
@@ -953,19 +991,42 @@ export function checkOverlays(cfg, proj, { root = pluginRoot(), home = homedir()
|
|
|
953
991
|
}
|
|
954
992
|
}
|
|
955
993
|
}
|
|
956
|
-
// A configured name no roster agent carries runs nothing:
|
|
957
|
-
// applies. A warn, not an issue — a newer package's agent
|
|
958
|
-
// reason for a committed overlay to name one this copy does
|
|
994
|
+
// A configured name no roster agent carries runs nothing: what it
|
|
995
|
+
// configures never applies. A warn, not an issue — a newer package's agent
|
|
996
|
+
// is a legitimate reason for a committed overlay to name one this copy does
|
|
997
|
+
// not ship.
|
|
998
|
+
// An effort the harness's levels lack fails at spawn on a harness whose
|
|
999
|
+
// spawn takes those levels only, and is the harness's to judge elsewhere:
|
|
1000
|
+
// a warn names it either way (the effort ADR, decision 7). Read against the
|
|
1001
|
+
// manifest's floor alone: doctor reads no model catalog.
|
|
1002
|
+
const levels = effortSpec(harnessForOverlay(o.id))?.levels?.map((l) => l.id) || null;
|
|
1003
|
+
if (levels) {
|
|
1004
|
+
const set = [
|
|
1005
|
+
...(o.efforts.default ? [["agents.default.effort", o.efforts.default]] : []),
|
|
1006
|
+
...Object.entries(o.efforts.per_agent).map(([n, v]) => [`agents.per_agent.${n}.effort`, v]),
|
|
1007
|
+
];
|
|
1008
|
+
for (const [k, v] of set)
|
|
1009
|
+
if (!levels.includes(v))
|
|
1010
|
+
out.push(
|
|
1011
|
+
finding(
|
|
1012
|
+
"install",
|
|
1013
|
+
"warn",
|
|
1014
|
+
"overlay-unknown-effort",
|
|
1015
|
+
`${where} sets \`${k}\` to ${v}, which is not one of ${harnessForOverlay(o.id)?.display_name || o.id}'s effort levels (${levels.join(", ")}) — a spawn may refuse it.`,
|
|
1016
|
+
where,
|
|
1017
|
+
),
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
959
1020
|
const roster = layoutRoster(cfg);
|
|
960
1021
|
if (roster) {
|
|
961
|
-
for (const n of Object.keys(o.agents.per_agent)) {
|
|
1022
|
+
for (const n of new Set([...Object.keys(o.agents.per_agent), ...Object.keys(o.efforts.per_agent)])) {
|
|
962
1023
|
if (!roster.includes(n))
|
|
963
1024
|
out.push(
|
|
964
1025
|
finding(
|
|
965
1026
|
"install",
|
|
966
1027
|
"warn",
|
|
967
1028
|
"overlay-unknown-agent",
|
|
968
|
-
`${where} configures \`${n}\`, which is not in the ${cfg.layout} roster (${roster.join(", ")}) — no agent by that name runs, so
|
|
1029
|
+
`${where} configures \`${n}\`, which is not in the ${cfg.layout} roster (${roster.join(", ")}) — no agent by that name runs, so what it configures never applies. A typo, or an agent this package does not ship yet.`,
|
|
969
1030
|
where,
|
|
970
1031
|
),
|
|
971
1032
|
);
|
|
@@ -1148,7 +1209,16 @@ export function checkVersionDrift(home = homedir(), states = [], proj = null, {
|
|
|
1148
1209
|
const seen = [];
|
|
1149
1210
|
// The registry read here is the source harness's own.
|
|
1150
1211
|
const registryOf = sourceHarness()?.id || null;
|
|
1151
|
-
for
|
|
1212
|
+
// The copies the host loads for this project (contract 17, amended
|
|
1213
|
+
// 2026-10-14): this checkout's own row or a user-scope row, never another
|
|
1214
|
+
// project's — the registry keeps every project's local rows, and another
|
|
1215
|
+
// checkout's old copy is not a drift here. Every row, not one per key: this
|
|
1216
|
+
// project's own row against a user-scope row is the drift contract 17 names.
|
|
1217
|
+
// Without a project, the user-scope rows alone.
|
|
1218
|
+
const rows = proj
|
|
1219
|
+
? projectCopies(home, proj)
|
|
1220
|
+
: installedPluginEntries(home).filter((e) => e.present && e.enabled !== false && !e.projectPath);
|
|
1221
|
+
for (const e of rows) {
|
|
1152
1222
|
// A disabled registration is not a copy anyone runs (contract 17, amended 2026-09-05).
|
|
1153
1223
|
if (e.version && e.present && e.enabled !== false)
|
|
1154
1224
|
seen.push({
|
|
@@ -1284,24 +1354,32 @@ export function checkOverrideCopies(proj, home = homedir(), harness = speakingHa
|
|
|
1284
1354
|
return out;
|
|
1285
1355
|
}
|
|
1286
1356
|
|
|
1287
|
-
//
|
|
1288
|
-
//
|
|
1289
|
-
//
|
|
1290
|
-
//
|
|
1291
|
-
// of
|
|
1357
|
+
// The variable outranks every effort projectstore passes at spawn and the
|
|
1358
|
+
// bundled agents' frontmatter effort: with it set, the spawn offers no effort
|
|
1359
|
+
// parameter at all (the effort ADR, decision 7). So a value set for cost or
|
|
1360
|
+
// latency silently replaces the efforts the overlay configures — exactly the
|
|
1361
|
+
// class of "the value configured is not the value that runs" doctor names.
|
|
1292
1362
|
// The variable names come from the manifest (runtime.agent_overrides); the
|
|
1293
|
-
//
|
|
1294
|
-
export function checkEnvEffort() {
|
|
1363
|
+
// overlay is read only when one is set, so a clean startup reads nothing.
|
|
1364
|
+
export function checkEnvEffort(proj = null) {
|
|
1295
1365
|
return agentOverrides()
|
|
1296
1366
|
.filter((o) => o.kind === "effort")
|
|
1297
|
-
.map((o) =>
|
|
1298
|
-
|
|
1367
|
+
.map((o) => {
|
|
1368
|
+
const ov = proj ? readOverlayAt(proj) : null;
|
|
1369
|
+
const set = ov
|
|
1370
|
+
? [
|
|
1371
|
+
...(ov.efforts.default ? ["agents.default.effort"] : []),
|
|
1372
|
+
...Object.keys(ov.efforts.per_agent).map((n) => `agents.per_agent.${n}.effort`),
|
|
1373
|
+
]
|
|
1374
|
+
: [];
|
|
1375
|
+
const where = set.length ? ` (${relative(proj, ov.path) || ov.path} sets ${set.join(", ")})` : "";
|
|
1376
|
+
return finding(
|
|
1299
1377
|
"install",
|
|
1300
1378
|
"warn",
|
|
1301
1379
|
"env-effort",
|
|
1302
|
-
`${o.env}=${o.value} is set — it
|
|
1303
|
-
)
|
|
1304
|
-
);
|
|
1380
|
+
`${o.env}=${o.value} is set — it outranks every effort projectstore passes at spawn${where} and the bundled agents' frontmatter, so every projectstore agent runs at "${o.value}". Unset it for the configured efforts to run.`,
|
|
1381
|
+
);
|
|
1382
|
+
});
|
|
1305
1383
|
}
|
|
1306
1384
|
|
|
1307
1385
|
export function checkEnvModel() {
|
|
@@ -1538,28 +1616,48 @@ export function checkGitignore(proj) {
|
|
|
1538
1616
|
}
|
|
1539
1617
|
}
|
|
1540
1618
|
}
|
|
1541
|
-
|
|
1619
|
+
// The host's settings file is checkHostIgnored's (the layout spec, contract 9
|
|
1620
|
+
// as amended 2026-10-14): only git knows every rule that ignores it, and this
|
|
1621
|
+
// function runs in the SessionStart subset, whose budget forbids a subprocess.
|
|
1622
|
+
return out;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// The line read the host-file check used before it asked git, and still uses
|
|
1626
|
+
// when git cannot answer: the project's own .gitignore, exact lines only.
|
|
1627
|
+
function hostIgnoreLineMissing(proj, env) {
|
|
1542
1628
|
let lines = [];
|
|
1543
1629
|
try {
|
|
1544
1630
|
lines = readFileSync(join(proj, ".gitignore"), "utf8")
|
|
1545
1631
|
.split("\n")
|
|
1546
1632
|
.map((l) => l.trim());
|
|
1547
1633
|
} catch {}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1634
|
+
if (lines.includes(".claude/") || lines.includes(".claude")) return [];
|
|
1635
|
+
return [relative(proj, hostSettingsPath(proj, env, sourceHarness()))].filter((w) => !lines.includes(w));
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// Whether git ignores the host's machine-local settings file (the layout spec,
|
|
1639
|
+
// contract 9 as amended 2026-10-14). Our own files are self-ignored inside
|
|
1640
|
+
// .projectstore/; what is left is the host's. Git's answer covers every rule
|
|
1641
|
+
// it applies — nested .gitignore files, .git/info/exclude, core.excludesFile
|
|
1642
|
+
// and its XDG default — where the line read saw only the project's exact
|
|
1643
|
+
// lines. --no-index keeps this about rules: a tracked file with a matching rule
|
|
1644
|
+
// is quiet here (tracking is checkTrackedRuntime's). Exit 0 is ignored, 1 is
|
|
1645
|
+
// not; anything else — no git on PATH, an error, a timeout — falls back to the
|
|
1646
|
+
// line read. Full report only: a subprocess, like checkTrackedRuntime.
|
|
1647
|
+
export function checkHostIgnored(proj, { env = process.env } = {}) {
|
|
1648
|
+
if (!existsSync(join(proj, ".git"))) return [];
|
|
1649
|
+
const file = relative(proj, hostSettingsPath(proj, env, sourceHarness()));
|
|
1650
|
+
const r = spawnSync("git", ["check-ignore", "-q", "--no-index", "--", file], { cwd: proj, env, timeout: 5000 });
|
|
1651
|
+
const missing = r.status === 0 ? [] : r.status === 1 ? [file] : hostIgnoreLineMissing(proj, env);
|
|
1652
|
+
if (!missing.length) return [];
|
|
1653
|
+
return [
|
|
1555
1654
|
finding(
|
|
1556
1655
|
"install",
|
|
1557
1656
|
"warn",
|
|
1558
1657
|
"gitignore",
|
|
1559
1658
|
`Machine-specific files not gitignored: ${missing.join(", ")} (or ignore ".claude/" wholesale).`,
|
|
1560
1659
|
),
|
|
1561
|
-
|
|
1562
|
-
return out;
|
|
1660
|
+
];
|
|
1563
1661
|
}
|
|
1564
1662
|
|
|
1565
1663
|
// An ignore line never untracks a file already in the index, so the project
|
|
@@ -2956,8 +3054,9 @@ export async function runInstallChecks(cfg, proj, opts = {}) {
|
|
|
2956
3054
|
...checkAgentsBlock(proj, { env: opts.env, root: opts.root }),
|
|
2957
3055
|
...checkOverrideCopies(proj),
|
|
2958
3056
|
...checkEnvModel(),
|
|
2959
|
-
...checkEnvEffort(),
|
|
3057
|
+
...checkEnvEffort(proj),
|
|
2960
3058
|
...checkGitignore(proj),
|
|
3059
|
+
...checkHostIgnored(proj, { env: opts.env }),
|
|
2961
3060
|
...checkTrackedRuntime(proj),
|
|
2962
3061
|
...checkVaultGit(cfg),
|
|
2963
3062
|
...checkAutoUpdate(),
|
|
@@ -3042,7 +3141,7 @@ export function runStartupChecks(cfg, proj, budgetMs = 150) {
|
|
|
3042
3141
|
restamp: findings.some((f) => f.level === "info" && f.check === "upgrade"),
|
|
3043
3142
|
}),
|
|
3044
3143
|
() => checkEnvModel(),
|
|
3045
|
-
() => checkEnvEffort(),
|
|
3144
|
+
() => checkEnvEffort(proj),
|
|
3046
3145
|
];
|
|
3047
3146
|
for (const step of steps) {
|
|
3048
3147
|
if (Date.now() - started > budgetMs) return { skipped: true, count: 0, findings: foldIntoMove(findings) };
|
|
@@ -503,10 +503,12 @@ export function speakingHarness(env = process.env, dir = MANIFEST_DIR) {
|
|
|
503
503
|
|
|
504
504
|
// ─── Runtime names and paths ───────────────────────────────────────────
|
|
505
505
|
|
|
506
|
-
// The three branded names
|
|
507
|
-
// variable it is talking about without
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
// The three branded names a harness uses — the active one unless the caller
|
|
507
|
+
// names it — so a caller can say which variable it is talking about without
|
|
508
|
+
// spelling it. A surface rendered FOR a harness (the status-line launcher is
|
|
509
|
+
// Claude Code's) names that harness, whichever process renders it.
|
|
510
|
+
export function runtimeEnvNames(env = process.env, harness = activeHarness(env)) {
|
|
511
|
+
const r = harness?.runtime || {};
|
|
510
512
|
return { projectDir: r.project_dir_env || null, pluginRoot: r.plugin_root_env || null, home: r.home_env || null };
|
|
511
513
|
}
|
|
512
514
|
|
|
@@ -816,17 +818,20 @@ export function pickExisting(current, legacy) {
|
|
|
816
818
|
|
|
817
819
|
// The per-project directory the HARNESS discovers (".claude" for the source
|
|
818
820
|
// layout) — how a harness is detected in a project and where its own settings
|
|
819
|
-
// live. Not where our config is: that is layoutPaths() (2026-09-06).
|
|
820
|
-
|
|
821
|
-
|
|
821
|
+
// live. Not where our config is: that is layoutPaths() (2026-09-06). A
|
|
822
|
+
// caller reading ONE harness's settings names it: which harness launched the
|
|
823
|
+
// process is not whose directory that is (the install spec, contract 6, as
|
|
824
|
+
// amended 2026-10-14).
|
|
825
|
+
export function projectConfigDir(env = process.env, harness = activeHarness(env)) {
|
|
826
|
+
return harness?.runtime?.harness_dir || ".claude";
|
|
822
827
|
}
|
|
823
828
|
|
|
824
829
|
// The host's own machine-local settings file in a project (the statusline
|
|
825
830
|
// surface's file in the manifest — `.claude/settings.local.json` for Claude
|
|
826
831
|
// Code). A harness surface, not ours: the one `.claude` path the core may
|
|
827
832
|
// build, and it builds it from the manifest (2026-09-06).
|
|
828
|
-
export function hostSettingsPath(projectDir, env = process.env) {
|
|
829
|
-
const h =
|
|
833
|
+
export function hostSettingsPath(projectDir, env = process.env, harness = activeHarness(env)) {
|
|
834
|
+
const h = harness;
|
|
830
835
|
const file = h?.surfaces?.statusline?.file || join(h?.runtime?.harness_dir || ".claude", "settings.local.json");
|
|
831
836
|
return join(projectDir, file);
|
|
832
837
|
}
|