@phren/cli 0.1.11 → 0.1.12
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/cli/hooks-session.js +7 -2
- package/dist/cli/team.js +12 -0
- package/dist/cli-hooks-git.js +9 -2
- package/dist/cli-hooks-stop.js +11 -23
- package/dist/tools/ops.js +39 -3
- package/package.json +1 -1
|
@@ -428,8 +428,13 @@ async function runBestEffortGit(args, cwd) {
|
|
|
428
428
|
}
|
|
429
429
|
async function countUnsyncedCommits(cwd) {
|
|
430
430
|
const upstream = await runBestEffortGit(["rev-parse", "--abbrev-ref", "@{upstream}"], cwd);
|
|
431
|
-
if (!upstream.ok || !upstream.output)
|
|
432
|
-
|
|
431
|
+
if (!upstream.ok || !upstream.output) {
|
|
432
|
+
const allCommits = await runBestEffortGit(["rev-list", "--count", "HEAD"], cwd);
|
|
433
|
+
if (!allCommits.ok || !allCommits.output)
|
|
434
|
+
return 0;
|
|
435
|
+
const parsed = Number.parseInt(allCommits.output.trim(), 10);
|
|
436
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
437
|
+
}
|
|
433
438
|
const ahead = await runBestEffortGit(["rev-list", "--count", `${upstream.output.trim()}..HEAD`], cwd);
|
|
434
439
|
if (!ahead.ok || !ahead.output)
|
|
435
440
|
return 0;
|
package/dist/cli/team.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as path from "path";
|
|
|
7
7
|
import { execFileSync } from "child_process";
|
|
8
8
|
import { getPhrenPath } from "../shared.js";
|
|
9
9
|
import { isValidProjectName } from "../utils.js";
|
|
10
|
+
import { addProjectToProfile, resolveActiveProfile } from "../profile-store.js";
|
|
10
11
|
import { addStoreToRegistry, findStoreByName, generateStoreId, readTeamBootstrap, updateStoreProjects, } from "../store-registry.js";
|
|
11
12
|
const EXEC_TIMEOUT_MS = 30_000;
|
|
12
13
|
function getOptionValue(args, name) {
|
|
@@ -243,6 +244,17 @@ async function handleTeamAddProject(args) {
|
|
|
243
244
|
if (!currentProjects.includes(projectName)) {
|
|
244
245
|
updateStoreProjects(phrenPath, storeName, [...currentProjects, projectName]);
|
|
245
246
|
}
|
|
247
|
+
// Add project to active profile so it's visible to profile-filtered operations
|
|
248
|
+
const activeProfile = resolveActiveProfile(phrenPath);
|
|
249
|
+
if (activeProfile.ok && activeProfile.data) {
|
|
250
|
+
const addResult = addProjectToProfile(phrenPath, activeProfile.data, projectName);
|
|
251
|
+
if (!addResult.ok) {
|
|
252
|
+
throw new Error(addResult.error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (!activeProfile.ok && activeProfile.code !== "FILE_NOT_FOUND") {
|
|
256
|
+
throw new Error(activeProfile.error);
|
|
257
|
+
}
|
|
246
258
|
console.log(`Added project "${projectName}" to team store "${storeName}"`);
|
|
247
259
|
console.log(` Path: ${projectDir}`);
|
|
248
260
|
console.log(` Journal: ${journalDir}`);
|
package/dist/cli-hooks-git.js
CHANGED
|
@@ -123,8 +123,15 @@ export async function runBestEffortGit(args, cwd) {
|
|
|
123
123
|
}
|
|
124
124
|
export async function countUnsyncedCommits(cwd) {
|
|
125
125
|
const upstream = await runBestEffortGit(["rev-parse", "--abbrev-ref", "@{upstream}"], cwd);
|
|
126
|
-
if (!upstream.ok || !upstream.output)
|
|
127
|
-
|
|
126
|
+
if (!upstream.ok || !upstream.output) {
|
|
127
|
+
// No upstream tracking branch — count all local commits as unsynced
|
|
128
|
+
// so the warning at the call site fires instead of silently returning 0
|
|
129
|
+
const allCommits = await runBestEffortGit(["rev-list", "--count", "HEAD"], cwd);
|
|
130
|
+
if (!allCommits.ok || !allCommits.output)
|
|
131
|
+
return 0;
|
|
132
|
+
const parsed = Number.parseInt(allCommits.output.trim(), 10);
|
|
133
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
134
|
+
}
|
|
128
135
|
const ahead = await runBestEffortGit(["rev-list", "--count", `${upstream.output.trim()}..HEAD`], cwd);
|
|
129
136
|
if (!ahead.ok || !ahead.output)
|
|
130
137
|
return 0;
|
package/dist/cli-hooks-stop.js
CHANGED
|
@@ -435,32 +435,20 @@ export async function handleHookStop() {
|
|
|
435
435
|
return;
|
|
436
436
|
}
|
|
437
437
|
// Check if HEAD has an upstream tracking branch before attempting sync.
|
|
438
|
-
//
|
|
438
|
+
// If no upstream is set but a remote exists, auto-set it to avoid silent push failures.
|
|
439
439
|
const upstream = await runBestEffortGit(["rev-parse", "--abbrev-ref", "@{upstream}"], phrenPath);
|
|
440
440
|
if (!upstream.ok || !upstream.output) {
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
lastStopAt: now,
|
|
451
|
-
lastAutoSave: { at: now, status: "no-upstream", detail: noUpstreamDetail },
|
|
452
|
-
lastSync: {
|
|
453
|
-
lastPushAt: now,
|
|
454
|
-
lastPushStatus: "no-upstream",
|
|
455
|
-
lastPushDetail: noUpstreamDetail,
|
|
456
|
-
unsyncedCommits,
|
|
457
|
-
},
|
|
458
|
-
});
|
|
459
|
-
appendAuditLog(phrenPath, "hook_stop", "status=no-upstream");
|
|
460
|
-
if (unsyncedCommits > 3) {
|
|
461
|
-
process.stderr.write(`phren: ${unsyncedCommits} unsynced commits — no upstream tracking branch.\n`);
|
|
441
|
+
// Try to auto-set upstream: get current branch and set tracking to origin/<branch>
|
|
442
|
+
const branch = await runBestEffortGit(["rev-parse", "--abbrev-ref", "HEAD"], phrenPath);
|
|
443
|
+
if (branch.ok && branch.output) {
|
|
444
|
+
const branchName = branch.output.trim();
|
|
445
|
+
const setUpstream = await runBestEffortGit(["branch", "--set-upstream-to", `origin/${branchName}`, branchName], phrenPath);
|
|
446
|
+
if (!setUpstream.ok) {
|
|
447
|
+
// Upstream auto-set failed — log and continue to sync anyway
|
|
448
|
+
logger.debug("hookStop", `failed to auto-set upstream for ${branchName}`);
|
|
449
|
+
}
|
|
462
450
|
}
|
|
463
|
-
|
|
451
|
+
// Fall through to scheduleBackgroundSync instead of returning early
|
|
464
452
|
}
|
|
465
453
|
const unsyncedCommits = await countUnsyncedCommits(phrenPath);
|
|
466
454
|
const scheduled = scheduleBackgroundSync(phrenPath);
|
package/dist/tools/ops.js
CHANGED
|
@@ -10,16 +10,48 @@ import { addProjectFromPath } from "../core/project.js";
|
|
|
10
10
|
import { PROJECT_OWNERSHIP_MODES, parseProjectOwnershipMode } from "../project-config.js";
|
|
11
11
|
import { resolveRuntimeProfile } from "../runtime-profile.js";
|
|
12
12
|
import { getMachineName } from "../machine-identity.js";
|
|
13
|
+
import { resolveAllStores } from "../store-registry.js";
|
|
13
14
|
import { getProjectConsolidationStatus, CONSOLIDATION_ENTRY_THRESHOLD } from "../content/validate.js";
|
|
14
15
|
import { logger } from "../logger.js";
|
|
15
16
|
import { getRuntimeHealth } from "../governance/policy.js";
|
|
16
17
|
import { countUnsyncedCommits } from "../cli-hooks-git.js";
|
|
17
18
|
// ── Handlers ─────────────────────────────────────────────────────────────────
|
|
18
|
-
async function handleAddProject(ctx, { path: targetPath, profile: requestedProfile, ownership }) {
|
|
19
|
+
async function handleAddProject(ctx, { path: targetPath, profile: requestedProfile, ownership, store: storeName }) {
|
|
19
20
|
const { phrenPath, profile, withWriteQueue } = ctx;
|
|
20
21
|
return withWriteQueue(async () => {
|
|
21
22
|
try {
|
|
22
|
-
|
|
23
|
+
// Resolve the target store path: explicit store > auto-route by project claim > primary
|
|
24
|
+
let targetPhrenPath = phrenPath;
|
|
25
|
+
let storeRole = "primary";
|
|
26
|
+
if (storeName) {
|
|
27
|
+
const stores = resolveAllStores(phrenPath);
|
|
28
|
+
const store = stores.find((s) => s.name === storeName);
|
|
29
|
+
if (!store) {
|
|
30
|
+
return mcpResponse({ ok: false, error: `Store "${storeName}" not found` });
|
|
31
|
+
}
|
|
32
|
+
if (store.role === "readonly") {
|
|
33
|
+
return mcpResponse({ ok: false, error: `Store "${storeName}" is read-only` });
|
|
34
|
+
}
|
|
35
|
+
targetPhrenPath = store.path;
|
|
36
|
+
storeRole = store.role;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Check if any non-primary writable store claims this project
|
|
40
|
+
const projectName = targetPath
|
|
41
|
+
? path.basename(path.resolve(targetPath)).toLowerCase().replace(/[^a-z0-9_-]/g, "-")
|
|
42
|
+
: undefined;
|
|
43
|
+
if (projectName) {
|
|
44
|
+
const stores = resolveAllStores(phrenPath);
|
|
45
|
+
for (const store of stores) {
|
|
46
|
+
if (store.role !== "readonly" && store.role !== "primary" && store.projects?.includes(projectName)) {
|
|
47
|
+
targetPhrenPath = store.path;
|
|
48
|
+
storeRole = store.role;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const added = addProjectFromPath(targetPhrenPath, targetPath, requestedProfile || profile || undefined, parseProjectOwnershipMode(ownership) ?? undefined);
|
|
23
55
|
if (!added.ok) {
|
|
24
56
|
return mcpResponse({
|
|
25
57
|
ok: false,
|
|
@@ -29,7 +61,9 @@ async function handleAddProject(ctx, { path: targetPath, profile: requestedProfi
|
|
|
29
61
|
await ctx.rebuildIndex();
|
|
30
62
|
return mcpResponse({
|
|
31
63
|
ok: true,
|
|
32
|
-
message: `Added project "${added.data.project}" (${added.data.ownership}) from ${added.data.path}
|
|
64
|
+
message: `Added project "${added.data.project}" (${added.data.ownership}) from ${added.data.path}` +
|
|
65
|
+
(storeRole !== "primary" ? ` [store: ${storeName || storeRole}]` : "") +
|
|
66
|
+
`.`,
|
|
33
67
|
data: added.data,
|
|
34
68
|
});
|
|
35
69
|
}
|
|
@@ -419,6 +453,8 @@ export function register(server, ctx) {
|
|
|
419
453
|
profile: z.string().optional().describe("Profile to update. Defaults to the active profile."),
|
|
420
454
|
ownership: z.enum(PROJECT_OWNERSHIP_MODES).optional()
|
|
421
455
|
.describe("How Phren should treat repo-facing instruction files: phren-managed, detached, or repo-managed."),
|
|
456
|
+
store: z.string().optional()
|
|
457
|
+
.describe("Target store name (from stores.yaml). If omitted, auto-routes to the store that claims this project, or falls back to the primary store."),
|
|
422
458
|
}),
|
|
423
459
|
}, (params) => handleAddProject(ctx, params));
|
|
424
460
|
server.registerTool("health_check", {
|