memory-journal-mcp 7.4.0 → 7.5.0
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/README.md +51 -31
- package/dist/{chunk-P5V2VY6N.js → chunk-VHA46GLM.js} +262 -18
- package/dist/{chunk-5ZA77VUW.js → chunk-XNOUTCRV.js} +244 -105
- package/dist/cli.js +11 -3
- package/dist/index.d.ts +8 -2
- package/dist/index.js +2 -2
- package/dist/{tools-WZUENKJ6.js → tools-HTE4YXMW.js} +1 -1
- package/package.json +2 -1
- package/skills/README.md +2 -0
- package/skills/github-commander/SKILL.md +1 -0
- package/skills/github-commander/workflows/copilot-audit.md +48 -0
- package/skills/github-copilot-cli/SKILL.md +64 -0
- package/skills/package.json +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { withSessionInit, withPriority, ASSISTANT_FOCUSED, TOOL_GROUPS, HIGH_PRIORITY, LOW_PRIORITY, MEDIUM_PRIORITY, setDefaultSandboxMode, initializeAuditLogger, parseToolFilter, getFilterSummary, getToolFilterFromEnv, getTools, getEnabledGroups, callTool, getGlobalAuditLogger, sendProgress, SUPPORTED_SCOPES, getRequiredScope, hasScope, getAuditResourceDef, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, parseScopes, BASE_SCOPES, getAllToolNames, globalMetrics, DEFAULT_BRIEFING_CONFIG } from './chunk-
|
|
1
|
+
import { withSessionInit, withPriority, ASSISTANT_FOCUSED, TOOL_GROUPS, HIGH_PRIORITY, LOW_PRIORITY, MEDIUM_PRIORITY, setDefaultSandboxMode, initializeAuditLogger, parseToolFilter, getFilterSummary, getToolFilterFromEnv, getTools, getEnabledGroups, callTool, getGlobalAuditLogger, sendProgress, SUPPORTED_SCOPES, getRequiredScope, hasScope, getAuditResourceDef, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, DEFAULT_FLAG_VOCABULARY, parseScopes, BASE_SCOPES, getAllToolNames, globalMetrics, DEFAULT_BRIEFING_CONFIG } from './chunk-VHA46GLM.js';
|
|
2
2
|
import { logger, GitHubIntegration, ConfigurationError, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, MemoryJournalMcpError, validateDateFormatPattern } from './chunk-WXDEVIFL.js';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -492,16 +492,14 @@ function rowsToEntries(tagsMgr, rows) {
|
|
|
492
492
|
if (rows.length === 0) return [];
|
|
493
493
|
const entries = rows.map((r) => {
|
|
494
494
|
const p = r;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
...rest,
|
|
495
|
+
return {
|
|
496
|
+
...p,
|
|
498
497
|
isPersonal: Boolean(p.isPersonal),
|
|
499
498
|
// SQLite uses 0/1
|
|
500
499
|
tags: [],
|
|
501
500
|
// Attach importanceScore if the query computed it (importance-sorted results)
|
|
502
|
-
...importanceScore !== void 0 ? { importanceScore: Math.round(importanceScore * 100) / 100 } : {}
|
|
501
|
+
...p.importanceScore !== void 0 ? { importanceScore: Math.round(p.importanceScore * 100) / 100 } : {}
|
|
503
502
|
};
|
|
504
|
-
return entry;
|
|
505
503
|
});
|
|
506
504
|
const ids = entries.map((e) => e.id);
|
|
507
505
|
const tagsMap = tagsMgr.batchGetTagsForEntries(ids);
|
|
@@ -546,7 +544,7 @@ function createEntry(context, input) {
|
|
|
546
544
|
timestamp,
|
|
547
545
|
input.isPersonal ?? true ? 1 : 0,
|
|
548
546
|
input.significanceType || null,
|
|
549
|
-
input.autoContext
|
|
547
|
+
input.autoContext ?? null,
|
|
550
548
|
input.projectNumber ?? null,
|
|
551
549
|
input.projectOwner || null,
|
|
552
550
|
input.issueNumber ?? null,
|
|
@@ -646,7 +644,7 @@ function updateEntry(context, id, input) {
|
|
|
646
644
|
}
|
|
647
645
|
if (input.autoContext !== void 0) {
|
|
648
646
|
updates.push("auto_context = ?");
|
|
649
|
-
values.push(input.autoContext
|
|
647
|
+
values.push(input.autoContext ?? null);
|
|
650
648
|
}
|
|
651
649
|
for (const key of [
|
|
652
650
|
"projectNumber",
|
|
@@ -968,15 +966,6 @@ function searchByDateRange(context, startDate, endDate, options) {
|
|
|
968
966
|
return rowsToEntries(tagsMgr, rows);
|
|
969
967
|
}
|
|
970
968
|
function sanitizeFtsQuery(query) {
|
|
971
|
-
const trimmed = query.trim();
|
|
972
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length > 2 && !trimmed.slice(1, -1).includes('"')) {
|
|
973
|
-
const inner = trimmed.slice(1, -1).trim();
|
|
974
|
-
const words = inner.split(/\s+/).filter(Boolean);
|
|
975
|
-
if (words.length > 1) {
|
|
976
|
-
return words.join(" AND ");
|
|
977
|
-
}
|
|
978
|
-
return inner;
|
|
979
|
-
}
|
|
980
969
|
return query;
|
|
981
970
|
}
|
|
982
971
|
|
|
@@ -1037,15 +1026,16 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
|
|
|
1037
1026
|
period: r.period,
|
|
1038
1027
|
significantCount: r.significant_count
|
|
1039
1028
|
}));
|
|
1040
|
-
const relCountRow = db.prepare(
|
|
1029
|
+
const relCountRow = db.prepare(`SELECT COUNT(*) as count FROM relationships r JOIN memory_journal m ON r.from_entry_id = m.id WHERE m.deleted_at IS NULL${dateFilter}`).get(...dateParams);
|
|
1041
1030
|
const relTypeRows = db.prepare(
|
|
1042
1031
|
`
|
|
1043
|
-
SELECT relationship_type, COUNT(*) as count
|
|
1044
|
-
FROM relationships
|
|
1045
|
-
|
|
1046
|
-
|
|
1032
|
+
SELECT r.relationship_type, COUNT(*) as count
|
|
1033
|
+
FROM relationships r
|
|
1034
|
+
JOIN memory_journal m ON r.from_entry_id = m.id
|
|
1035
|
+
WHERE m.deleted_at IS NULL AND r.relationship_type IN ('blocked_by', 'resolved', 'caused')${dateFilter}
|
|
1036
|
+
GROUP BY r.relationship_type
|
|
1047
1037
|
`
|
|
1048
|
-
).all();
|
|
1038
|
+
).all(...dateParams);
|
|
1049
1039
|
const totalRelationships = relCountRow.count;
|
|
1050
1040
|
const avgPerEntry = totalEntries > 0 ? totalRelationships / totalEntries : 0;
|
|
1051
1041
|
const currentPeriod = entriesByPeriod[0]?.period ?? "";
|
|
@@ -2023,6 +2013,11 @@ var ICON_PROMPT = {
|
|
|
2023
2013
|
mimeType: "image/svg+xml",
|
|
2024
2014
|
sizes: ["24x24"]
|
|
2025
2015
|
};
|
|
2016
|
+
var ICON_FLAG = {
|
|
2017
|
+
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/%3E%3Cline x1="12" y1="9" x2="12" y2="13"/%3E%3Cline x1="12" y1="17" x2="12.01" y2="17"/%3E%3C/svg%3E',
|
|
2018
|
+
mimeType: "image/svg+xml",
|
|
2019
|
+
sizes: ["24x24"]
|
|
2020
|
+
};
|
|
2026
2021
|
|
|
2027
2022
|
// src/handlers/resources/core/briefing/github-section.ts
|
|
2028
2023
|
async function buildGitHubSection(github, config) {
|
|
@@ -2372,6 +2367,51 @@ function buildSkillsDirInfo(skillsDirPath) {
|
|
|
2372
2367
|
return void 0;
|
|
2373
2368
|
}
|
|
2374
2369
|
}
|
|
2370
|
+
function buildFlagsContext(context) {
|
|
2371
|
+
if (!context.teamDb) return void 0;
|
|
2372
|
+
try {
|
|
2373
|
+
const flagEntries = context.teamDb.searchEntries("", {
|
|
2374
|
+
entryType: "flag",
|
|
2375
|
+
limit: 20
|
|
2376
|
+
});
|
|
2377
|
+
const activeFlags = flagEntries.map((entry) => {
|
|
2378
|
+
const autoCtx = entry.autoContext;
|
|
2379
|
+
if (!autoCtx) return null;
|
|
2380
|
+
try {
|
|
2381
|
+
const parsed = JSON.parse(autoCtx);
|
|
2382
|
+
if (typeof parsed === "object" && parsed !== null && "flag_type" in parsed && "resolved" in parsed) {
|
|
2383
|
+
const ctx = parsed;
|
|
2384
|
+
if (ctx["resolved"] === true) return null;
|
|
2385
|
+
const content = entry.content ?? "";
|
|
2386
|
+
return {
|
|
2387
|
+
id: entry.id,
|
|
2388
|
+
flag_type: String(ctx["flag_type"]),
|
|
2389
|
+
target_user: typeof ctx["target_user"] === "string" ? ctx["target_user"] : null,
|
|
2390
|
+
preview: content.slice(0, 80) + (content.length > 80 ? "..." : ""),
|
|
2391
|
+
timestamp: entry.timestamp
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
return null;
|
|
2395
|
+
} catch {
|
|
2396
|
+
return null;
|
|
2397
|
+
}
|
|
2398
|
+
}).filter(
|
|
2399
|
+
(f) => f !== null
|
|
2400
|
+
);
|
|
2401
|
+
if (activeFlags.length === 0) return void 0;
|
|
2402
|
+
return {
|
|
2403
|
+
count: activeFlags.length,
|
|
2404
|
+
flags: activeFlags
|
|
2405
|
+
};
|
|
2406
|
+
} catch (error) {
|
|
2407
|
+
logger.debug("Failed to build flags context", {
|
|
2408
|
+
module: "BRIEFING",
|
|
2409
|
+
operation: "flags-context",
|
|
2410
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2411
|
+
});
|
|
2412
|
+
return void 0;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2375
2415
|
|
|
2376
2416
|
// src/handlers/resources/core/briefing/user-message.ts
|
|
2377
2417
|
var escapeTableCell = (text) => text.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
@@ -2461,8 +2501,8 @@ function formatUserMessage(opts) {
|
|
|
2461
2501
|
parts.push(`\u{1F4C8} ${analyticsInsights.activityTrend}`);
|
|
2462
2502
|
if (analyticsInsights.significanceSpike !== null)
|
|
2463
2503
|
parts.push(`\u{1F525} ${analyticsInsights.significanceSpike}`);
|
|
2464
|
-
if (analyticsInsights.relationshipDensity !== void 0
|
|
2465
|
-
parts.push(`\u{1F517}
|
|
2504
|
+
if (analyticsInsights.relationshipDensity !== void 0)
|
|
2505
|
+
parts.push(`\u{1F517} Relationship density: ${analyticsInsights.relationshipDensity}`);
|
|
2466
2506
|
if (analyticsInsights.staleProjects.length > 0)
|
|
2467
2507
|
parts.push(`\u{1F4A4} ${analyticsInsights.staleProjects.length} stale projects`);
|
|
2468
2508
|
analyticsRow = `
|
|
@@ -2470,6 +2510,15 @@ function formatUserMessage(opts) {
|
|
|
2470
2510
|
}
|
|
2471
2511
|
const summariesOutput = summaryPreviews && summaryPreviews.length > 0 ? summaryPreviews.map((s) => `
|
|
2472
2512
|
| **Summary** | ${escapeTableCell(s)} |`).join("") : "";
|
|
2513
|
+
let flagsRow = "";
|
|
2514
|
+
if (opts.flagSummary && opts.flagSummary.count > 0) {
|
|
2515
|
+
const flagParts = opts.flagSummary.flags.slice(0, 5).map((f) => {
|
|
2516
|
+
const target = f.target_user ? ` \u2192 @${f.target_user}` : "";
|
|
2517
|
+
return `\u{1F6A9} ${f.flag_type}${target}: ${f.preview.slice(0, 50)}`;
|
|
2518
|
+
});
|
|
2519
|
+
flagsRow = `
|
|
2520
|
+
| **Flags** | ${escapeTableCell(flagParts.join(" \xB7 "))}${opts.flagSummary.count > 5 ? ` (+${String(opts.flagSummary.count - 5)} more)` : ""} |`;
|
|
2521
|
+
}
|
|
2473
2522
|
return `\u{1F4CB} **Session Context Loaded**
|
|
2474
2523
|
| Context | Value |
|
|
2475
2524
|
|---------|-------|
|
|
@@ -2478,7 +2527,7 @@ function formatUserMessage(opts) {
|
|
|
2478
2527
|
| **CI** | ${escapeTableCell(ciDisplay)} |
|
|
2479
2528
|
| **Journal** | ${totalEntries} entries |${opts.teamTotalEntries !== void 0 ? `
|
|
2480
2529
|
| **Team DB** | ${opts.teamTotalEntries} entries |` : ""}
|
|
2481
|
-
| **Latest** | ${escapeTableCell(latestPreview)} |${summariesOutput}${issuesRow}${prsRow}${milestoneRow}${insightsRow}${copilotRow}${analyticsRow}${rulesFile ? `
|
|
2530
|
+
| **Latest** | ${escapeTableCell(latestPreview)} |${summariesOutput}${issuesRow}${prsRow}${milestoneRow}${insightsRow}${copilotRow}${analyticsRow}${flagsRow}${rulesFile ? `
|
|
2482
2531
|
| **Rules** | ${escapeTableCell(rulesFile.name)} (${String(rulesFile.sizeKB)} KB, updated ${rulesFile.lastModified}) |` : ""}${skillsDir ? `
|
|
2483
2532
|
| **Skills** | ${String(skillsDir.count)} skill${skillsDir.count !== 1 ? "s" : ""} available |` : ""}`;
|
|
2484
2533
|
}
|
|
@@ -2575,6 +2624,7 @@ async function buildBriefingData(context, targetRepo) {
|
|
|
2575
2624
|
const rulesFile = buildRulesFileInfo(config.rulesFilePath);
|
|
2576
2625
|
const skillsDir = buildSkillsDirInfo(config.skillsDirPath);
|
|
2577
2626
|
const insights = buildInsightsSection(context);
|
|
2627
|
+
const flags = buildFlagsContext(context);
|
|
2578
2628
|
const latestPreview = journal.latestEntries[0] ? `#${journal.latestEntries[0].id} (${journal.latestEntries[0].type}): ${journal.latestEntries[0].preview}` : "No entries yet";
|
|
2579
2629
|
const summaryPreviews = journal.sessionSummaries ? journal.sessionSummaries.map((s) => `#${s.id} (${s.type}): ${s.preview}`) : null;
|
|
2580
2630
|
const userMessage = formatUserMessage({
|
|
@@ -2588,12 +2638,18 @@ async function buildBriefingData(context, targetRepo) {
|
|
|
2588
2638
|
teamTotalEntries: team?.teamInfo.totalEntries,
|
|
2589
2639
|
rulesFile,
|
|
2590
2640
|
skillsDir,
|
|
2591
|
-
analyticsInsights: insights ?? void 0
|
|
2641
|
+
analyticsInsights: insights ?? void 0,
|
|
2642
|
+
flagSummary: flags
|
|
2592
2643
|
});
|
|
2593
2644
|
return {
|
|
2594
2645
|
data: {
|
|
2595
2646
|
version: VERSION,
|
|
2596
2647
|
serverTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2648
|
+
localTime: new Intl.DateTimeFormat("en-US", {
|
|
2649
|
+
dateStyle: "full",
|
|
2650
|
+
timeStyle: "short",
|
|
2651
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
2652
|
+
}).format(/* @__PURE__ */ new Date()),
|
|
2597
2653
|
journal: {
|
|
2598
2654
|
totalEntries: journal.totalEntries,
|
|
2599
2655
|
latestEntries: journal.latestEntries,
|
|
@@ -2605,6 +2661,7 @@ async function buildBriefingData(context, targetRepo) {
|
|
|
2605
2661
|
...rulesFile ? { rulesFile } : {},
|
|
2606
2662
|
...skillsDir ? { skillsDir } : {},
|
|
2607
2663
|
...insights ? { insights } : {},
|
|
2664
|
+
...flags ? { activeFlags: flags } : {},
|
|
2608
2665
|
...config.projectRegistry ? { registeredWorkspaces: config.projectRegistry } : {},
|
|
2609
2666
|
behaviors: {
|
|
2610
2667
|
create: "implementations, decisions, bug-fixes, milestones",
|
|
@@ -2708,6 +2765,25 @@ This server leverages the \`neverinfamous-agent-skills\` package. If the user's
|
|
|
2708
2765
|
|
|
2709
2766
|
- The user can distribute or update these skills across their repositories by running \`npx neverinfamous-agent-skills@latest\`.
|
|
2710
2767
|
- If you need to create a new skill, reference the bundled \`skill-builder\` instructions!
|
|
2768
|
+
|
|
2769
|
+
### Hush Protocol (Team Flags)
|
|
2770
|
+
|
|
2771
|
+
Flags are machine-actionable signals stored in the team database. They replace Slack/Teams noise with structured, searchable entries that surface automatically in the briefing.
|
|
2772
|
+
|
|
2773
|
+
**When to create a flag** (\`pass_team_flag\`):
|
|
2774
|
+
|
|
2775
|
+
- \`blocker\` \u2014 work is blocked and requires another person's action
|
|
2776
|
+
- \`needs_review\` \u2014 code, document, or decision needs peer review
|
|
2777
|
+
- \`help_requested\` \u2014 stuck and need guidance or pairing
|
|
2778
|
+
- \`fyi\` \u2014 non-blocking awareness signal (completed migration, config change, etc.)
|
|
2779
|
+
|
|
2780
|
+
**When to resolve** (\`resolve_team_flag\`): After the blocking condition is cleared. Include a brief resolution comment describing what was done. Resolving is idempotent \u2014 safe to call on already-resolved flags.
|
|
2781
|
+
|
|
2782
|
+
**Briefing integration**: The \`memory://briefing\` payload includes \`activeFlags\` when unresolved flags exist. The user's agent rules may instruct you to render these prominently. Always check for and acknowledge active flags at session start.
|
|
2783
|
+
|
|
2784
|
+
**Dashboard**: Read \`memory://flags\` to see all active (unresolved) flags. Read \`memory://flags/vocabulary\` to see the configured flag types.
|
|
2785
|
+
|
|
2786
|
+
**Code Mode**: \`mj.team.passTeamFlag({ flag_type, message })\` and \`mj.team.resolveTeamFlag({ flag_id })\`.
|
|
2711
2787
|
`;
|
|
2712
2788
|
var COPILOT_REVIEW_INSTRUCTIONS = `
|
|
2713
2789
|
## Copilot Review Patterns
|
|
@@ -2744,71 +2820,19 @@ function buildQuickAccess(groups) {
|
|
|
2744
2820
|
return table;
|
|
2745
2821
|
}
|
|
2746
2822
|
var CODE_MODE_NAMESPACE_ROWS = [
|
|
2747
|
-
{
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
},
|
|
2753
|
-
{
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
example: '`mj.search.searchEntries("performance")`'
|
|
2758
|
-
},
|
|
2759
|
-
{
|
|
2760
|
-
group: "analytics",
|
|
2761
|
-
label: "Analytics",
|
|
2762
|
-
namespace: "`mj.analytics.*`",
|
|
2763
|
-
example: "`mj.analytics.getStatistics()`"
|
|
2764
|
-
},
|
|
2765
|
-
{
|
|
2766
|
-
group: "relationships",
|
|
2767
|
-
label: "Relationships",
|
|
2768
|
-
namespace: "`mj.relationships.*`",
|
|
2769
|
-
example: '`mj.relationships.linkEntries(1, 2, "implements")`'
|
|
2770
|
-
},
|
|
2771
|
-
{
|
|
2772
|
-
group: "io",
|
|
2773
|
-
label: "IO",
|
|
2774
|
-
namespace: "`mj.io.*`",
|
|
2775
|
-
example: '`mj.io.importMarkdown("content")`'
|
|
2776
|
-
},
|
|
2777
|
-
{
|
|
2778
|
-
group: "io",
|
|
2779
|
-
label: "Export",
|
|
2780
|
-
namespace: "`mj.export.*`",
|
|
2781
|
-
example: '`mj.export.exportEntries("json")`'
|
|
2782
|
-
},
|
|
2783
|
-
{
|
|
2784
|
-
group: "admin",
|
|
2785
|
-
label: "Admin",
|
|
2786
|
-
namespace: "`mj.admin.*`",
|
|
2787
|
-
example: "`mj.admin.rebuildVectorIndex()`"
|
|
2788
|
-
},
|
|
2789
|
-
{
|
|
2790
|
-
group: "github",
|
|
2791
|
-
label: "GitHub",
|
|
2792
|
-
namespace: "`mj.github.*`",
|
|
2793
|
-
example: '`mj.github.getGithubIssues({ state: "open" })`'
|
|
2794
|
-
},
|
|
2795
|
-
{
|
|
2796
|
-
group: "backup",
|
|
2797
|
-
label: "Backup",
|
|
2798
|
-
namespace: "`mj.backup.*`",
|
|
2799
|
-
example: "`mj.backup.backupJournal()`"
|
|
2800
|
-
},
|
|
2801
|
-
{
|
|
2802
|
-
group: "team",
|
|
2803
|
-
label: "Team",
|
|
2804
|
-
namespace: "`mj.team.*`",
|
|
2805
|
-
example: '`mj.team.teamCreateEntry("Team update")`'
|
|
2806
|
-
}
|
|
2823
|
+
{ group: "core", label: "Core", namespace: "`mj.core.*`", example: '`mj.core.createEntry("Implemented feature X")`' },
|
|
2824
|
+
{ group: "search", label: "Search", namespace: "`mj.search.*`", example: '`mj.search.searchEntries("performance")`' },
|
|
2825
|
+
{ group: "analytics", label: "Analytics", namespace: "`mj.analytics.*`", example: "`mj.analytics.getStatistics()`" },
|
|
2826
|
+
{ group: "relationships", label: "Relationships", namespace: "`mj.relationships.*`", example: '`mj.relationships.linkEntries(1, 2, "implements")`' },
|
|
2827
|
+
{ group: "io", label: "IO", namespace: "`mj.io.*`", example: '`mj.io.importMarkdown("content")`' },
|
|
2828
|
+
{ group: "io", label: "Export", namespace: "`mj.export.*`", example: '`mj.export.exportEntries("json")`' },
|
|
2829
|
+
{ group: "admin", label: "Admin", namespace: "`mj.admin.*`", example: "`mj.admin.rebuildVectorIndex()`" },
|
|
2830
|
+
{ group: "github", label: "GitHub", namespace: "`mj.github.*`", example: '`mj.github.getGithubIssues({ state: "open" })`' },
|
|
2831
|
+
{ group: "backup", label: "Backup", namespace: "`mj.backup.*`", example: "`mj.backup.backupJournal()`" },
|
|
2832
|
+
{ group: "team", label: "Team", namespace: "`mj.team.*`", example: '`mj.team.teamCreateEntry("Team update")`' }
|
|
2807
2833
|
];
|
|
2808
2834
|
function buildCodeModeInstructions(groups) {
|
|
2809
|
-
const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map(
|
|
2810
|
-
(r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`
|
|
2811
|
-
).join("\n");
|
|
2835
|
+
const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map((r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`).join("\n");
|
|
2812
2836
|
const fullSection = CODE_MODE_FULL_TEXT;
|
|
2813
2837
|
const tableStart = fullSection.indexOf("| Group");
|
|
2814
2838
|
const tableEnd = fullSection.indexOf("\n\n**Features**");
|
|
@@ -2957,7 +2981,7 @@ var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
|
|
|
2957
2981
|
|
|
2958
2982
|
- **Team cross-database search**: \`search_entries\` and \`search_by_date_range\` automatically merge team DB results when \`TEAM_DB_PATH\` is configured. Results include a \`source\` field ("personal" or "team").
|
|
2959
2983
|
- **Team vector search**: Team has its own isolated vector index. Use \`team_rebuild_vector_index\` if the team index drifts. \`team_semantic_search\` works identically to personal \`semantic_search\`.
|
|
2960
|
-
- **Team tools without \`TEAM_DB_PATH\`**: All
|
|
2984
|
+
- **Team tools without \`TEAM_DB_PATH\`**: All 25 team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
|
|
2961
2985
|
`;
|
|
2962
2986
|
function generateInstructions(enabledTools, prompts, latestEntry, level = "standard", enabledGroups) {
|
|
2963
2987
|
const groups = enabledGroups ?? getEnabledGroups(enabledTools);
|
|
@@ -4990,15 +5014,41 @@ function getTemplateResourceDefinitions() {
|
|
|
4990
5014
|
// src/handlers/resources/team.ts
|
|
4991
5015
|
function enrichWithAuthor(entries, context) {
|
|
4992
5016
|
const teamDb = context.teamDb;
|
|
4993
|
-
if (!teamDb) return entries.map((e) => ({ ...e, author: null }));
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5017
|
+
if (!teamDb || entries.length === 0) return entries.map((e) => ({ ...e, author: null }));
|
|
5018
|
+
const ids = entries.map((e) => e.id);
|
|
5019
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
5020
|
+
const authorResult = teamDb.executeRawQuery(
|
|
5021
|
+
`SELECT id, author FROM memory_journal WHERE id IN (${placeholders})`,
|
|
5022
|
+
ids
|
|
5023
|
+
);
|
|
5024
|
+
const authorMap = /* @__PURE__ */ new Map();
|
|
5025
|
+
if (authorResult[0]) {
|
|
5026
|
+
authorResult[0].values.forEach((row) => {
|
|
5027
|
+
authorMap.set(row[0], row[1]);
|
|
5028
|
+
});
|
|
5029
|
+
}
|
|
5030
|
+
return entries.map((e) => ({
|
|
5031
|
+
...e,
|
|
5032
|
+
author: authorMap.get(e.id) ?? null
|
|
5033
|
+
}));
|
|
5034
|
+
}
|
|
5035
|
+
function parseFlagAutoContext(autoContext) {
|
|
5036
|
+
if (!autoContext) return null;
|
|
5037
|
+
try {
|
|
5038
|
+
const parsed = JSON.parse(autoContext);
|
|
5039
|
+
if (typeof parsed === "object" && parsed !== null && "flag_type" in parsed && "resolved" in parsed) {
|
|
5040
|
+
const ctx = parsed;
|
|
5041
|
+
return {
|
|
5042
|
+
flag_type: String(ctx["flag_type"]),
|
|
5043
|
+
target_user: typeof ctx["target_user"] === "string" ? ctx["target_user"] : null,
|
|
5044
|
+
resolved: Boolean(ctx["resolved"]),
|
|
5045
|
+
link: typeof ctx["link"] === "string" ? ctx["link"] : null
|
|
5046
|
+
};
|
|
5047
|
+
}
|
|
5048
|
+
return null;
|
|
5049
|
+
} catch {
|
|
5050
|
+
return null;
|
|
5051
|
+
}
|
|
5002
5052
|
}
|
|
5003
5053
|
function getTeamResourceDefinitions() {
|
|
5004
5054
|
return [
|
|
@@ -5077,6 +5127,77 @@ function getTeamResourceDefinitions() {
|
|
|
5077
5127
|
}
|
|
5078
5128
|
};
|
|
5079
5129
|
}
|
|
5130
|
+
},
|
|
5131
|
+
// ====================================================================
|
|
5132
|
+
// Flag Resources (Hush Protocol)
|
|
5133
|
+
// ====================================================================
|
|
5134
|
+
{
|
|
5135
|
+
uri: "memory://flags",
|
|
5136
|
+
name: "Active Flags",
|
|
5137
|
+
title: "Active Team Flags Dashboard",
|
|
5138
|
+
description: "Active (unresolved) flags from the Hush Protocol. Shows machine-actionable developer signals that need attention. Requires TEAM_DB_PATH.",
|
|
5139
|
+
mimeType: "application/json",
|
|
5140
|
+
icons: [ICON_FLAG],
|
|
5141
|
+
annotations: withPriority(0.8, ASSISTANT_FOCUSED),
|
|
5142
|
+
handler: (_uri, context) => {
|
|
5143
|
+
if (!context.teamDb) {
|
|
5144
|
+
return {
|
|
5145
|
+
data: {
|
|
5146
|
+
error: "Team database not configured. Set TEAM_DB_PATH to enable.",
|
|
5147
|
+
activeFlags: [],
|
|
5148
|
+
count: 0
|
|
5149
|
+
}
|
|
5150
|
+
};
|
|
5151
|
+
}
|
|
5152
|
+
const flagEntries = context.teamDb.searchEntries("", {
|
|
5153
|
+
entryType: "flag",
|
|
5154
|
+
limit: 100
|
|
5155
|
+
});
|
|
5156
|
+
const enriched = enrichWithAuthor(flagEntries, context);
|
|
5157
|
+
const activeFlags = enriched.map((entry) => {
|
|
5158
|
+
const flagCtx = parseFlagAutoContext(entry.autoContext);
|
|
5159
|
+
if (!flagCtx || flagCtx.resolved) return null;
|
|
5160
|
+
return {
|
|
5161
|
+
id: entry.id,
|
|
5162
|
+
flag_type: flagCtx.flag_type,
|
|
5163
|
+
target_user: flagCtx.target_user,
|
|
5164
|
+
link: flagCtx.link,
|
|
5165
|
+
author: entry.author,
|
|
5166
|
+
timestamp: entry.timestamp,
|
|
5167
|
+
preview: entry.content.slice(0, 120) + (entry.content.length > 120 ? "..." : ""),
|
|
5168
|
+
tags: entry.tags,
|
|
5169
|
+
projectNumber: entry.projectNumber ?? null
|
|
5170
|
+
};
|
|
5171
|
+
}).filter((f) => f !== null);
|
|
5172
|
+
const lastModified = activeFlags[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5173
|
+
return {
|
|
5174
|
+
data: {
|
|
5175
|
+
activeFlags,
|
|
5176
|
+
count: activeFlags.length
|
|
5177
|
+
},
|
|
5178
|
+
annotations: { lastModified }
|
|
5179
|
+
};
|
|
5180
|
+
}
|
|
5181
|
+
},
|
|
5182
|
+
{
|
|
5183
|
+
uri: "memory://flags/vocabulary",
|
|
5184
|
+
name: "Flag Vocabulary",
|
|
5185
|
+
title: "Hush Protocol Flag Vocabulary",
|
|
5186
|
+
description: "Returns the configured flag vocabulary for the Hush Protocol. Static resource reflecting server-wide configuration.",
|
|
5187
|
+
mimeType: "application/json",
|
|
5188
|
+
icons: [ICON_FLAG],
|
|
5189
|
+
annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
|
|
5190
|
+
handler: (_uri, context) => {
|
|
5191
|
+
const custom = context.briefingConfig?.flagVocabulary;
|
|
5192
|
+
const vocabulary = custom && custom.length > 0 ? custom : [...DEFAULT_FLAG_VOCABULARY];
|
|
5193
|
+
return {
|
|
5194
|
+
data: {
|
|
5195
|
+
vocabulary,
|
|
5196
|
+
count: vocabulary.length,
|
|
5197
|
+
isDefault: !custom || custom.length === 0
|
|
5198
|
+
}
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5080
5201
|
}
|
|
5081
5202
|
];
|
|
5082
5203
|
}
|
|
@@ -5289,7 +5410,7 @@ function getHelpResourceDefinitions() {
|
|
|
5289
5410
|
var toolIndexModule = null;
|
|
5290
5411
|
async function getAllToolDefinitionsAsync(context) {
|
|
5291
5412
|
try {
|
|
5292
|
-
toolIndexModule ??= await import('./tools-
|
|
5413
|
+
toolIndexModule ??= await import('./tools-HTE4YXMW.js');
|
|
5293
5414
|
if (toolIndexModule === null) return [];
|
|
5294
5415
|
const tools = toolIndexModule.getTools(context.db, null);
|
|
5295
5416
|
return tools.map((t) => ({
|
|
@@ -5433,7 +5554,7 @@ var teamCollaborationResource = {
|
|
|
5433
5554
|
try {
|
|
5434
5555
|
const matrix = computeTeamCollaborationMatrix(context.teamDb);
|
|
5435
5556
|
return {
|
|
5436
|
-
data: { success: true,
|
|
5557
|
+
data: { success: true, matrix },
|
|
5437
5558
|
annotations: { lastModified }
|
|
5438
5559
|
};
|
|
5439
5560
|
} catch (error) {
|
|
@@ -6596,7 +6717,23 @@ function setupStateful(ctx, app, server) {
|
|
|
6596
6717
|
}
|
|
6597
6718
|
};
|
|
6598
6719
|
if (ctx.serverConnected) {
|
|
6599
|
-
|
|
6720
|
+
try {
|
|
6721
|
+
await Promise.race([
|
|
6722
|
+
server.close(),
|
|
6723
|
+
new Promise(
|
|
6724
|
+
(_, reject) => setTimeout(() => reject(new Error("Timeout closing SDK transport")), 250)
|
|
6725
|
+
)
|
|
6726
|
+
]);
|
|
6727
|
+
} catch (e) {
|
|
6728
|
+
logger.error("Forcing server transport close due to timeout", {
|
|
6729
|
+
module: "HTTP",
|
|
6730
|
+
error: e instanceof Error ? e.message : String(e)
|
|
6731
|
+
});
|
|
6732
|
+
const internalServer = server;
|
|
6733
|
+
if (internalServer.server !== void 0 && typeof internalServer.server._onclose === "function") {
|
|
6734
|
+
internalServer.server._onclose();
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6600
6737
|
}
|
|
6601
6738
|
await server.connect(newTransport);
|
|
6602
6739
|
ctx.serverConnected = true;
|
|
@@ -7168,7 +7305,8 @@ async function createServer(options) {
|
|
|
7168
7305
|
} : void 0;
|
|
7169
7306
|
const customToolHandlerConfig = {
|
|
7170
7307
|
defaultProjectNumber,
|
|
7171
|
-
projectRegistry: options.projectRegistry
|
|
7308
|
+
projectRegistry: options.projectRegistry,
|
|
7309
|
+
flagVocabulary: options.flagVocabulary
|
|
7172
7310
|
};
|
|
7173
7311
|
const allTools = getTools(
|
|
7174
7312
|
db,
|
|
@@ -7331,7 +7469,8 @@ async function createServer(options) {
|
|
|
7331
7469
|
// Ensure defaultProjectNumber is available to resource handlers
|
|
7332
7470
|
// (may come via briefingConfig from CLI, or directly from server options)
|
|
7333
7471
|
defaultProjectNumber: options.briefingConfig?.defaultProjectNumber ?? defaultProjectNumber,
|
|
7334
|
-
projectRegistry: options.projectRegistry
|
|
7472
|
+
projectRegistry: options.projectRegistry,
|
|
7473
|
+
flagVocabulary: options.flagVocabulary
|
|
7335
7474
|
};
|
|
7336
7475
|
const result = await readResource(
|
|
7337
7476
|
uri.href,
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { VERSION, createServer } from './chunk-
|
|
2
|
-
import { DEFAULT_AUDIT_LOG_MAX_SIZE_BYTES } from './chunk-
|
|
1
|
+
import { VERSION, createServer } from './chunk-XNOUTCRV.js';
|
|
2
|
+
import { DEFAULT_AUDIT_LOG_MAX_SIZE_BYTES } from './chunk-VHA46GLM.js';
|
|
3
3
|
import './chunk-OKOVZ5QE.js';
|
|
4
4
|
import { logger } from './chunk-WXDEVIFL.js';
|
|
5
5
|
import { Command } from 'commander';
|
|
@@ -109,6 +109,9 @@ program.name("memory-journal-mcp").description("Project context management for A
|
|
|
109
109
|
).option(
|
|
110
110
|
"--workflow-summary <text>",
|
|
111
111
|
"Workflow summary for memory://workflows resource (env: MEMORY_JOURNAL_WORKFLOW_SUMMARY)"
|
|
112
|
+
).option(
|
|
113
|
+
"--flag-vocabulary <terms>",
|
|
114
|
+
"Comma-separated flag vocabulary for Hush Protocol (env: FLAG_VOCABULARY, default: blocker,needs_review,help_requested,fyi)"
|
|
112
115
|
).action(
|
|
113
116
|
async (options) => {
|
|
114
117
|
logger.setLevel(options.logLevel);
|
|
@@ -217,7 +220,12 @@ program.name("memory-journal-mcp").description("Project context management for A
|
|
|
217
220
|
defaultProjectNumber: options.defaultProject ? parseInt(options.defaultProject, 10) : process.env["DEFAULT_PROJECT_NUMBER"] ? parseInt(process.env["DEFAULT_PROJECT_NUMBER"], 10) : void 0
|
|
218
221
|
},
|
|
219
222
|
instructionLevel: options.instructionLevel !== "standard" ? options.instructionLevel : process.env["INSTRUCTION_LEVEL"] ?? "standard",
|
|
220
|
-
auditConfig
|
|
223
|
+
auditConfig,
|
|
224
|
+
flagVocabulary: (() => {
|
|
225
|
+
const raw = options.flagVocabulary ?? process.env["FLAG_VOCABULARY"];
|
|
226
|
+
if (!raw) return void 0;
|
|
227
|
+
return raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
228
|
+
})()
|
|
221
229
|
});
|
|
222
230
|
} catch (error) {
|
|
223
231
|
logger.error("Failed to start server", {
|
package/dist/index.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ interface ToolFilterConfig {
|
|
|
38
38
|
/**
|
|
39
39
|
* Entry types for journal entries
|
|
40
40
|
*/
|
|
41
|
-
type EntryType = 'personal_reflection' | 'project_decision' | 'technical_achievement' | 'bug_fix' | 'feature_implementation' | 'code_review' | 'meeting_notes' | 'learning' | 'research' | 'planning' | 'retrospective' | 'standup' | 'technical_note' | 'development_note' | 'enhancement' | 'milestone' | 'system_integration_test' | 'test_entry' | 'other';
|
|
41
|
+
type EntryType = 'personal_reflection' | 'project_decision' | 'technical_achievement' | 'bug_fix' | 'feature_implementation' | 'code_review' | 'meeting_notes' | 'learning' | 'research' | 'planning' | 'retrospective' | 'standup' | 'technical_note' | 'development_note' | 'enhancement' | 'milestone' | 'flag' | 'system_integration_test' | 'test_entry' | 'other';
|
|
42
42
|
/**
|
|
43
43
|
* Significance types for important entries
|
|
44
44
|
*/
|
|
@@ -82,6 +82,7 @@ interface JournalEntry {
|
|
|
82
82
|
workflowRunId?: number | null;
|
|
83
83
|
workflowName?: string | null;
|
|
84
84
|
workflowStatus?: string | null;
|
|
85
|
+
importanceScore?: number;
|
|
85
86
|
}
|
|
86
87
|
/**
|
|
87
88
|
* Tag entity
|
|
@@ -330,6 +331,8 @@ interface ServerConfig {
|
|
|
330
331
|
modelName: string;
|
|
331
332
|
/** Briefing depth for AI client instructions */
|
|
332
333
|
instructionLevel?: 'essential' | 'standard' | 'full';
|
|
334
|
+
/** Hush Protocol flag vocabulary (defaults: blocker, needs_review, help_requested, fyi) */
|
|
335
|
+
flagVocabulary?: string[];
|
|
333
336
|
}
|
|
334
337
|
/**
|
|
335
338
|
* Default configuration values
|
|
@@ -525,7 +528,7 @@ type SandboxMode = 'vm' | 'worker';
|
|
|
525
528
|
/**
|
|
526
529
|
* Tool group definitions mapping group names to tool names
|
|
527
530
|
*
|
|
528
|
-
* All
|
|
531
|
+
* All 70 tools are categorized here for filtering support.
|
|
529
532
|
*/
|
|
530
533
|
declare const TOOL_GROUPS: Record<ToolGroup, string[]>;
|
|
531
534
|
/**
|
|
@@ -618,6 +621,8 @@ interface BriefingConfig {
|
|
|
618
621
|
defaultProjectNumber?: number;
|
|
619
622
|
/** Project registry mapping dynamic repo IDs to local paths and kanban boards */
|
|
620
623
|
projectRegistry?: Record<string, ProjectRegistryEntry>;
|
|
624
|
+
/** Hush Protocol flag vocabulary passed from CLI/env */
|
|
625
|
+
flagVocabulary?: string[];
|
|
621
626
|
}
|
|
622
627
|
|
|
623
628
|
/** Audit log configuration */
|
|
@@ -664,6 +669,7 @@ interface ServerOptions {
|
|
|
664
669
|
projectRegistry?: Record<string, ProjectRegistryEntry>;
|
|
665
670
|
instructionLevel?: 'essential' | 'standard' | 'full';
|
|
666
671
|
auditConfig?: AuditConfig;
|
|
672
|
+
flagVocabulary?: string[];
|
|
667
673
|
}
|
|
668
674
|
/**
|
|
669
675
|
* Create and start the MCP server
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { VERSION, createServer } from './chunk-
|
|
2
|
-
export { META_GROUPS, TOOL_GROUPS, calculateTokenSavings, filterTools, getAllToolNames, getFilterSummary, getToolFilterFromEnv, getToolGroup, isToolEnabled, parseToolFilter } from './chunk-
|
|
1
|
+
export { VERSION, createServer } from './chunk-XNOUTCRV.js';
|
|
2
|
+
export { META_GROUPS, TOOL_GROUPS, calculateTokenSavings, filterTools, getAllToolNames, getFilterSummary, getToolFilterFromEnv, getToolGroup, isToolEnabled, parseToolFilter } from './chunk-VHA46GLM.js';
|
|
3
3
|
import './chunk-OKOVZ5QE.js';
|
|
4
4
|
export { logger } from './chunk-WXDEVIFL.js';
|
|
5
5
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-journal-mcp",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "Project context management for AI-assisted development - Persistent knowledge graphs and intelligent context recall across fragmented AI threads",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"overrides": {
|
|
89
89
|
"axios": "^1.13.6",
|
|
90
90
|
"brace-expansion": "^2.0.2",
|
|
91
|
+
"diff": "9.0.0",
|
|
91
92
|
"glob": "^11.1.0",
|
|
92
93
|
"onnxruntime-web": "npm:empty-npm-package@1.0.0",
|
|
93
94
|
"sharp": "npm:empty-npm-package@1.0.0",
|
package/skills/README.md
CHANGED
|
@@ -45,6 +45,7 @@ The markdown body contains the full instructions the agent follows once the skil
|
|
|
45
45
|
| `docker` | Production-grade Docker — multi-stage builds, security hardening, Compose v2, BuildKit, and CI/CD integration |
|
|
46
46
|
| `github-actions` | GitHub Actions CI/CD — SHA pinning, reusable workflows, caching, matrix strategies, and artifacts v4 |
|
|
47
47
|
| `github-commander` | GitHub pipeline workflows for orchestrating issues, regressions, and deployments |
|
|
48
|
+
| `github-copilot-cli` | Adversarial pre-push validation and full repository code audits driven by the @github/copilot terminal harness |
|
|
48
49
|
| `gitlab` | Specialized assistant skill for managing repositories, code search, and CI/CD in GitLab |
|
|
49
50
|
| `golang` | Master Go development with production-grade best practices from Google and Uber style guides |
|
|
50
51
|
| `mysql` | Enterprise MySQL production rules — query safety, connection pooling, strict schema configurations |
|
|
@@ -67,6 +68,7 @@ This package natively bundles the `github-commander` skill, which equips your AI
|
|
|
67
68
|
- **`issue-triage`**: End-to-end bug replication, PR submission, and Kanban lifecycle linking.
|
|
68
69
|
- **`milestone-sprint`**: Sequential traversal of all open issues mapped to a specific release target.
|
|
69
70
|
- **`pr-review`**: Exhaustive local execution, typechecking, and heuristic code reviews against base branches.
|
|
71
|
+
- **`copilot-audit`**: AI-evaluating-AI adversarial evaluations covering localized diffs and whole codebases.
|
|
70
72
|
- **`security-audit`**: Deep Trivy/CodeQL supply chain matrix evaluation.
|
|
71
73
|
- **`code-quality-audit`**: Enforcement of project guidelines, strict-typing boundaries, and import normalization.
|
|
72
74
|
- **`perf-audit`**: Bundle-size constraints, runtime hot-path execution, and CI/CD cache-hit evaluations.
|
|
@@ -39,6 +39,7 @@ Load this skill when any of these apply:
|
|
|
39
39
|
| ----------------------- | --------------------------------- | ------------------------------------------- |
|
|
40
40
|
| **Issue Triage** | `workflows/issue-triage.md` | Fix a single GitHub issue end-to-end |
|
|
41
41
|
| **PR Review** | `workflows/pr-review.md` | Review a PR with validation pipeline |
|
|
42
|
+
| **Copilot Audit** | `workflows/copilot-audit.md` | Adversarial Copilot CLI repo/PR review |
|
|
42
43
|
| **Milestone Sprint** | `workflows/milestone-sprint.md` | Work through milestone issues sequentially |
|
|
43
44
|
| **Roadmap Kickoff** | `workflows/roadmap-kickoff.md` | Translate planning epics into Kanban issues |
|
|
44
45
|
| **Update Dependencies** | `workflows/update-deps.md` | Dependency update with audit trail |
|