@phren/cli 0.1.10 → 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.
@@ -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
- return 0;
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;
@@ -1156,17 +1161,51 @@ export async function handleHookContext() {
1156
1161
  parts.push("");
1157
1162
  }
1158
1163
  }
1164
+ // Collect pinned tasks across ALL projects (excluding Done)
1165
+ const allTaskRows = queryRows(db, "SELECT project, content FROM docs WHERE type = 'task'", []);
1166
+ const pinnedFromOtherProjects = [];
1167
+ if (allTaskRows) {
1168
+ for (const row of allTaskRows) {
1169
+ const taskProject = row[0];
1170
+ if (taskProject === project)
1171
+ continue;
1172
+ const content = row[1];
1173
+ const pinned = content.split("\n")
1174
+ .filter(l => l.startsWith("- [ ] ") && /\[pinned\]/i.test(l))
1175
+ .map(l => `[${taskProject}] ${l}`);
1176
+ pinnedFromOtherProjects.push(...pinned);
1177
+ }
1178
+ }
1179
+ // Active project tasks — pinned float to top, exclude Done
1159
1180
  const taskRow = queryRows(db, "SELECT content FROM docs WHERE project = ? AND type = 'task'", [project]);
1181
+ const pinnedItems = [];
1182
+ const otherItems = [];
1160
1183
  if (taskRow) {
1161
1184
  const content = taskRow[0][0];
1162
- const activeItems = content.split("\n").filter(l => l.startsWith("- "));
1163
- const filtered = filterTaskByPriority(activeItems);
1164
- const trimmed = filtered.slice(0, 5);
1165
- if (trimmed.length > 0) {
1185
+ const allItems = content.split("\n").filter(l => l.startsWith("- [ ] "));
1186
+ for (const item of allItems) {
1187
+ if (/\[pinned\]/i.test(item)) {
1188
+ pinnedItems.push(item);
1189
+ }
1190
+ else {
1191
+ otherItems.push(item);
1192
+ }
1193
+ }
1194
+ }
1195
+ const filteredOther = filterTaskByPriority(otherItems);
1196
+ const allPinned = [...pinnedItems, ...pinnedFromOtherProjects].slice(0, 10);
1197
+ const remaining = Math.max(0, 5 - allPinned.length);
1198
+ const trimmedOther = filteredOther.slice(0, remaining);
1199
+ if (allPinned.length > 0 || trimmedOther.length > 0) {
1200
+ if (allPinned.length > 0) {
1201
+ parts.push("## Pinned tasks");
1202
+ parts.push(allPinned.join("\n"));
1203
+ }
1204
+ if (trimmedOther.length > 0) {
1166
1205
  parts.push("## Active tasks");
1167
- parts.push(trimmed.join("\n"));
1168
- parts.push("");
1206
+ parts.push(trimmedOther.join("\n"));
1169
1207
  }
1208
+ parts.push("");
1170
1209
  }
1171
1210
  }
1172
1211
  else {
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}`);
@@ -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
- return 0;
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;
@@ -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
- // Detached HEAD or branches without upstream would cause silent push failures.
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
- const unsyncedCommits = await countUnsyncedCommits(phrenPath);
442
- const noUpstreamDetail = "commit created; no upstream tracking branch";
443
- finalizeTaskSession({
444
- phrenPath,
445
- sessionId: taskSessionId,
446
- status: "no-upstream",
447
- detail: noUpstreamDetail,
448
- });
449
- updateRuntimeHealth(phrenPath, {
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
- return;
451
+ // Fall through to scheduleBackgroundSync instead of returning early
464
452
  }
465
453
  const unsyncedCommits = await countUnsyncedCommits(phrenPath);
466
454
  const scheduled = scheduleBackgroundSync(phrenPath);