@phren/cli 0.0.28 → 0.0.32
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/mcp/dist/capabilities/cli.js +2 -5
- package/mcp/dist/capabilities/mcp.js +5 -8
- package/mcp/dist/capabilities/types.js +2 -5
- package/mcp/dist/capabilities/vscode.js +2 -5
- package/mcp/dist/capabilities/web-ui.js +2 -5
- package/mcp/dist/{cli-actions.js → cli/actions.js} +22 -21
- package/mcp/dist/{cli.js → cli/cli.js} +13 -13
- package/mcp/dist/{cli-config.js → cli/config.js} +9 -9
- package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
- package/mcp/dist/{cli-govern.js → cli/govern.js} +10 -9
- package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
- package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
- package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
- package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
- package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
- package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +42 -57
- package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
- package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
- package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
- package/mcp/dist/{cli-search.js → cli/search.js} +8 -7
- package/mcp/dist/cli-hooks-git.js +243 -0
- package/mcp/dist/cli-hooks-prompt.js +319 -0
- package/mcp/dist/cli-hooks-session-handlers.js +349 -0
- package/mcp/dist/cli-hooks-stop.js +557 -0
- package/mcp/dist/{content-archive.js → content/archive.js} +8 -9
- package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
- package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
- package/mcp/dist/{content-learning.js → content/learning.js} +12 -12
- package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
- package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
- package/mcp/dist/{core-project.js → core/project.js} +4 -4
- package/mcp/dist/{core-search.js → core/search.js} +2 -2
- package/mcp/dist/{data-access.js → data/access.js} +131 -13
- package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
- package/mcp/dist/embedding.js +9 -14
- package/mcp/dist/entrypoint.js +11 -11
- package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
- package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
- package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
- package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +4 -4
- package/mcp/dist/{governance-audit.js → governance/audit.js} +2 -2
- package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
- package/mcp/dist/{governance-policy.js → governance/policy.js} +10 -12
- package/mcp/dist/{governance-rbac.js → governance/rbac.js} +3 -3
- package/mcp/dist/{governance-scores.js → governance/scores.js} +8 -10
- package/mcp/dist/hooks.js +39 -31
- package/mcp/dist/index-query.js +4 -1
- package/mcp/dist/index.js +53 -29
- package/mcp/dist/{init-config.js → init/config.js} +6 -6
- package/mcp/dist/{init.js → init/init.js} +28 -29
- package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
- package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
- package/mcp/dist/{init-shared.js → init/shared.js} +3 -3
- package/mcp/dist/init-bootstrap.js +68 -0
- package/mcp/dist/init-detect.js +38 -0
- package/mcp/dist/init-dryrun.js +55 -0
- package/mcp/dist/init-env.js +114 -0
- package/mcp/dist/init-fresh.js +239 -0
- package/mcp/dist/init-hooks.js +26 -0
- package/mcp/dist/init-mcp.js +65 -0
- package/mcp/dist/init-migrate.js +51 -0
- package/mcp/dist/init-modes.js +135 -0
- package/mcp/dist/init-npm.js +37 -0
- package/mcp/dist/init-project-local.js +99 -0
- package/mcp/dist/init-semantic.js +48 -0
- package/mcp/dist/init-types.js +1 -0
- package/mcp/dist/init-uninstall.js +482 -0
- package/mcp/dist/init-update.js +96 -0
- package/mcp/dist/init-walkthrough-merge.js +90 -0
- package/mcp/dist/init-walkthrough.js +529 -0
- package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
- package/mcp/dist/{link-context.js → link/context.js} +4 -4
- package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
- package/mcp/dist/{link.js → link/link.js} +26 -31
- package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
- package/mcp/dist/logger.js +11 -3
- package/mcp/dist/phren-art.js +0 -6
- package/mcp/dist/phren-paths.js +30 -12
- package/mcp/dist/proactivity.js +2 -2
- package/mcp/dist/profile-store.js +5 -6
- package/mcp/dist/project-config.js +2 -2
- package/mcp/dist/project-topics.js +1 -1
- package/mcp/dist/query-correlation.js +1 -1
- package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
- package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
- package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
- package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +3 -3
- package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
- package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +15 -24
- package/mcp/dist/shared/governance.js +4 -0
- package/mcp/dist/{shared-index.js → shared/index.js} +92 -123
- package/mcp/dist/{shared-ollama.js → shared/ollama.js} +2 -2
- package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +16 -21
- package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +17 -20
- package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
- package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
- package/mcp/dist/shared.js +4 -59
- package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
- package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
- package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
- package/mcp/dist/{shell-render.js → shell/render.js} +1 -1
- package/mcp/dist/{shell.js → shell/shell.js} +11 -11
- package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
- package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
- package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
- package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
- package/mcp/dist/{skill-registry.js → skill/registry.js} +4 -4
- package/mcp/dist/{skill-state.js → skill/state.js} +1 -1
- package/mcp/dist/startup-embedding.js +2 -2
- package/mcp/dist/status.js +15 -14
- package/mcp/dist/{tasks-github.js → task/github.js} +2 -2
- package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
- package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +7 -7
- package/mcp/dist/telemetry.js +3 -4
- package/mcp/dist/tool-registry.js +29 -17
- package/mcp/dist/tools/config.js +515 -0
- package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
- package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
- package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
- package/mcp/dist/{mcp-finding.js → tools/finding.js} +97 -124
- package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
- package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
- package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
- package/mcp/dist/{mcp-ops.js → tools/ops.js} +169 -71
- package/mcp/dist/{mcp-search.js → tools/search.js} +19 -23
- package/mcp/dist/{mcp-session.js → tools/session.js} +48 -23
- package/mcp/dist/{mcp-skills.js → tools/skills.js} +33 -35
- package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
- package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
- package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
- package/mcp/dist/{memory-ui-page.js → ui/page.js} +4 -6
- package/mcp/dist/{memory-ui-server.js → ui/server.js} +30 -22
- package/mcp/dist/update.js +2 -2
- package/mcp/dist/utils.js +51 -11
- package/package.json +2 -2
- package/scripts/preuninstall.mjs +31 -0
- package/starter/global/CLAUDE.md +3 -2
- package/mcp/dist/mcp-config.js +0 -551
- package/mcp/dist/shared-governance.js +0 -4
- /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
- /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
- /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
- /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
- /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
- /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
- /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
- /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
|
-
import { debugLog, appendAuditLog, phrenOk, phrenErr, PhrenError } from "
|
|
5
|
-
import { normalizeMemoryScope } from "
|
|
6
|
-
import { withFileLock } from "
|
|
7
|
-
import { isValidProjectName, safeProjectPath } from "
|
|
8
|
-
import { getMachineName } from "
|
|
9
|
-
import { buildCitationComment, buildSourceComment, getHeadCommit, getRepoRoot, inferCitationLocation, isFindingProvenanceSource, } from "./
|
|
10
|
-
import { isDuplicateFinding, scanForSecrets, normalizeObservationTags, resolveCoref, detectConflicts, extractDynamicEntities } from "./
|
|
11
|
-
import { validateFindingsFormat, validateFinding } from "./
|
|
12
|
-
import { countActiveFindings, autoArchiveToReference } from "./
|
|
13
|
-
import { resolveAutoFindingTaskItem, resolveFindingTaskReference, resolveFindingSessionId, } from "
|
|
14
|
-
import { buildLifecycleComments, extractFindingType, parseFindingLifecycle, stripLifecycleComments, } from "
|
|
15
|
-
import { METADATA_REGEX, } from "./
|
|
4
|
+
import { debugLog, appendAuditLog, phrenOk, phrenErr, PhrenError } from "../shared.js";
|
|
5
|
+
import { normalizeMemoryScope } from "../shared.js";
|
|
6
|
+
import { withFileLock } from "../shared/governance.js";
|
|
7
|
+
import { isValidProjectName, safeProjectPath } from "../utils.js";
|
|
8
|
+
import { getMachineName } from "../machine-identity.js";
|
|
9
|
+
import { buildCitationComment, buildSourceComment, getHeadCommit, getRepoRoot, inferCitationLocation, isFindingProvenanceSource, } from "./citation.js";
|
|
10
|
+
import { isDuplicateFinding, scanForSecrets, normalizeObservationTags, resolveCoref, detectConflicts, extractDynamicEntities } from "./dedup.js";
|
|
11
|
+
import { validateFindingsFormat, validateFinding } from "./validate.js";
|
|
12
|
+
import { countActiveFindings, autoArchiveToReference } from "./archive.js";
|
|
13
|
+
import { resolveAutoFindingTaskItem, resolveFindingTaskReference, resolveFindingSessionId, } from "../finding/context.js";
|
|
14
|
+
import { buildLifecycleComments, extractFindingType, parseFindingLifecycle, stripLifecycleComments, } from "../finding/lifecycle.js";
|
|
15
|
+
import { METADATA_REGEX, } from "./metadata.js";
|
|
16
16
|
/** Default cap for active findings before auto-archiving is triggered. */
|
|
17
17
|
const DEFAULT_FINDINGS_CAP = 20;
|
|
18
18
|
const LIFECYCLE_ANNOTATION_RE = METADATA_REGEX.lifecycleAnnotation;
|
|
@@ -2,11 +2,11 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
4
|
import { execFileSync } from "child_process";
|
|
5
|
-
import { debugLog, EXEC_TIMEOUT_MS, getProjectDirs } from "
|
|
6
|
-
import { errorMessage } from "
|
|
7
|
-
import { countActiveFindings } from "./
|
|
8
|
-
import { isTaskFileName } from "
|
|
9
|
-
import { METADATA_REGEX } from "./
|
|
5
|
+
import { debugLog, EXEC_TIMEOUT_MS, getProjectDirs } from "../shared.js";
|
|
6
|
+
import { errorMessage } from "../utils.js";
|
|
7
|
+
import { countActiveFindings } from "./archive.js";
|
|
8
|
+
import { isTaskFileName } from "../data/tasks.js";
|
|
9
|
+
import { METADATA_REGEX } from "./metadata.js";
|
|
10
10
|
/** Maximum allowed length for a single finding entry (token budget protection). */
|
|
11
11
|
export const MAX_FINDING_LENGTH = 2000;
|
|
12
12
|
function safeParseDate(s) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { isValidProjectName } from "
|
|
2
|
-
import { addFindingToFile, } from "
|
|
3
|
-
import { removeFinding as removeFindingStore, } from "
|
|
4
|
-
import { MAX_FINDING_LENGTH } from "
|
|
1
|
+
import { isValidProjectName } from "../utils.js";
|
|
2
|
+
import { addFindingToFile, } from "../shared/content.js";
|
|
3
|
+
import { removeFinding as removeFindingStore, } from "../data/access.js";
|
|
4
|
+
import { MAX_FINDING_LENGTH } from "../content/validate.js";
|
|
5
5
|
/**
|
|
6
6
|
* Validate and add a single finding. Shared validation logic used by
|
|
7
7
|
* both CLI `phren add-finding` and MCP `add_finding` tool.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
|
-
import { phrenErr, phrenOk, readRootManifest } from "
|
|
3
|
-
import { bootstrapFromExisting } from "
|
|
4
|
-
import { resolveActiveProfile } from "
|
|
5
|
-
import { TASKS_FILENAME } from "
|
|
2
|
+
import { phrenErr, phrenOk, readRootManifest } from "../shared.js";
|
|
3
|
+
import { bootstrapFromExisting } from "../init/setup.js";
|
|
4
|
+
import { resolveActiveProfile } from "../profile-store.js";
|
|
5
|
+
import { TASKS_FILENAME } from "../data/tasks.js";
|
|
6
6
|
export function addProjectFromPath(phrenPath, targetPath, requestedProfile, ownership) {
|
|
7
7
|
if (!targetPath) {
|
|
8
8
|
return phrenErr("Path is required. Pass the current project directory explicitly to avoid adding the wrong working directory.");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { STOP_WORDS } from "
|
|
2
|
-
import { queryDocRows } from "
|
|
1
|
+
import { STOP_WORDS } from "../utils.js";
|
|
2
|
+
import { queryDocRows } from "../shared/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Keyword overlap fallback for when FTS5 returns no results.
|
|
5
5
|
* Scans all docs (optionally filtered by project/type), scores each by
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
// Barrel module: re-exports task, profile, and shell-state APIs from their
|
|
2
|
+
// dedicated modules (data-tasks.ts, profile-store.ts, shell-state-store.ts)
|
|
3
|
+
// and owns finding/queue logic directly.
|
|
1
4
|
import * as fs from "fs";
|
|
2
5
|
import * as path from "path";
|
|
3
6
|
import * as yaml from "js-yaml";
|
|
4
|
-
import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, isRecord, } from "
|
|
5
|
-
import { normalizeQueueEntryText, } from "
|
|
6
|
-
import { addFindingToFile, } from "
|
|
7
|
-
import { isValidProjectName, queueFilePath, safeProjectPath } from "
|
|
8
|
-
import { parseCitationComment, parseSourceComment, } from "
|
|
9
|
-
import { parseFindingLifecycle, } from "
|
|
10
|
-
import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, normalizeFindingText, } from "
|
|
11
|
-
import { withSafeLock, ensureProject } from "
|
|
12
|
-
export { readTasks, readTasksAcrossProjects, resolveTaskItem, addTask, addTasks, completeTasks, completeTask, removeTask, removeTasks, updateTask, linkTaskIssue, pinTask, unpinTask, workNextTask, tidyDoneTasks, taskMarkdown, appendChildFinding, promoteTask, TASKS_FILENAME, TASK_FILE_ALIASES, canonicalTaskFilePath, resolveTaskFilePath, isTaskFileName, } from "./
|
|
13
|
-
export { addProjectToProfile, listMachines, listProfiles, listProjectCards, removeProjectFromProfile, setMachineProfile, } from "
|
|
14
|
-
export { loadShellState, resetShellState, saveShellState, } from "
|
|
15
|
-
export { getRuntimeHealth as readRuntimeHealth } from "
|
|
7
|
+
import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, isRecord, } from "../shared.js";
|
|
8
|
+
import { normalizeQueueEntryText, } from "../shared/governance.js";
|
|
9
|
+
import { addFindingToFile, } from "../shared/content.js";
|
|
10
|
+
import { isValidProjectName, queueFilePath, safeProjectPath } from "../utils.js";
|
|
11
|
+
import { parseCitationComment, parseSourceComment, } from "../content/citation.js";
|
|
12
|
+
import { parseFindingLifecycle, } from "../finding/lifecycle.js";
|
|
13
|
+
import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, normalizeFindingText, } from "../content/metadata.js";
|
|
14
|
+
import { withSafeLock, ensureProject } from "../shared/data-utils.js";
|
|
15
|
+
export { readTasks, readTasksAcrossProjects, resolveTaskItem, addTask, addTasks, completeTasks, completeTask, removeTask, removeTasks, updateTask, linkTaskIssue, pinTask, unpinTask, workNextTask, tidyDoneTasks, taskMarkdown, appendChildFinding, promoteTask, TASKS_FILENAME, TASK_FILE_ALIASES, canonicalTaskFilePath, resolveTaskFilePath, isTaskFileName, } from "./tasks.js";
|
|
16
|
+
export { addProjectToProfile, listMachines, listProfiles, listProjectCards, removeProjectFromProfile, setMachineProfile, } from "../profile-store.js";
|
|
17
|
+
export { loadShellState, resetShellState, saveShellState, } from "../shell/state-store.js";
|
|
18
|
+
export { getRuntimeHealth as readRuntimeHealth } from "../shared/governance.js";
|
|
16
19
|
function extractDateHeading(line) {
|
|
17
20
|
const heading = line.match(/^##\s+(.+)$/);
|
|
18
21
|
if (!heading)
|
|
@@ -113,7 +116,6 @@ export function readFindings(phrenPath, project, opts = {}) {
|
|
|
113
116
|
const includeArchived = opts.includeArchived ?? false;
|
|
114
117
|
for (let i = 0; i < lines.length; i++) {
|
|
115
118
|
const line = lines[i];
|
|
116
|
-
const trimmed = line.trim();
|
|
117
119
|
const archiveStartMatch = isArchiveStart(line);
|
|
118
120
|
const archiveEnd = isArchiveEnd(line);
|
|
119
121
|
if (archiveStartMatch) {
|
|
@@ -277,6 +279,55 @@ export function removeFinding(phrenPath, project, match) {
|
|
|
277
279
|
return phrenOk(`Removed from ${project}: ${matched}`);
|
|
278
280
|
});
|
|
279
281
|
}
|
|
282
|
+
export function removeFindings(phrenPath, project, matches) {
|
|
283
|
+
const ensured = ensureProject(phrenPath, project);
|
|
284
|
+
if (!ensured.ok)
|
|
285
|
+
return forwardErr(ensured);
|
|
286
|
+
const findingsPath = path.resolve(path.join(ensured.data, 'FINDINGS.md'));
|
|
287
|
+
if (!findingsPath.startsWith(phrenPath + path.sep) && findingsPath !== phrenPath) {
|
|
288
|
+
return phrenErr(`FINDINGS.md path escapes phren store`, PhrenError.VALIDATION_ERROR);
|
|
289
|
+
}
|
|
290
|
+
if (!fs.existsSync(findingsPath))
|
|
291
|
+
return phrenErr(`No FINDINGS.md file found for "${project}". Add a finding first with add_finding or :find add.`, PhrenError.FILE_NOT_FOUND);
|
|
292
|
+
return withSafeLock(findingsPath, () => {
|
|
293
|
+
const lines = fs.readFileSync(findingsPath, "utf8").split("\n");
|
|
294
|
+
const removed = [];
|
|
295
|
+
const errors = [];
|
|
296
|
+
const bulletLines = collectFindingBulletLines(lines);
|
|
297
|
+
const activeBullets = bulletLines.filter(({ archived }) => !archived);
|
|
298
|
+
const archivedBullets = bulletLines.filter(({ archived }) => archived);
|
|
299
|
+
// Collect indices to remove (with citation lines) in one pass over matches
|
|
300
|
+
const indicesToRemove = new Set();
|
|
301
|
+
for (const match of matches) {
|
|
302
|
+
const needle = normalizeFindingText(match);
|
|
303
|
+
const activeMatch = findMatchingFindingBullet(activeBullets.filter(({ i }) => !indicesToRemove.has(i)), needle, match);
|
|
304
|
+
if (activeMatch.kind === "ambiguous") {
|
|
305
|
+
errors.push(match);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (activeMatch.kind === "not_found") {
|
|
309
|
+
const archivedMatch = findMatchingFindingBullet(archivedBullets, needle, match);
|
|
310
|
+
if (archivedMatch.kind === "ambiguous" || archivedMatch.kind === "found") {
|
|
311
|
+
errors.push(match);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
errors.push(match);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const idx = activeMatch.idx;
|
|
318
|
+
indicesToRemove.add(idx);
|
|
319
|
+
if (isCitationLine(lines[idx + 1] || ""))
|
|
320
|
+
indicesToRemove.add(idx + 1);
|
|
321
|
+
removed.push(lines[idx]);
|
|
322
|
+
}
|
|
323
|
+
if (removed.length > 0) {
|
|
324
|
+
const filtered = lines.filter((_, i) => !indicesToRemove.has(i));
|
|
325
|
+
const normalized = filtered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
326
|
+
fs.writeFileSync(findingsPath, normalized);
|
|
327
|
+
}
|
|
328
|
+
return phrenOk({ removed, errors });
|
|
329
|
+
});
|
|
330
|
+
}
|
|
280
331
|
export function editFinding(phrenPath, project, oldText, newText) {
|
|
281
332
|
const ensured = ensureProject(phrenPath, project);
|
|
282
333
|
if (!ensured.ok)
|
|
@@ -384,6 +435,73 @@ export function readReviewQueue(phrenPath, project) {
|
|
|
384
435
|
}
|
|
385
436
|
return phrenOk(items);
|
|
386
437
|
}
|
|
438
|
+
/** Locate a queue line and apply a mutation within a file lock. */
|
|
439
|
+
function withQueueLineOp(phrenPath, project, lineText, op) {
|
|
440
|
+
const ensured = ensureProject(phrenPath, project);
|
|
441
|
+
if (!ensured.ok)
|
|
442
|
+
return forwardErr(ensured);
|
|
443
|
+
const file = queuePath(phrenPath, project);
|
|
444
|
+
if (!fs.existsSync(file))
|
|
445
|
+
return phrenErr(`No review queue found for "${project}".`, PhrenError.FILE_NOT_FOUND);
|
|
446
|
+
return withSafeLock(file, () => {
|
|
447
|
+
const lines = fs.readFileSync(file, "utf8").split("\n");
|
|
448
|
+
const idx = lines.findIndex((l) => l.trim() === lineText.trim());
|
|
449
|
+
if (idx === -1)
|
|
450
|
+
return phrenErr(`Queue item not found in "${project}".`, PhrenError.NOT_FOUND);
|
|
451
|
+
return op(lines, idx, file);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
function writeQueueLines(file, lines) {
|
|
455
|
+
fs.writeFileSync(file, lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n");
|
|
456
|
+
}
|
|
457
|
+
/** Remove a queue item's line from review.md (finding stays in FINDINGS.md). */
|
|
458
|
+
export function approveQueueItem(phrenPath, project, lineText) {
|
|
459
|
+
return withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
|
|
460
|
+
lines.splice(idx, 1);
|
|
461
|
+
writeQueueLines(file, lines);
|
|
462
|
+
return phrenOk(`Approved queue item in ${project}`);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
/** Remove a queue item from review.md AND remove the corresponding finding from FINDINGS.md. */
|
|
466
|
+
export function rejectQueueItem(phrenPath, project, lineText) {
|
|
467
|
+
const lockResult = withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
|
|
468
|
+
lines.splice(idx, 1);
|
|
469
|
+
writeQueueLines(file, lines);
|
|
470
|
+
return phrenOk("ok");
|
|
471
|
+
});
|
|
472
|
+
if (!lockResult.ok)
|
|
473
|
+
return lockResult;
|
|
474
|
+
const parsed = parseQueueLine(lineText);
|
|
475
|
+
if (parsed.text) {
|
|
476
|
+
const removeResult = removeFinding(phrenPath, project, parsed.text);
|
|
477
|
+
if (!removeResult.ok) {
|
|
478
|
+
return phrenOk(`Rejected queue item from ${project} (note: finding not found in FINDINGS.md — may have already been removed)`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return phrenOk(`Rejected and removed queue item from ${project}`);
|
|
482
|
+
}
|
|
483
|
+
/** Edit a queue item's text in review.md and the corresponding finding in FINDINGS.md. */
|
|
484
|
+
export function editQueueItem(phrenPath, project, lineText, newText) {
|
|
485
|
+
const trimmed = newText.replace(/[\r\n]+/g, " ").trim();
|
|
486
|
+
if (!trimmed)
|
|
487
|
+
return phrenErr("New text cannot be empty.", PhrenError.EMPTY_INPUT);
|
|
488
|
+
const parsed = parseQueueLine(lineText);
|
|
489
|
+
const lockResult = withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
|
|
490
|
+
const dateMatch = lines[idx].match(/^- \[(\d{4}-\d{2}-\d{2})\]\s*/);
|
|
491
|
+
lines[idx] = dateMatch ? `- [${dateMatch[1]}] ${trimmed}` : `- ${trimmed}`;
|
|
492
|
+
writeQueueLines(file, lines);
|
|
493
|
+
return phrenOk("ok");
|
|
494
|
+
});
|
|
495
|
+
if (!lockResult.ok)
|
|
496
|
+
return lockResult;
|
|
497
|
+
if (parsed.text) {
|
|
498
|
+
const editResult = editFinding(phrenPath, project, parsed.text, trimmed);
|
|
499
|
+
if (!editResult.ok) {
|
|
500
|
+
return phrenOk(`Updated queue item in ${project} (note: corresponding finding not found in FINDINGS.md)`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return phrenOk(`Updated queue item in ${project}`);
|
|
504
|
+
}
|
|
387
505
|
export function readReviewQueueAcrossProjects(phrenPath, profile) {
|
|
388
506
|
const validation = validateAggregateQueueProfile(phrenPath, profile);
|
|
389
507
|
if (!validation.ok)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { randomBytes, randomUUID } from "crypto";
|
|
4
|
-
import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, } from "
|
|
5
|
-
import { validateTaskFormat } from "
|
|
6
|
-
import { safeProjectPath } from "
|
|
7
|
-
import { withSafeLock, ensureProject } from "
|
|
4
|
+
import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, } from "../shared.js";
|
|
5
|
+
import { validateTaskFormat } from "../shared/content.js";
|
|
6
|
+
import { safeProjectPath } from "../utils.js";
|
|
7
|
+
import { withSafeLock, ensureProject } from "../shared/data-utils.js";
|
|
8
8
|
const ACTIVE_HEADINGS = new Set(["active", "in progress", "in-progress", "current", "wip"]);
|
|
9
9
|
const QUEUE_HEADINGS = new Set(["queue", "queued", "task", "todo", "upcoming", "next"]);
|
|
10
10
|
const DONE_HEADINGS = new Set(["done", "completed", "finished", "archived"]);
|
|
@@ -408,7 +408,7 @@ export function addTask(phrenPath, project, item, opts) {
|
|
|
408
408
|
return phrenOk(newItem);
|
|
409
409
|
});
|
|
410
410
|
}
|
|
411
|
-
export function addTasks(phrenPath, project, items) {
|
|
411
|
+
export function addTasks(phrenPath, project, items, opts) {
|
|
412
412
|
const bPath = canonicalTaskFilePath(phrenPath, project);
|
|
413
413
|
if (!bPath)
|
|
414
414
|
return phrenErr(`Project name "${project}" is not valid.`, PhrenError.INVALID_PROJECT_NAME);
|
|
@@ -429,10 +429,12 @@ export function addTasks(phrenPath, project, items) {
|
|
|
429
429
|
}
|
|
430
430
|
parsed.data.items.Queue.push({
|
|
431
431
|
id: `Q${parsed.data.items.Queue.length + 1}`,
|
|
432
|
+
stableId: newBid(),
|
|
432
433
|
section: "Queue",
|
|
433
434
|
line,
|
|
434
435
|
checked: false,
|
|
435
436
|
priority: normalizePriority(line),
|
|
437
|
+
scope: opts?.scope,
|
|
436
438
|
});
|
|
437
439
|
added.push(line);
|
|
438
440
|
}
|
package/mcp/dist/embedding.js
CHANGED
|
@@ -2,9 +2,10 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
4
|
import { debugLog, runtimeDir, } from "./shared.js";
|
|
5
|
-
import { withFileLock } from "./shared
|
|
5
|
+
import { withFileLock } from "./shared/governance.js";
|
|
6
6
|
import { errorMessage } from "./utils.js";
|
|
7
|
-
import { bootstrapSqlJs } from "./shared
|
|
7
|
+
import { bootstrapSqlJs } from "./shared/sqljs.js";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
8
9
|
let sqlJsLoader = bootstrapSqlJs;
|
|
9
10
|
const EMBED_CACHE_DB = "embed-cache.db";
|
|
10
11
|
function getCacheDbPath(phrenPath) {
|
|
@@ -79,8 +80,7 @@ async function openCacheDb(phrenPath) {
|
|
|
79
80
|
db?.close();
|
|
80
81
|
}
|
|
81
82
|
catch (e2) {
|
|
82
|
-
|
|
83
|
-
process.stderr.write(`[phren] embedding openCacheDb dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
83
|
+
logger.debug("embedding", `embedding openCacheDb dbClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
84
84
|
}
|
|
85
85
|
throw err;
|
|
86
86
|
}
|
|
@@ -126,14 +126,12 @@ function persistDb(phrenPath, db) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
catch (err) {
|
|
129
|
-
|
|
130
|
-
process.stderr.write(`[phren] embedding persistDb onDiskLoad: ${errorMessage(err)}\n`);
|
|
129
|
+
logger.debug("embedding", `embedding persistDb onDiskLoad: ${errorMessage(err)}`);
|
|
131
130
|
try {
|
|
132
131
|
onDisk?.close();
|
|
133
132
|
}
|
|
134
133
|
catch (e2) {
|
|
135
|
-
|
|
136
|
-
process.stderr.write(`[phren] embedding persistDb onDiskClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
134
|
+
logger.debug("embedding", `embedding persistDb onDiskClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
137
135
|
}
|
|
138
136
|
onDisk = null;
|
|
139
137
|
}
|
|
@@ -149,8 +147,7 @@ function persistDb(phrenPath, db) {
|
|
|
149
147
|
onDisk.close();
|
|
150
148
|
}
|
|
151
149
|
catch (e2) {
|
|
152
|
-
|
|
153
|
-
process.stderr.write(`[phren] embedding persistDb onDiskCloseFinally: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
150
|
+
logger.debug("embedding", `embedding persistDb onDiskCloseFinally: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
154
151
|
}
|
|
155
152
|
}
|
|
156
153
|
});
|
|
@@ -269,8 +266,7 @@ export async function getCachedEmbedding(phrenPath, text, apiKey, model) {
|
|
|
269
266
|
db?.close();
|
|
270
267
|
}
|
|
271
268
|
catch (e2) {
|
|
272
|
-
|
|
273
|
-
process.stderr.write(`[phren] embedding getCachedEmbedding dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
269
|
+
logger.debug("embedding", `embedding getCachedEmbedding dbClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
274
270
|
}
|
|
275
271
|
}
|
|
276
272
|
}
|
|
@@ -320,8 +316,7 @@ export async function getCachedEmbeddings(phrenPath, texts, apiKey, model) {
|
|
|
320
316
|
db?.close();
|
|
321
317
|
}
|
|
322
318
|
catch (e2) {
|
|
323
|
-
|
|
324
|
-
process.stderr.write(`[phren] embedding getCachedEmbeddings dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
319
|
+
logger.debug("embedding", `embedding getCachedEmbeddings dbClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
325
320
|
}
|
|
326
321
|
}
|
|
327
322
|
}
|
package/mcp/dist/entrypoint.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { parseMcpMode, runInit } from "./init.js";
|
|
3
|
+
import { parseMcpMode, runInit } from "./init/init.js";
|
|
4
4
|
import { errorMessage } from "./utils.js";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
5
6
|
import { defaultPhrenPath, findPhrenPath } from "./shared.js";
|
|
6
|
-
import { addProjectFromPath } from "./core
|
|
7
|
+
import { addProjectFromPath } from "./core/project.js";
|
|
7
8
|
import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, parseProjectOwnershipMode, } from "./project-config.js";
|
|
8
9
|
const HELP_TEXT = `phren - persistent knowledge for your agents
|
|
9
10
|
|
|
@@ -344,7 +345,7 @@ export async function runTopLevelCommand(argv) {
|
|
|
344
345
|
return finish();
|
|
345
346
|
}
|
|
346
347
|
if (argvCommand === "uninstall") {
|
|
347
|
-
const { runUninstall } = await import("./init.js");
|
|
348
|
+
const { runUninstall } = await import("./init/init.js");
|
|
348
349
|
const skipConfirm = argv.includes("--yes") || argv.includes("-y");
|
|
349
350
|
await runUninstall({ yes: skipConfirm });
|
|
350
351
|
return finish();
|
|
@@ -355,8 +356,8 @@ export async function runTopLevelCommand(argv) {
|
|
|
355
356
|
return finish();
|
|
356
357
|
}
|
|
357
358
|
if (argvCommand === "verify") {
|
|
358
|
-
const { runPostInitVerify, getVerifyOutcomeNote } = await import("./init.js");
|
|
359
|
-
const { getWorkflowPolicy } = await import("./shared
|
|
359
|
+
const { runPostInitVerify, getVerifyOutcomeNote } = await import("./init/init.js");
|
|
360
|
+
const { getWorkflowPolicy } = await import("./shared/governance.js");
|
|
360
361
|
const phrenPath = findPhrenPath() || defaultPhrenPath();
|
|
361
362
|
const result = runPostInitVerify(phrenPath);
|
|
362
363
|
console.log(`phren verify: ${result.ok ? "ok" : "issues found"}`);
|
|
@@ -376,7 +377,7 @@ export async function runTopLevelCommand(argv) {
|
|
|
376
377
|
return finish(result.ok ? 0 : 1);
|
|
377
378
|
}
|
|
378
379
|
if (argvCommand === "mcp-mode") {
|
|
379
|
-
const { runMcpMode } = await import("./init.js");
|
|
380
|
+
const { runMcpMode } = await import("./init/init.js");
|
|
380
381
|
try {
|
|
381
382
|
await runMcpMode(argv[1]);
|
|
382
383
|
return finish();
|
|
@@ -387,7 +388,7 @@ export async function runTopLevelCommand(argv) {
|
|
|
387
388
|
}
|
|
388
389
|
}
|
|
389
390
|
if (argvCommand === "hooks-mode") {
|
|
390
|
-
const { runHooksMode } = await import("./init.js");
|
|
391
|
+
const { runHooksMode } = await import("./init/init.js");
|
|
391
392
|
try {
|
|
392
393
|
await runHooksMode(argv[1]);
|
|
393
394
|
return finish();
|
|
@@ -405,19 +406,18 @@ export async function runTopLevelCommand(argv) {
|
|
|
405
406
|
return finish();
|
|
406
407
|
}
|
|
407
408
|
if (!argvCommand && process.stdin.isTTY && process.stdout.isTTY) {
|
|
408
|
-
const { runCliCommand } = await import("./cli.js");
|
|
409
|
+
const { runCliCommand } = await import("./cli/cli.js");
|
|
409
410
|
await runCliCommand("shell", []);
|
|
410
411
|
return finish();
|
|
411
412
|
}
|
|
412
413
|
if (argvCommand && CLI_COMMANDS.includes(argvCommand)) {
|
|
413
|
-
const { runCliCommand } = await import("./cli.js");
|
|
414
|
+
const { runCliCommand } = await import("./cli/cli.js");
|
|
414
415
|
try {
|
|
415
416
|
const { trackCliCommand } = await import("./telemetry.js");
|
|
416
417
|
trackCliCommand(defaultPhrenPath(), argvCommand);
|
|
417
418
|
}
|
|
418
419
|
catch (err) {
|
|
419
|
-
|
|
420
|
-
process.stderr.write(`[phren] cli trackCliCommand: ${errorMessage(err)}\n`);
|
|
420
|
+
logger.debug("cli", `trackCliCommand: ${errorMessage(err)}`);
|
|
421
421
|
}
|
|
422
422
|
await runCliCommand(argvCommand, argv.slice(1));
|
|
423
423
|
return finish();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { safeProjectPath } from "
|
|
4
|
-
import { resolveTaskFilePath } from "
|
|
3
|
+
import { safeProjectPath } from "../utils.js";
|
|
4
|
+
import { resolveTaskFilePath } from "../data/tasks.js";
|
|
5
5
|
const ACTIVE_HEADINGS = new Set(["active", "in progress", "in-progress", "current", "wip"]);
|
|
6
6
|
const QUEUE_HEADINGS = new Set(["queue", "queued", "task", "todo", "upcoming", "next"]);
|
|
7
7
|
const DONE_HEADINGS = new Set(["done", "completed", "finished", "archived"]);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as crypto from "crypto";
|
|
2
2
|
import * as fs from "fs";
|
|
3
|
-
import { impactLogFile } from "
|
|
4
|
-
import { withFileLock } from "
|
|
5
|
-
import { normalizeFindingText } from "
|
|
3
|
+
import { impactLogFile } from "../shared.js";
|
|
4
|
+
import { withFileLock } from "../shared/governance.js";
|
|
5
|
+
import { normalizeFindingText } from "../content/metadata.js";
|
|
6
6
|
let highImpactCache = null;
|
|
7
7
|
function nowIso() {
|
|
8
8
|
return new Date().toISOString();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
|
-
import { runtimeDir, phrenOk, phrenErr, PhrenError } from "
|
|
5
|
-
import { withFileLock } from "
|
|
6
|
-
import { addFindingToFile } from "
|
|
7
|
-
import { isValidProjectName, errorMessage } from "
|
|
4
|
+
import { runtimeDir, phrenOk, phrenErr, PhrenError } from "../shared.js";
|
|
5
|
+
import { withFileLock } from "../shared/governance.js";
|
|
6
|
+
import { addFindingToFile } from "../shared/content.js";
|
|
7
|
+
import { isValidProjectName, errorMessage } from "../utils.js";
|
|
8
8
|
function journalRoot(phrenPath) {
|
|
9
9
|
const dir = path.join(runtimeDir(phrenPath), "finding-journal");
|
|
10
10
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { PhrenError, phrenErr, phrenOk } from "
|
|
3
|
+
import { PhrenError, phrenErr, phrenOk } from "../phren-core.js";
|
|
4
4
|
// Phren lifecycle comment prefix. No backward compat.
|
|
5
5
|
const LIFECYCLE_PREFIX = "phren";
|
|
6
|
-
import { withFileLock } from "
|
|
7
|
-
import { isValidProjectName, safeProjectPath } from "
|
|
8
|
-
import { isArchiveEnd, isArchiveStart, parseCreatedDate as parseCreatedDateMeta, parseStatusField, parseStatus, parseSupersession, parseContradiction, parseFindingId as parseFindingIdMeta, stripLifecycleMetadata, stripRelationMetadata, normalizeFindingText, } from "
|
|
6
|
+
import { withFileLock } from "../shared/governance.js";
|
|
7
|
+
import { isValidProjectName, safeProjectPath } from "../utils.js";
|
|
8
|
+
import { isArchiveEnd, isArchiveStart, parseCreatedDate as parseCreatedDateMeta, parseStatusField, parseStatus, parseSupersession, parseContradiction, parseFindingId as parseFindingIdMeta, stripLifecycleMetadata, stripRelationMetadata, normalizeFindingText, } from "../content/metadata.js";
|
|
9
9
|
export const FINDING_TYPE_DECAY = {
|
|
10
10
|
'pattern': { maxAgeDays: 365, decayMultiplier: 1.0 }, // Slow decay, long-lived
|
|
11
11
|
'decision': { maxAgeDays: Infinity, decayMultiplier: 1.0 }, // Never decays
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { debugLog } from "
|
|
4
|
-
import { errorMessage } from "
|
|
3
|
+
import { debugLog } from "../shared.js";
|
|
4
|
+
import { errorMessage } from "../utils.js";
|
|
5
5
|
export function recordRetrieval(phrenPath, file, section) {
|
|
6
6
|
const dir = path.join(phrenPath, ".runtime");
|
|
7
7
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { debugLog } from "
|
|
4
|
-
import { errorMessage } from "
|
|
3
|
+
import { debugLog } from "../shared.js";
|
|
4
|
+
import { errorMessage } from "../utils.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
5
6
|
// Acquire the file lock, returning true on success or throwing on timeout.
|
|
6
7
|
function acquireFileLock(lockPath) {
|
|
7
8
|
const maxWait = Number.parseInt(process.env.PHREN_FILE_LOCK_MAX_WAIT_MS || "5000", 10) || 5000;
|
|
@@ -19,8 +20,7 @@ function acquireFileLock(lockPath) {
|
|
|
19
20
|
break;
|
|
20
21
|
}
|
|
21
22
|
catch (err) {
|
|
22
|
-
|
|
23
|
-
process.stderr.write(`[phren] acquireFileLock lockWrite: ${errorMessage(err)}\n`);
|
|
23
|
+
logger.debug("acquireFileLock", `lockWrite: ${errorMessage(err)}`);
|
|
24
24
|
try {
|
|
25
25
|
const stat = fs.statSync(lockPath);
|
|
26
26
|
if (Date.now() - stat.mtimeMs > staleThreshold) {
|
|
@@ -40,7 +40,14 @@ function acquireFileLock(lockPath) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
|
-
|
|
43
|
+
try {
|
|
44
|
+
const result = require('child_process').spawnSync('tasklist', ['/FI', `PID eq ${lockPid}`, '/NH'], { encoding: 'utf8', timeout: 2000 });
|
|
45
|
+
if (result.stdout && result.stdout.includes(String(lockPid)))
|
|
46
|
+
ownerDead = false;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
ownerDead = true;
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -54,8 +61,7 @@ function acquireFileLock(lockPath) {
|
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
catch (statErr) {
|
|
57
|
-
|
|
58
|
-
process.stderr.write(`[phren] acquireFileLock staleStat: ${statErr instanceof Error ? statErr.message : String(statErr)}\n`);
|
|
64
|
+
logger.debug("acquireFileLock", `staleStat: ${statErr instanceof Error ? statErr.message : String(statErr)}`);
|
|
59
65
|
sleep(pollInterval);
|
|
60
66
|
waited += pollInterval;
|
|
61
67
|
continue;
|
|
@@ -75,8 +81,7 @@ function releaseFileLock(lockPath) {
|
|
|
75
81
|
fs.unlinkSync(lockPath);
|
|
76
82
|
}
|
|
77
83
|
catch (err) {
|
|
78
|
-
|
|
79
|
-
process.stderr.write(`[phren] releaseFileLock: ${errorMessage(err)}\n`);
|
|
84
|
+
logger.debug("releaseFileLock", `${errorMessage(err)}`);
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
// Q10: withFileLock now accepts both sync and async callbacks.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as crypto from "crypto";
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import { appendAuditLog, debugLog, getProjectDirs, isRecord, runtimeHealthFile, withDefaults, phrenErr, PhrenError, phrenOk, resolveFindingsPath } from "
|
|
5
|
-
import { withFileLock, isFiniteNumber, hasValidSchemaVersion } from "
|
|
6
|
-
import { errorMessage, isValidProjectName, safeProjectPath } from "
|
|
7
|
-
import { readProjectConfig } from "
|
|
8
|
-
import { getActiveProfileDefaults } from "
|
|
9
|
-
import { runCustomHooks } from "
|
|
10
|
-
import { METADATA_REGEX, isCitationLine, isArchiveStart as isArchiveStartMeta, isArchiveEnd as isArchiveEndMeta, stripLifecycleMetadata as stripLifecycleMetadataMeta, } from "
|
|
4
|
+
import { appendAuditLog, debugLog, getProjectDirs, isRecord, runtimeHealthFile, withDefaults, phrenErr, PhrenError, phrenOk, resolveFindingsPath } from "../shared.js";
|
|
5
|
+
import { withFileLock, isFiniteNumber, hasValidSchemaVersion } from "../shared/governance.js";
|
|
6
|
+
import { errorMessage, isValidProjectName, safeProjectPath } from "../utils.js";
|
|
7
|
+
import { readProjectConfig } from "../project-config.js";
|
|
8
|
+
import { getActiveProfileDefaults } from "../profile-store.js";
|
|
9
|
+
import { runCustomHooks } from "../hooks.js";
|
|
10
|
+
import { METADATA_REGEX, isCitationLine, isArchiveStart as isArchiveStartMeta, isArchiveEnd as isArchiveEndMeta, stripLifecycleMetadata as stripLifecycleMetadataMeta, } from "../content/metadata.js";
|
|
11
11
|
export const MAX_QUEUE_ENTRY_LENGTH = 500;
|
|
12
12
|
export const GOVERNANCE_SCHEMA_VERSION = 1;
|
|
13
13
|
const DEFAULT_POLICY = {
|
|
@@ -641,7 +641,7 @@ export function pruneDeadMemories(phrenPath, project, dryRun) {
|
|
|
641
641
|
const file = resolveFindingsPath(dir);
|
|
642
642
|
if (!file)
|
|
643
643
|
continue;
|
|
644
|
-
// Q23:
|
|
644
|
+
// Q23: see docs/decisions/Q23-per-file-lock-concurrent-writers.md
|
|
645
645
|
withFileLock(file, () => {
|
|
646
646
|
const lines = fs.readFileSync(file, "utf8").split("\n");
|
|
647
647
|
let currentDate = null;
|
|
@@ -723,13 +723,11 @@ export function consolidateProjectFindings(phrenPath, project, dryRun) {
|
|
|
723
723
|
const file = resolveFindingsPath(path.join(phrenPath, project));
|
|
724
724
|
if (!file)
|
|
725
725
|
return phrenErr(`No FINDINGS.md found for "${project}".`, PhrenError.FILE_NOT_FOUND);
|
|
726
|
-
// Q23:
|
|
726
|
+
// Q23: see docs/decisions/Q23-per-file-lock-concurrent-writers.md
|
|
727
727
|
const result = withFileLock(file, () => {
|
|
728
728
|
const raw = fs.readFileSync(file, "utf8");
|
|
729
729
|
const lines = raw.split("\n");
|
|
730
|
-
// Q12:
|
|
731
|
-
// Archive blocks (<!-- phren:archive:start/end --> and <details>...</details>) are
|
|
732
|
-
// collected verbatim and appended unchanged after the consolidated active section.
|
|
730
|
+
// Q12: see docs/decisions/Q12-active-vs-archive-separation.md
|
|
733
731
|
const archiveBlocks = [];
|
|
734
732
|
const activeLines = [];
|
|
735
733
|
let inArchive = false;
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import * as fs from "fs";
|
|
20
20
|
import * as path from "path";
|
|
21
|
-
import { debugLog } from "
|
|
22
|
-
import { errorMessage } from "
|
|
23
|
-
import { readProjectConfig } from "
|
|
21
|
+
import { debugLog } from "../shared.js";
|
|
22
|
+
import { errorMessage } from "../utils.js";
|
|
23
|
+
import { readProjectConfig } from "../project-config.js";
|
|
24
24
|
function configDir(phrenPath) {
|
|
25
25
|
return path.join(phrenPath, ".config");
|
|
26
26
|
}
|