prjct-cli 0.52.0 → 0.54.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/CHANGELOG.md +83 -0
- package/core/agentic/memory-system.ts +216 -0
- package/core/agentic/prompt-builder.ts +87 -13
- package/core/commands/analysis.ts +167 -10
- package/core/infrastructure/config-manager.ts +24 -0
- package/core/services/memory-service.ts +21 -0
- package/core/types/config.ts +6 -0
- package/core/types/memory.ts +70 -0
- package/core/utils/output.ts +2 -2
- package/dist/bin/prjct.mjs +378 -26
- package/package.json +1 -1
package/dist/bin/prjct.mjs
CHANGED
|
@@ -1595,7 +1595,9 @@ var init_config_manager = __esm({
|
|
|
1595
1595
|
const now = getTimestamp();
|
|
1596
1596
|
const localConfig = {
|
|
1597
1597
|
projectId,
|
|
1598
|
-
dataPath: displayPath
|
|
1598
|
+
dataPath: displayPath,
|
|
1599
|
+
showMetrics: true
|
|
1600
|
+
// PRJ-70: default to true for new projects
|
|
1599
1601
|
};
|
|
1600
1602
|
await this.writeConfig(projectPath, localConfig);
|
|
1601
1603
|
const globalConfig = {
|
|
@@ -1734,6 +1736,26 @@ var init_config_manager = __esm({
|
|
|
1734
1736
|
const config = await this.readConfig(projectPath);
|
|
1735
1737
|
return this.validateConfig(config);
|
|
1736
1738
|
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Get showMetrics setting from config.
|
|
1741
|
+
* Defaults to true for new or existing projects without the setting.
|
|
1742
|
+
* @see PRJ-70
|
|
1743
|
+
*/
|
|
1744
|
+
async getShowMetrics(projectPath) {
|
|
1745
|
+
const config = await this.readConfig(projectPath);
|
|
1746
|
+
return config?.showMetrics ?? true;
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Set showMetrics setting in config.
|
|
1750
|
+
* @see PRJ-70
|
|
1751
|
+
*/
|
|
1752
|
+
async setShowMetrics(projectPath, showMetrics) {
|
|
1753
|
+
const config = await this.readConfig(projectPath);
|
|
1754
|
+
if (config) {
|
|
1755
|
+
config.showMetrics = showMetrics;
|
|
1756
|
+
await this.writeConfig(projectPath, config);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1737
1759
|
/**
|
|
1738
1760
|
* Get configuration with defaults
|
|
1739
1761
|
* Returns LOCAL config only (projectId, dataPath)
|
|
@@ -2235,10 +2257,10 @@ var init_output = __esm({
|
|
|
2235
2257
|
const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
|
|
2236
2258
|
const border = "\u2500".repeat(maxLen + 2);
|
|
2237
2259
|
console.log(chalk2.dim(`\u250C${border}\u2510`));
|
|
2238
|
-
console.log(chalk2.dim("\u2502")
|
|
2260
|
+
console.log(`${chalk2.dim("\u2502")} ${chalk2.bold(title.padEnd(maxLen))} ${chalk2.dim("\u2502")}`);
|
|
2239
2261
|
console.log(chalk2.dim(`\u251C${border}\u2524`));
|
|
2240
2262
|
for (const line of lines) {
|
|
2241
|
-
console.log(chalk2.dim("\u2502")
|
|
2263
|
+
console.log(`${chalk2.dim("\u2502")} ${line.padEnd(maxLen)} ${chalk2.dim("\u2502")}`);
|
|
2242
2264
|
}
|
|
2243
2265
|
console.log(chalk2.dim(`\u2514${border}\u2518`));
|
|
2244
2266
|
return this;
|
|
@@ -9064,6 +9086,159 @@ var init_memory_system = __esm({
|
|
|
9064
9086
|
});
|
|
9065
9087
|
return scored.filter((m) => m._score > 0).sort((a, b) => b._score - a._score).slice(0, limit).map(({ _score, ...memory }) => memory);
|
|
9066
9088
|
}
|
|
9089
|
+
/**
|
|
9090
|
+
* Enhanced memory retrieval with domain-based filtering and metrics.
|
|
9091
|
+
* Implements selective memory retrieval based on task relevance.
|
|
9092
|
+
* @see PRJ-107
|
|
9093
|
+
*/
|
|
9094
|
+
async getRelevantMemoriesWithMetrics(projectId, query) {
|
|
9095
|
+
const db = await this.load(projectId);
|
|
9096
|
+
const totalMemories = db.memories.length;
|
|
9097
|
+
if (totalMemories === 0) {
|
|
9098
|
+
return {
|
|
9099
|
+
memories: [],
|
|
9100
|
+
metrics: {
|
|
9101
|
+
totalMemories: 0,
|
|
9102
|
+
memoriesConsidered: 0,
|
|
9103
|
+
memoriesReturned: 0,
|
|
9104
|
+
filteringRatio: 0,
|
|
9105
|
+
avgRelevanceScore: 0
|
|
9106
|
+
}
|
|
9107
|
+
};
|
|
9108
|
+
}
|
|
9109
|
+
const maxResults = query.maxResults ?? 10;
|
|
9110
|
+
const minRelevance = query.minRelevance ?? 10;
|
|
9111
|
+
const scored = db.memories.map((memory) => {
|
|
9112
|
+
const breakdown = {
|
|
9113
|
+
domainMatch: 0,
|
|
9114
|
+
tagMatch: 0,
|
|
9115
|
+
recency: 0,
|
|
9116
|
+
confidence: 0,
|
|
9117
|
+
keywords: 0,
|
|
9118
|
+
userTriggered: 0
|
|
9119
|
+
};
|
|
9120
|
+
if (query.taskDomain) {
|
|
9121
|
+
const domainTags = this._getDomainTags(query.taskDomain);
|
|
9122
|
+
const matchingTags = (memory.tags || []).filter((tag) => domainTags.includes(tag));
|
|
9123
|
+
breakdown.domainMatch = Math.min(25, matchingTags.length * 10);
|
|
9124
|
+
}
|
|
9125
|
+
if (query.commandName) {
|
|
9126
|
+
const commandTags = this._getCommandTags(query.commandName);
|
|
9127
|
+
const matchingTags = (memory.tags || []).filter((tag) => commandTags.includes(tag));
|
|
9128
|
+
breakdown.tagMatch = Math.min(20, matchingTags.length * 8);
|
|
9129
|
+
}
|
|
9130
|
+
const age = Date.now() - new Date(memory.updatedAt).getTime();
|
|
9131
|
+
const daysSinceUpdate = age / (1e3 * 60 * 60 * 24);
|
|
9132
|
+
breakdown.recency = Math.max(0, Math.round(15 - daysSinceUpdate * 0.5));
|
|
9133
|
+
if (memory.confidence) {
|
|
9134
|
+
breakdown.confidence = memory.confidence === "high" ? 20 : memory.confidence === "medium" ? 12 : 5;
|
|
9135
|
+
} else if (memory.observationCount) {
|
|
9136
|
+
breakdown.confidence = Math.min(20, memory.observationCount * 3);
|
|
9137
|
+
}
|
|
9138
|
+
if (query.taskDescription) {
|
|
9139
|
+
const keywords = this._extractKeywordsFromText(query.taskDescription);
|
|
9140
|
+
let keywordScore = 0;
|
|
9141
|
+
for (const keyword of keywords) {
|
|
9142
|
+
if (memory.content.toLowerCase().includes(keyword)) keywordScore += 2;
|
|
9143
|
+
if (memory.title.toLowerCase().includes(keyword)) keywordScore += 3;
|
|
9144
|
+
}
|
|
9145
|
+
breakdown.keywords = Math.min(15, keywordScore);
|
|
9146
|
+
}
|
|
9147
|
+
if (memory.userTriggered) {
|
|
9148
|
+
breakdown.userTriggered = 5;
|
|
9149
|
+
}
|
|
9150
|
+
const relevanceScore = breakdown.domainMatch + breakdown.tagMatch + breakdown.recency + breakdown.confidence + breakdown.keywords + breakdown.userTriggered;
|
|
9151
|
+
return {
|
|
9152
|
+
...memory,
|
|
9153
|
+
relevanceScore,
|
|
9154
|
+
scoreBreakdown: breakdown
|
|
9155
|
+
};
|
|
9156
|
+
});
|
|
9157
|
+
const considered = scored.filter((m) => m.relevanceScore >= minRelevance);
|
|
9158
|
+
const sorted = considered.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
9159
|
+
const returned = sorted.slice(0, maxResults);
|
|
9160
|
+
const avgRelevanceScore = returned.length > 0 ? Math.round(returned.reduce((sum, m) => sum + m.relevanceScore, 0) / returned.length) : 0;
|
|
9161
|
+
return {
|
|
9162
|
+
memories: returned,
|
|
9163
|
+
metrics: {
|
|
9164
|
+
totalMemories,
|
|
9165
|
+
memoriesConsidered: considered.length,
|
|
9166
|
+
memoriesReturned: returned.length,
|
|
9167
|
+
filteringRatio: totalMemories > 0 ? returned.length / totalMemories : 0,
|
|
9168
|
+
avgRelevanceScore
|
|
9169
|
+
}
|
|
9170
|
+
};
|
|
9171
|
+
}
|
|
9172
|
+
/**
|
|
9173
|
+
* Map task domain to relevant memory tags.
|
|
9174
|
+
* @see PRJ-107
|
|
9175
|
+
*/
|
|
9176
|
+
_getDomainTags(domain) {
|
|
9177
|
+
const domainTagMap = {
|
|
9178
|
+
frontend: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.FILE_STRUCTURE, MEMORY_TAGS.ARCHITECTURE],
|
|
9179
|
+
backend: [
|
|
9180
|
+
MEMORY_TAGS.CODE_STYLE,
|
|
9181
|
+
MEMORY_TAGS.ARCHITECTURE,
|
|
9182
|
+
MEMORY_TAGS.DEPENDENCIES,
|
|
9183
|
+
MEMORY_TAGS.TECH_STACK
|
|
9184
|
+
],
|
|
9185
|
+
devops: [MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.DEPENDENCIES],
|
|
9186
|
+
docs: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.NAMING_CONVENTION],
|
|
9187
|
+
testing: [MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.CODE_STYLE],
|
|
9188
|
+
database: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.NAMING_CONVENTION],
|
|
9189
|
+
general: Object.values(MEMORY_TAGS)
|
|
9190
|
+
};
|
|
9191
|
+
return domainTagMap[domain] || [];
|
|
9192
|
+
}
|
|
9193
|
+
/**
|
|
9194
|
+
* Map command to relevant memory tags.
|
|
9195
|
+
* @see PRJ-107
|
|
9196
|
+
*/
|
|
9197
|
+
_getCommandTags(commandName) {
|
|
9198
|
+
const commandTags = {
|
|
9199
|
+
ship: [MEMORY_TAGS.COMMIT_STYLE, MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR],
|
|
9200
|
+
feature: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
|
|
9201
|
+
done: [MEMORY_TAGS.SHIP_WORKFLOW],
|
|
9202
|
+
analyze: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE],
|
|
9203
|
+
spec: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
|
|
9204
|
+
task: [MEMORY_TAGS.BRANCH_NAMING, MEMORY_TAGS.CODE_STYLE],
|
|
9205
|
+
sync: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.DEPENDENCIES],
|
|
9206
|
+
test: [MEMORY_TAGS.TEST_BEHAVIOR],
|
|
9207
|
+
bug: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.TEST_BEHAVIOR]
|
|
9208
|
+
};
|
|
9209
|
+
return commandTags[commandName] || [];
|
|
9210
|
+
}
|
|
9211
|
+
/**
|
|
9212
|
+
* Extract keywords from text for matching.
|
|
9213
|
+
*/
|
|
9214
|
+
_extractKeywordsFromText(text) {
|
|
9215
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
9216
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
9217
|
+
"the",
|
|
9218
|
+
"a",
|
|
9219
|
+
"an",
|
|
9220
|
+
"is",
|
|
9221
|
+
"are",
|
|
9222
|
+
"to",
|
|
9223
|
+
"for",
|
|
9224
|
+
"and",
|
|
9225
|
+
"or",
|
|
9226
|
+
"in",
|
|
9227
|
+
"on",
|
|
9228
|
+
"at",
|
|
9229
|
+
"by",
|
|
9230
|
+
"with",
|
|
9231
|
+
"from",
|
|
9232
|
+
"as",
|
|
9233
|
+
"it",
|
|
9234
|
+
"this",
|
|
9235
|
+
"that",
|
|
9236
|
+
"be",
|
|
9237
|
+
"have",
|
|
9238
|
+
"has"
|
|
9239
|
+
]);
|
|
9240
|
+
return words.filter((w) => w.length > 2 && !stopWords.has(w));
|
|
9241
|
+
}
|
|
9067
9242
|
_extractContextTags(context2) {
|
|
9068
9243
|
const tags = [];
|
|
9069
9244
|
const commandTags = {
|
|
@@ -9184,6 +9359,14 @@ Context: ${context2}` : ""}`,
|
|
|
9184
9359
|
getMemoryStats(projectId) {
|
|
9185
9360
|
return this._semanticMemories.getMemoryStats(projectId);
|
|
9186
9361
|
}
|
|
9362
|
+
/**
|
|
9363
|
+
* Get relevant memories with domain-based filtering and metrics.
|
|
9364
|
+
* Implements selective memory retrieval based on task relevance.
|
|
9365
|
+
* @see PRJ-107
|
|
9366
|
+
*/
|
|
9367
|
+
getRelevantMemoriesWithMetrics(projectId, query) {
|
|
9368
|
+
return this._semanticMemories.getRelevantMemoriesWithMetrics(projectId, query);
|
|
9369
|
+
}
|
|
9187
9370
|
// ===========================================================================
|
|
9188
9371
|
// TIER 1: Session Memory
|
|
9189
9372
|
// ===========================================================================
|
|
@@ -12332,11 +12515,51 @@ var init_prompt_builder = __esm({
|
|
|
12332
12515
|
__name(this, "PromptBuilder");
|
|
12333
12516
|
}
|
|
12334
12517
|
_checklistsCache = null;
|
|
12518
|
+
_checklistsCacheTime = 0;
|
|
12335
12519
|
_checklistRoutingCache = null;
|
|
12520
|
+
_checklistRoutingCacheTime = 0;
|
|
12336
12521
|
_currentContext = null;
|
|
12337
12522
|
_stateCache = /* @__PURE__ */ new Map();
|
|
12338
12523
|
_stateCacheTTL = 5e3;
|
|
12339
12524
|
// 5 seconds
|
|
12525
|
+
_templateCache = /* @__PURE__ */ new Map();
|
|
12526
|
+
TEMPLATE_CACHE_TTL_MS = 6e4;
|
|
12527
|
+
// 60 seconds
|
|
12528
|
+
/**
|
|
12529
|
+
* Get a template with TTL caching.
|
|
12530
|
+
* Returns cached content if within TTL, otherwise loads from disk.
|
|
12531
|
+
* @see PRJ-76
|
|
12532
|
+
*/
|
|
12533
|
+
getTemplate(templatePath) {
|
|
12534
|
+
const cached = this._templateCache.get(templatePath);
|
|
12535
|
+
const now = Date.now();
|
|
12536
|
+
if (cached && now - cached.loadedAt < this.TEMPLATE_CACHE_TTL_MS) {
|
|
12537
|
+
return cached.content;
|
|
12538
|
+
}
|
|
12539
|
+
try {
|
|
12540
|
+
if (fs25.existsSync(templatePath)) {
|
|
12541
|
+
const content = fs25.readFileSync(templatePath, "utf-8");
|
|
12542
|
+
this._templateCache.set(templatePath, { content, loadedAt: now });
|
|
12543
|
+
return content;
|
|
12544
|
+
}
|
|
12545
|
+
} catch (error) {
|
|
12546
|
+
if (!isNotFoundError(error)) {
|
|
12547
|
+
console.error(`Template loading warning: ${error.message}`);
|
|
12548
|
+
}
|
|
12549
|
+
}
|
|
12550
|
+
return null;
|
|
12551
|
+
}
|
|
12552
|
+
/**
|
|
12553
|
+
* Clear the template cache (for testing or forced refresh)
|
|
12554
|
+
* @see PRJ-76
|
|
12555
|
+
*/
|
|
12556
|
+
clearTemplateCache() {
|
|
12557
|
+
this._templateCache.clear();
|
|
12558
|
+
this._checklistsCache = null;
|
|
12559
|
+
this._checklistsCacheTime = 0;
|
|
12560
|
+
this._checklistRoutingCache = null;
|
|
12561
|
+
this._checklistRoutingCacheTime = 0;
|
|
12562
|
+
}
|
|
12340
12563
|
/**
|
|
12341
12564
|
* Reset context (for testing)
|
|
12342
12565
|
*/
|
|
@@ -12351,9 +12574,14 @@ var init_prompt_builder = __esm({
|
|
|
12351
12574
|
}
|
|
12352
12575
|
/**
|
|
12353
12576
|
* Load quality checklists from templates/checklists/
|
|
12577
|
+
* Uses lazy loading with TTL cache.
|
|
12578
|
+
* @see PRJ-76
|
|
12354
12579
|
*/
|
|
12355
12580
|
loadChecklists() {
|
|
12356
|
-
|
|
12581
|
+
const now = Date.now();
|
|
12582
|
+
if (this._checklistsCache && now - this._checklistsCacheTime < this.TEMPLATE_CACHE_TTL_MS) {
|
|
12583
|
+
return this._checklistsCache;
|
|
12584
|
+
}
|
|
12357
12585
|
const checklistsDir = path25.join(__dirname, "..", "..", "templates", "checklists");
|
|
12358
12586
|
const checklists = {};
|
|
12359
12587
|
try {
|
|
@@ -12361,8 +12589,11 @@ var init_prompt_builder = __esm({
|
|
|
12361
12589
|
const files = fs25.readdirSync(checklistsDir).filter((f) => f.endsWith(".md"));
|
|
12362
12590
|
for (const file of files) {
|
|
12363
12591
|
const name = file.replace(".md", "");
|
|
12364
|
-
const
|
|
12365
|
-
|
|
12592
|
+
const templatePath = path25.join(checklistsDir, file);
|
|
12593
|
+
const content = this.getTemplate(templatePath);
|
|
12594
|
+
if (content) {
|
|
12595
|
+
checklists[name] = content;
|
|
12596
|
+
}
|
|
12366
12597
|
}
|
|
12367
12598
|
}
|
|
12368
12599
|
} catch (error) {
|
|
@@ -12371,6 +12602,7 @@ var init_prompt_builder = __esm({
|
|
|
12371
12602
|
}
|
|
12372
12603
|
}
|
|
12373
12604
|
this._checklistsCache = checklists;
|
|
12605
|
+
this._checklistsCacheTime = now;
|
|
12374
12606
|
return checklists;
|
|
12375
12607
|
}
|
|
12376
12608
|
/**
|
|
@@ -12468,9 +12700,14 @@ var init_prompt_builder = __esm({
|
|
|
12468
12700
|
}
|
|
12469
12701
|
/**
|
|
12470
12702
|
* Load checklist routing template for Claude to decide which checklists apply
|
|
12703
|
+
* Uses lazy loading with TTL cache.
|
|
12704
|
+
* @see PRJ-76
|
|
12471
12705
|
*/
|
|
12472
12706
|
loadChecklistRouting() {
|
|
12473
|
-
|
|
12707
|
+
const now = Date.now();
|
|
12708
|
+
if (this._checklistRoutingCache && now - this._checklistRoutingCacheTime < this.TEMPLATE_CACHE_TTL_MS) {
|
|
12709
|
+
return this._checklistRoutingCache;
|
|
12710
|
+
}
|
|
12474
12711
|
const routingPath = path25.join(
|
|
12475
12712
|
__dirname,
|
|
12476
12713
|
"..",
|
|
@@ -12479,14 +12716,10 @@ var init_prompt_builder = __esm({
|
|
|
12479
12716
|
"agentic",
|
|
12480
12717
|
"checklist-routing.md"
|
|
12481
12718
|
);
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
} catch (error) {
|
|
12487
|
-
if (!isNotFoundError(error)) {
|
|
12488
|
-
console.error(`Checklist routing warning: ${error.message}`);
|
|
12489
|
-
}
|
|
12719
|
+
const content = this.getTemplate(routingPath);
|
|
12720
|
+
if (content) {
|
|
12721
|
+
this._checklistRoutingCache = content;
|
|
12722
|
+
this._checklistRoutingCacheTime = now;
|
|
12490
12723
|
}
|
|
12491
12724
|
return this._checklistRoutingCache || null;
|
|
12492
12725
|
}
|
|
@@ -15113,6 +15346,22 @@ var init_memory_service = __esm({
|
|
|
15113
15346
|
}
|
|
15114
15347
|
}
|
|
15115
15348
|
}
|
|
15349
|
+
/**
|
|
15350
|
+
* Get recent events by projectId (for stats dashboard)
|
|
15351
|
+
* @see PRJ-89
|
|
15352
|
+
*/
|
|
15353
|
+
async getRecentEvents(projectId, limit = 100) {
|
|
15354
|
+
try {
|
|
15355
|
+
const memoryPath = path_manager_default.getFilePath(projectId, "memory", "context.jsonl");
|
|
15356
|
+
const entries = await jsonl_helper_default.readJsonLines(memoryPath);
|
|
15357
|
+
return entries.slice(-limit);
|
|
15358
|
+
} catch (error) {
|
|
15359
|
+
if (!isNotFoundError(error) && !(error instanceof SyntaxError)) {
|
|
15360
|
+
console.error(`Memory read error: ${error.message}`);
|
|
15361
|
+
}
|
|
15362
|
+
return [];
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15116
15365
|
};
|
|
15117
15366
|
memoryService = new MemoryService();
|
|
15118
15367
|
}
|
|
@@ -16583,6 +16832,7 @@ var AnalysisCommands;
|
|
|
16583
16832
|
var init_analysis2 = __esm({
|
|
16584
16833
|
"core/commands/analysis.ts"() {
|
|
16585
16834
|
"use strict";
|
|
16835
|
+
init_memory_system();
|
|
16586
16836
|
init_generator();
|
|
16587
16837
|
init_analyzer2();
|
|
16588
16838
|
init_command_installer();
|
|
@@ -16922,13 +17172,17 @@ ${formatFullDiff(diff)}`);
|
|
|
16922
17172
|
};
|
|
16923
17173
|
}
|
|
16924
17174
|
/**
|
|
16925
|
-
* /p:stats -
|
|
17175
|
+
* /p:stats - Session summary and value dashboard
|
|
16926
17176
|
*
|
|
16927
17177
|
* Displays:
|
|
17178
|
+
* - Session activity (tasks completed, features shipped today)
|
|
17179
|
+
* - Patterns learned (from memory system)
|
|
16928
17180
|
* - Token savings (total, compression rate, estimated cost)
|
|
16929
17181
|
* - Performance metrics (sync count, avg duration)
|
|
16930
17182
|
* - Agent usage breakdown
|
|
16931
17183
|
* - 30-day trend visualization
|
|
17184
|
+
*
|
|
17185
|
+
* @see PRJ-89
|
|
16932
17186
|
*/
|
|
16933
17187
|
async stats(projectPath = process.cwd(), options = {}) {
|
|
16934
17188
|
try {
|
|
@@ -16940,8 +17194,12 @@ ${formatFullDiff(diff)}`);
|
|
|
16940
17194
|
}
|
|
16941
17195
|
const summary = await metricsStorage.getSummary(projectId);
|
|
16942
17196
|
const dailyStats = await metricsStorage.getDailyStats(projectId, 30);
|
|
17197
|
+
const sessionActivity = await this._getSessionActivity(projectId);
|
|
17198
|
+
const patternsSummary = await memory_system_default.getPatternsSummary(projectId);
|
|
16943
17199
|
if (options.json) {
|
|
16944
17200
|
const jsonOutput = {
|
|
17201
|
+
session: sessionActivity,
|
|
17202
|
+
patterns: patternsSummary,
|
|
16945
17203
|
totalTokensSaved: summary.totalTokensSaved,
|
|
16946
17204
|
estimatedCostSaved: summary.estimatedCostSaved,
|
|
16947
17205
|
compressionRate: summary.compressionRate,
|
|
@@ -16958,10 +17216,8 @@ ${formatFullDiff(diff)}`);
|
|
|
16958
17216
|
const globalPath = path_manager_default.getGlobalProjectPath(projectId);
|
|
16959
17217
|
let projectName = "Unknown";
|
|
16960
17218
|
try {
|
|
16961
|
-
const fs47 = __require("node:fs/promises");
|
|
16962
|
-
const path56 = __require("node:path");
|
|
16963
17219
|
const projectJson = JSON.parse(
|
|
16964
|
-
await
|
|
17220
|
+
await fs34.readFile(path35.join(globalPath, "project.json"), "utf-8")
|
|
16965
17221
|
);
|
|
16966
17222
|
projectName = projectJson.name || "Unknown";
|
|
16967
17223
|
} catch {
|
|
@@ -16974,12 +17230,32 @@ ${formatFullDiff(diff)}`);
|
|
|
16974
17230
|
}) : "N/A";
|
|
16975
17231
|
console.log("");
|
|
16976
17232
|
console.log("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
16977
|
-
console.log("\u2502 \u{1F4CA} prjct-cli
|
|
17233
|
+
console.log("\u2502 \u{1F4CA} prjct-cli Stats Dashboard \u2502");
|
|
16978
17234
|
console.log(
|
|
16979
17235
|
`\u2502 Project: ${projectName.padEnd(20).slice(0, 20)} | Since: ${firstSyncDate.padEnd(12).slice(0, 12)} \u2502`
|
|
16980
17236
|
);
|
|
16981
17237
|
console.log("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
16982
17238
|
console.log("");
|
|
17239
|
+
console.log("\u{1F3AF} TODAY'S ACTIVITY");
|
|
17240
|
+
if (sessionActivity.sessionDuration) {
|
|
17241
|
+
console.log(` Duration: ${sessionActivity.sessionDuration}`);
|
|
17242
|
+
}
|
|
17243
|
+
console.log(` Tasks completed: ${sessionActivity.tasksCompleted}`);
|
|
17244
|
+
console.log(` Features shipped: ${sessionActivity.featuresShipped}`);
|
|
17245
|
+
if (sessionActivity.agentsUsed.length > 0) {
|
|
17246
|
+
const agentStr = sessionActivity.agentsUsed.slice(0, 3).map((a) => `${a.name} (${a.count}\xD7)`).join(", ");
|
|
17247
|
+
console.log(` Agents used: ${agentStr}`);
|
|
17248
|
+
}
|
|
17249
|
+
console.log("");
|
|
17250
|
+
if (patternsSummary.decisions > 0 || patternsSummary.preferences > 0) {
|
|
17251
|
+
console.log("\u{1F9E0} PATTERNS LEARNED");
|
|
17252
|
+
console.log(
|
|
17253
|
+
` Decisions: ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total)`
|
|
17254
|
+
);
|
|
17255
|
+
console.log(` Preferences: ${patternsSummary.preferences} saved`);
|
|
17256
|
+
console.log(` Workflows: ${patternsSummary.workflows} tracked`);
|
|
17257
|
+
console.log("");
|
|
17258
|
+
}
|
|
16983
17259
|
console.log("\u{1F4B0} TOKEN SAVINGS");
|
|
16984
17260
|
console.log(` Total saved: ${this._formatTokens(summary.totalTokensSaved)} tokens`);
|
|
16985
17261
|
console.log(
|
|
@@ -16992,7 +17268,7 @@ ${formatFullDiff(diff)}`);
|
|
|
16992
17268
|
console.log(` Avg sync time: ${this._formatDuration(summary.avgSyncDuration)}`);
|
|
16993
17269
|
console.log("");
|
|
16994
17270
|
if (summary.topAgents.length > 0) {
|
|
16995
|
-
console.log("\u{1F916} AGENT USAGE");
|
|
17271
|
+
console.log("\u{1F916} AGENT USAGE (all time)");
|
|
16996
17272
|
const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0);
|
|
16997
17273
|
for (const agent of summary.topAgents) {
|
|
16998
17274
|
const pct = totalUsage > 0 ? (agent.usageCount / totalUsage * 100).toFixed(0) : 0;
|
|
@@ -17021,20 +17297,68 @@ ${formatFullDiff(diff)}`);
|
|
|
17021
17297
|
summary,
|
|
17022
17298
|
dailyStats,
|
|
17023
17299
|
projectName,
|
|
17024
|
-
firstSyncDate
|
|
17300
|
+
firstSyncDate,
|
|
17301
|
+
sessionActivity,
|
|
17302
|
+
patternsSummary
|
|
17025
17303
|
);
|
|
17026
17304
|
console.log(markdown);
|
|
17027
17305
|
return { success: true, data: { markdown } };
|
|
17028
17306
|
}
|
|
17029
17307
|
return {
|
|
17030
17308
|
success: true,
|
|
17031
|
-
data: summary
|
|
17309
|
+
data: { ...summary, session: sessionActivity, patterns: patternsSummary }
|
|
17032
17310
|
};
|
|
17033
17311
|
} catch (error) {
|
|
17034
17312
|
console.error("\u274C Error:", error.message);
|
|
17035
17313
|
return { success: false, error: error.message };
|
|
17036
17314
|
}
|
|
17037
17315
|
}
|
|
17316
|
+
/**
|
|
17317
|
+
* Get session activity stats from today's events
|
|
17318
|
+
* @see PRJ-89
|
|
17319
|
+
*/
|
|
17320
|
+
async _getSessionActivity(projectId) {
|
|
17321
|
+
try {
|
|
17322
|
+
const recentHistory = await memoryService.getRecentEvents(projectId, 100);
|
|
17323
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
17324
|
+
const todayEvents = recentHistory.filter((e) => {
|
|
17325
|
+
const ts = e.timestamp || e.ts;
|
|
17326
|
+
return ts?.startsWith(today);
|
|
17327
|
+
});
|
|
17328
|
+
let sessionDuration = null;
|
|
17329
|
+
if (todayEvents.length >= 2) {
|
|
17330
|
+
const timestamps = todayEvents.map((e) => new Date(e.timestamp || e.ts).getTime()).filter((t) => !Number.isNaN(t)).sort((a, b) => a - b);
|
|
17331
|
+
if (timestamps.length >= 2) {
|
|
17332
|
+
const durationMs = timestamps[timestamps.length - 1] - timestamps[0];
|
|
17333
|
+
sessionDuration = date_helper_default.formatDuration(durationMs);
|
|
17334
|
+
}
|
|
17335
|
+
}
|
|
17336
|
+
const tasksCompleted = todayEvents.filter((e) => e.action === "task_completed").length;
|
|
17337
|
+
const featuresShipped = todayEvents.filter((e) => e.action === "feature_shipped").length;
|
|
17338
|
+
const agentCounts = /* @__PURE__ */ new Map();
|
|
17339
|
+
for (const event of todayEvents) {
|
|
17340
|
+
if (event.action === "sync" && Array.isArray(event.subagents)) {
|
|
17341
|
+
for (const agent of event.subagents) {
|
|
17342
|
+
agentCounts.set(agent, (agentCounts.get(agent) || 0) + 1);
|
|
17343
|
+
}
|
|
17344
|
+
}
|
|
17345
|
+
}
|
|
17346
|
+
const agentsUsed = Array.from(agentCounts.entries()).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
|
|
17347
|
+
return {
|
|
17348
|
+
sessionDuration,
|
|
17349
|
+
tasksCompleted,
|
|
17350
|
+
featuresShipped,
|
|
17351
|
+
agentsUsed
|
|
17352
|
+
};
|
|
17353
|
+
} catch {
|
|
17354
|
+
return {
|
|
17355
|
+
sessionDuration: null,
|
|
17356
|
+
tasksCompleted: 0,
|
|
17357
|
+
featuresShipped: 0,
|
|
17358
|
+
agentsUsed: []
|
|
17359
|
+
};
|
|
17360
|
+
}
|
|
17361
|
+
}
|
|
17038
17362
|
// =========== Stats Helper Methods ===========
|
|
17039
17363
|
_formatTokens(tokens) {
|
|
17040
17364
|
if (tokens >= 1e6) {
|
|
@@ -17061,12 +17385,40 @@ ${formatFullDiff(diff)}`);
|
|
|
17061
17385
|
return chars[idx];
|
|
17062
17386
|
}).join("");
|
|
17063
17387
|
}
|
|
17064
|
-
_generateStatsMarkdown(summary, _dailyStats, projectName, firstSyncDate) {
|
|
17388
|
+
_generateStatsMarkdown(summary, _dailyStats, projectName, firstSyncDate, sessionActivity, patternsSummary) {
|
|
17065
17389
|
const lines = [];
|
|
17066
|
-
lines.push(`# ${projectName} -
|
|
17390
|
+
lines.push(`# ${projectName} - Stats Dashboard`);
|
|
17067
17391
|
lines.push("");
|
|
17068
17392
|
lines.push(`_Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()} | Tracking since: ${firstSyncDate}_`);
|
|
17069
17393
|
lines.push("");
|
|
17394
|
+
if (sessionActivity) {
|
|
17395
|
+
lines.push("## \u{1F3AF} Today's Activity");
|
|
17396
|
+
lines.push("");
|
|
17397
|
+
lines.push(`| Metric | Value |`);
|
|
17398
|
+
lines.push(`|--------|-------|`);
|
|
17399
|
+
if (sessionActivity.sessionDuration) {
|
|
17400
|
+
lines.push(`| Duration | ${sessionActivity.sessionDuration} |`);
|
|
17401
|
+
}
|
|
17402
|
+
lines.push(`| Tasks completed | ${sessionActivity.tasksCompleted} |`);
|
|
17403
|
+
lines.push(`| Features shipped | ${sessionActivity.featuresShipped} |`);
|
|
17404
|
+
if (sessionActivity.agentsUsed.length > 0) {
|
|
17405
|
+
const agentStr = sessionActivity.agentsUsed.slice(0, 3).map((a) => `${a.name} (${a.count}\xD7)`).join(", ");
|
|
17406
|
+
lines.push(`| Agents used | ${agentStr} |`);
|
|
17407
|
+
}
|
|
17408
|
+
lines.push("");
|
|
17409
|
+
}
|
|
17410
|
+
if (patternsSummary && (patternsSummary.decisions > 0 || patternsSummary.preferences > 0)) {
|
|
17411
|
+
lines.push("## \u{1F9E0} Patterns Learned");
|
|
17412
|
+
lines.push("");
|
|
17413
|
+
lines.push(`| Type | Count |`);
|
|
17414
|
+
lines.push(`|------|-------|`);
|
|
17415
|
+
lines.push(
|
|
17416
|
+
`| Decisions | ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total) |`
|
|
17417
|
+
);
|
|
17418
|
+
lines.push(`| Preferences | ${patternsSummary.preferences} |`);
|
|
17419
|
+
lines.push(`| Workflows | ${patternsSummary.workflows} |`);
|
|
17420
|
+
lines.push("");
|
|
17421
|
+
}
|
|
17070
17422
|
lines.push("## \u{1F4B0} Token Savings");
|
|
17071
17423
|
lines.push("");
|
|
17072
17424
|
lines.push(`| Metric | Value |`);
|
|
@@ -24664,7 +25016,7 @@ var require_package = __commonJS({
|
|
|
24664
25016
|
"package.json"(exports, module) {
|
|
24665
25017
|
module.exports = {
|
|
24666
25018
|
name: "prjct-cli",
|
|
24667
|
-
version: "0.
|
|
25019
|
+
version: "0.54.0",
|
|
24668
25020
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
24669
25021
|
main: "core/index.ts",
|
|
24670
25022
|
bin: {
|