aibridge-context 1.3.0 → 1.4.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/core/stateManager.js +666 -165
- package/package.json +1 -1
package/core/stateManager.js
CHANGED
|
@@ -5,10 +5,80 @@ const fsp = require('fs/promises');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
7
|
const CONTEXT_DIR_NAME = '.ai-context';
|
|
8
|
-
const MAX_RECENT_UPDATES =
|
|
8
|
+
const MAX_RECENT_UPDATES = 5;
|
|
9
9
|
const MAX_CHANGELOG_ENTRIES = 50;
|
|
10
|
+
const MAX_KEY_FEATURES = 6;
|
|
10
11
|
const IMPORTANT_DIRECTORIES = ['core/', 'server/', 'bin/'];
|
|
11
12
|
const IMPORTANT_EXTENSIONS = new Set(['.js', '.ts', '.py']);
|
|
13
|
+
const LOW_VALUE_FEATURE_KEYS = new Set([
|
|
14
|
+
'documentation',
|
|
15
|
+
'package_configuration',
|
|
16
|
+
'context_templates',
|
|
17
|
+
'project_workflow'
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const FEATURE_CATALOG = {
|
|
21
|
+
cli_workflow: {
|
|
22
|
+
name: 'CLI workflow for initializing, updating, and linking AI context',
|
|
23
|
+
subject: 'CLI workflow',
|
|
24
|
+
projectType: 'CLI tool'
|
|
25
|
+
},
|
|
26
|
+
github_sync: {
|
|
27
|
+
name: 'Public GitHub sync for AI-readable project context',
|
|
28
|
+
subject: 'GitHub sync system',
|
|
29
|
+
projectType: 'CLI tool'
|
|
30
|
+
},
|
|
31
|
+
project_intelligence: {
|
|
32
|
+
name: 'Project intelligence engine that turns development activity into AI-readable state',
|
|
33
|
+
subject: 'project intelligence engine',
|
|
34
|
+
projectType: 'project intelligence engine'
|
|
35
|
+
},
|
|
36
|
+
change_tracking: {
|
|
37
|
+
name: 'Meaningful change tracking that filters noise from project activity',
|
|
38
|
+
subject: 'change tracking engine',
|
|
39
|
+
projectType: 'change tracking engine'
|
|
40
|
+
},
|
|
41
|
+
local_context_server: {
|
|
42
|
+
name: 'Local server for AI-readable project context endpoints',
|
|
43
|
+
subject: 'AI context delivery service',
|
|
44
|
+
projectType: 'context delivery service'
|
|
45
|
+
},
|
|
46
|
+
context_delivery_system: {
|
|
47
|
+
name: 'Unified context delivery system connecting project intelligence and serving layers',
|
|
48
|
+
subject: 'AI context delivery system',
|
|
49
|
+
projectType: 'AI context system'
|
|
50
|
+
},
|
|
51
|
+
cli_orchestration: {
|
|
52
|
+
name: 'Command workflow that connects project intelligence with developer actions',
|
|
53
|
+
subject: 'CLI workflow',
|
|
54
|
+
projectType: 'CLI tool'
|
|
55
|
+
},
|
|
56
|
+
project_setup: {
|
|
57
|
+
name: 'Guided setup flow for safe AI context initialization',
|
|
58
|
+
subject: 'project setup flow',
|
|
59
|
+
projectType: 'CLI tool'
|
|
60
|
+
},
|
|
61
|
+
documentation: {
|
|
62
|
+
name: 'Developer guidance for adopting the AI context workflow',
|
|
63
|
+
subject: 'developer guidance',
|
|
64
|
+
projectType: 'project'
|
|
65
|
+
},
|
|
66
|
+
package_configuration: {
|
|
67
|
+
name: 'Package configuration for distributing the AI context CLI',
|
|
68
|
+
subject: 'package configuration',
|
|
69
|
+
projectType: 'package'
|
|
70
|
+
},
|
|
71
|
+
context_templates: {
|
|
72
|
+
name: 'Generated templates for bootstrapping AI-readable project context',
|
|
73
|
+
subject: 'generated AI context templates',
|
|
74
|
+
projectType: 'template set'
|
|
75
|
+
},
|
|
76
|
+
project_workflow: {
|
|
77
|
+
name: 'Core project workflow for maintaining AI-readable project state',
|
|
78
|
+
subject: 'project workflow',
|
|
79
|
+
projectType: 'project'
|
|
80
|
+
}
|
|
81
|
+
};
|
|
12
82
|
|
|
13
83
|
const DEFAULT_CONFIG = {
|
|
14
84
|
port: 3333,
|
|
@@ -237,24 +307,21 @@ async function writeTextAtomic(filePath, content) {
|
|
|
237
307
|
|
|
238
308
|
function createDefaultState(projectRoot) {
|
|
239
309
|
const metadata = detectProjectMetadata(projectRoot);
|
|
240
|
-
const keyFeatures = deriveKeyFeatures(projectRoot);
|
|
241
|
-
const knownIssues = deriveKnownIssues(projectRoot, metadata.techStack);
|
|
242
|
-
const currentStage = determineCurrentStage(keyFeatures);
|
|
243
310
|
const state = {
|
|
244
311
|
project: metadata.project,
|
|
245
312
|
version: metadata.version,
|
|
246
313
|
last_updated: new Date(0).toISOString(),
|
|
247
314
|
ai_summary: '',
|
|
248
315
|
tech_stack: metadata.techStack,
|
|
249
|
-
current_stage:
|
|
316
|
+
current_stage: 'Early development',
|
|
250
317
|
recent_updates: [],
|
|
251
|
-
key_features:
|
|
252
|
-
known_issues:
|
|
318
|
+
key_features: [],
|
|
319
|
+
known_issues: deriveKnownIssues(projectRoot, metadata.techStack, []),
|
|
253
320
|
next_steps: []
|
|
254
321
|
};
|
|
255
322
|
|
|
256
|
-
state.ai_summary = generateAiSummary(state);
|
|
257
|
-
state.next_steps = generateNextSteps(state);
|
|
323
|
+
state.ai_summary = generateAiSummary(state, []);
|
|
324
|
+
state.next_steps = generateNextSteps(state, []);
|
|
258
325
|
|
|
259
326
|
return state;
|
|
260
327
|
}
|
|
@@ -310,40 +377,39 @@ async function updateProjectState(projectRoot, changeEvent, options) {
|
|
|
310
377
|
const meaningfulEvents = collapseEventsByFile(
|
|
311
378
|
validEvents.filter((event) => isMeaningfulEvent(event))
|
|
312
379
|
);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
.filter(Boolean);
|
|
316
|
-
const previousRecentUpdates = normalizeStoredUpdates(existingState.recent_updates);
|
|
380
|
+
const groupedUpdates = groupEventsByIntent(meaningfulEvents);
|
|
381
|
+
const capabilityHistory = buildProjectCapabilityHistory(projectRoot);
|
|
317
382
|
const previousHistoryEntries = normalizeStoredHistoryEntries(existingChangelog.entries);
|
|
318
|
-
const recentUpdates = dedupeRecentUpdates(
|
|
319
|
-
interpretedEvents.map(toStateUpdate).concat(previousRecentUpdates)
|
|
320
|
-
).slice(0, MAX_RECENT_UPDATES);
|
|
321
383
|
const historyEntries = dedupeHistoryEntries(
|
|
322
|
-
|
|
384
|
+
groupedUpdates.concat(previousHistoryEntries, capabilityHistory)
|
|
323
385
|
).slice(0, MAX_CHANGELOG_ENTRIES);
|
|
324
|
-
const
|
|
325
|
-
|
|
386
|
+
const recentUpdates = historyEntries
|
|
387
|
+
.filter((entry) => entry.source !== 'project_snapshot')
|
|
388
|
+
.slice(0, MAX_RECENT_UPDATES)
|
|
389
|
+
.map(toStateUpdate);
|
|
390
|
+
const keyFeatures = promoteFeatures(historyEntries);
|
|
391
|
+
const knownIssues = deriveKnownIssues(projectRoot, metadata.techStack, keyFeatures);
|
|
326
392
|
const nextState = {
|
|
327
393
|
project: metadata.project,
|
|
328
394
|
version: metadata.version,
|
|
329
395
|
last_updated: timestamp,
|
|
330
396
|
ai_summary: '',
|
|
331
397
|
tech_stack: metadata.techStack,
|
|
332
|
-
current_stage: determineCurrentStage(keyFeatures),
|
|
398
|
+
current_stage: determineCurrentStage(keyFeatures, historyEntries),
|
|
333
399
|
recent_updates: recentUpdates,
|
|
334
400
|
key_features: keyFeatures,
|
|
335
401
|
known_issues: knownIssues,
|
|
336
402
|
next_steps: []
|
|
337
403
|
};
|
|
338
404
|
|
|
339
|
-
nextState.ai_summary = generateAiSummary(nextState);
|
|
340
|
-
nextState.next_steps = generateNextSteps(nextState);
|
|
405
|
+
nextState.ai_summary = generateAiSummary(nextState, historyEntries);
|
|
406
|
+
nextState.next_steps = generateNextSteps(nextState, historyEntries);
|
|
341
407
|
|
|
342
408
|
await writeJsonAtomic(contextPaths.stateFile, nextState);
|
|
343
409
|
await writeJsonAtomic(contextPaths.changelogFile, { entries: historyEntries });
|
|
344
410
|
|
|
345
411
|
if (logger) {
|
|
346
|
-
logger.debug(`Updated AI context with ${
|
|
412
|
+
logger.debug(`Updated AI context with ${groupedUpdates.length} grouped project intent(s).`);
|
|
347
413
|
}
|
|
348
414
|
|
|
349
415
|
if (typeof settings.syncCallback === 'function') {
|
|
@@ -460,22 +526,6 @@ function collapseEventsByFile(events) {
|
|
|
460
526
|
return Array.from(collapsedEvents.values());
|
|
461
527
|
}
|
|
462
528
|
|
|
463
|
-
function interpretChange(event) {
|
|
464
|
-
const filePath = normalizeProjectPath(event.file);
|
|
465
|
-
const area = classifyChangeArea(filePath);
|
|
466
|
-
const subject = describeChangeSubject(filePath, area);
|
|
467
|
-
const type = mapActionToType(event.action);
|
|
468
|
-
const title = `${mapActionToVerb(event.action)} ${subject}`;
|
|
469
|
-
|
|
470
|
-
return {
|
|
471
|
-
timestamp: event.timestamp || new Date().toISOString(),
|
|
472
|
-
file: filePath,
|
|
473
|
-
title,
|
|
474
|
-
type,
|
|
475
|
-
impact: describeImpact(area, subject, event.action)
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
529
|
function classifyChangeArea(filePath) {
|
|
480
530
|
const lowerPath = filePath.toLowerCase();
|
|
481
531
|
|
|
@@ -496,7 +546,7 @@ function classifyChangeArea(filePath) {
|
|
|
496
546
|
}
|
|
497
547
|
|
|
498
548
|
if (lowerPath.startsWith('bin/')) {
|
|
499
|
-
return '
|
|
549
|
+
return 'cli';
|
|
500
550
|
}
|
|
501
551
|
|
|
502
552
|
if (lowerPath.startsWith('templates/')) {
|
|
@@ -506,135 +556,335 @@ function classifyChangeArea(filePath) {
|
|
|
506
556
|
return 'project';
|
|
507
557
|
}
|
|
508
558
|
|
|
509
|
-
function
|
|
559
|
+
function describeRootDirectory(filePath) {
|
|
560
|
+
const normalizedPath = normalizeProjectPath(filePath);
|
|
561
|
+
const segments = normalizedPath.split('/');
|
|
562
|
+
|
|
563
|
+
if (segments.length === 1) {
|
|
564
|
+
return segments[0] || 'project';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return segments[0] || 'project';
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function detectIntentTheme(filePath, area) {
|
|
510
571
|
const lowerPath = filePath.toLowerCase();
|
|
511
|
-
const baseName = path.basename(filePath);
|
|
512
572
|
|
|
513
573
|
if (lowerPath === 'package.json') {
|
|
514
|
-
return '
|
|
574
|
+
return 'package_configuration';
|
|
515
575
|
}
|
|
516
576
|
|
|
517
577
|
if (lowerPath === 'readme.md') {
|
|
518
578
|
return 'documentation';
|
|
519
579
|
}
|
|
520
580
|
|
|
521
|
-
if (lowerPath
|
|
522
|
-
return '
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (lowerPath === 'core/statemanager.js') {
|
|
526
|
-
return 'state intelligence logic';
|
|
581
|
+
if (lowerPath.startsWith('bin/')) {
|
|
582
|
+
return 'cli_workflow';
|
|
527
583
|
}
|
|
528
584
|
|
|
529
|
-
if (lowerPath
|
|
530
|
-
return '
|
|
585
|
+
if (lowerPath.startsWith('server/')) {
|
|
586
|
+
return 'local_context_server';
|
|
531
587
|
}
|
|
532
588
|
|
|
533
|
-
if (lowerPath
|
|
534
|
-
return '
|
|
589
|
+
if (lowerPath.startsWith('templates/')) {
|
|
590
|
+
return 'context_templates';
|
|
535
591
|
}
|
|
536
592
|
|
|
537
|
-
if (lowerPath
|
|
538
|
-
return '
|
|
593
|
+
if (lowerPath.includes('gitsync') || lowerPath.includes('sync')) {
|
|
594
|
+
return 'github_sync';
|
|
539
595
|
}
|
|
540
596
|
|
|
541
|
-
if (lowerPath
|
|
542
|
-
return '
|
|
597
|
+
if (lowerPath.includes('watcher') || lowerPath.includes('watch')) {
|
|
598
|
+
return 'change_tracking';
|
|
543
599
|
}
|
|
544
600
|
|
|
545
|
-
if (lowerPath
|
|
546
|
-
return '
|
|
601
|
+
if (lowerPath.includes('state') || lowerPath.includes('context')) {
|
|
602
|
+
return 'project_intelligence';
|
|
547
603
|
}
|
|
548
604
|
|
|
549
|
-
if (
|
|
550
|
-
return
|
|
605
|
+
if (lowerPath.includes('init')) {
|
|
606
|
+
return 'project_setup';
|
|
551
607
|
}
|
|
552
608
|
|
|
553
609
|
if (area === 'logic') {
|
|
554
|
-
return
|
|
610
|
+
return 'project_intelligence';
|
|
555
611
|
}
|
|
556
612
|
|
|
557
|
-
|
|
558
|
-
|
|
613
|
+
return 'project_workflow';
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function createEventDescriptor(event) {
|
|
617
|
+
const normalizedPath = normalizeProjectPath(event.file);
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
timestamp: event.timestamp || new Date().toISOString(),
|
|
621
|
+
action: event.action || 'change',
|
|
622
|
+
file: normalizedPath,
|
|
623
|
+
area: classifyChangeArea(normalizedPath),
|
|
624
|
+
rootDirectory: describeRootDirectory(normalizedPath),
|
|
625
|
+
theme: detectIntentTheme(normalizedPath, classifyChangeArea(normalizedPath))
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function groupEventsByIntent(events) {
|
|
630
|
+
if (!Array.isArray(events) || events.length === 0) {
|
|
631
|
+
return [];
|
|
559
632
|
}
|
|
560
633
|
|
|
561
|
-
|
|
562
|
-
|
|
634
|
+
const groupedByArea = new Map();
|
|
635
|
+
|
|
636
|
+
for (const event of events) {
|
|
637
|
+
const descriptor = createEventDescriptor(event);
|
|
638
|
+
const groupKey = `${descriptor.area}:${descriptor.rootDirectory}`;
|
|
639
|
+
|
|
640
|
+
if (!groupedByArea.has(groupKey)) {
|
|
641
|
+
groupedByArea.set(groupKey, {
|
|
642
|
+
area: descriptor.area,
|
|
643
|
+
rootDirectory: descriptor.rootDirectory,
|
|
644
|
+
events: []
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
groupedByArea.get(groupKey).events.push(descriptor);
|
|
563
649
|
}
|
|
564
650
|
|
|
565
|
-
|
|
651
|
+
const mergedGroups = mergeCrossAreaIntentGroups(Array.from(groupedByArea.values()));
|
|
652
|
+
|
|
653
|
+
return mergedGroups
|
|
654
|
+
.map((group) => interpretIntentGroup(group))
|
|
655
|
+
.filter(Boolean)
|
|
656
|
+
.sort((left, right) => new Date(right.timestamp) - new Date(left.timestamp));
|
|
566
657
|
}
|
|
567
658
|
|
|
568
|
-
function
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
659
|
+
function buildProjectCapabilityHistory(projectRoot) {
|
|
660
|
+
const snapshotTimestamp = new Date(0).toISOString();
|
|
661
|
+
const projectFiles = scanProjectFiles(projectRoot, 2).filter((filePath) => scoreEvent(filePath) >= 2);
|
|
662
|
+
|
|
663
|
+
if (projectFiles.length === 0) {
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const capabilityBuckets = new Map();
|
|
668
|
+
|
|
669
|
+
for (const filePath of projectFiles) {
|
|
670
|
+
const area = classifyChangeArea(filePath);
|
|
671
|
+
const featureKey = detectIntentTheme(filePath, area);
|
|
672
|
+
|
|
673
|
+
if (!capabilityBuckets.has(featureKey)) {
|
|
674
|
+
capabilityBuckets.set(featureKey, []);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
capabilityBuckets.get(featureKey).push(filePath);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return Array.from(capabilityBuckets.entries())
|
|
681
|
+
.map(([featureKey, files]) => createCapabilitySnapshotEntry(featureKey, files.length, snapshotTimestamp))
|
|
682
|
+
.filter(Boolean);
|
|
574
683
|
}
|
|
575
684
|
|
|
576
|
-
function
|
|
577
|
-
if (
|
|
578
|
-
return
|
|
685
|
+
function mergeCrossAreaIntentGroups(groups) {
|
|
686
|
+
if (groups.length < 2) {
|
|
687
|
+
return groups;
|
|
579
688
|
}
|
|
580
689
|
|
|
581
|
-
|
|
582
|
-
|
|
690
|
+
const logicGroup = groups.find((group) => group.area === 'logic');
|
|
691
|
+
const backendGroup = groups.find((group) => group.area === 'backend');
|
|
692
|
+
const cliGroup = groups.find((group) => group.area === 'cli');
|
|
693
|
+
|
|
694
|
+
if (logicGroup && backendGroup && groups.length <= 3) {
|
|
695
|
+
return mergeSelectedGroups(groups, [logicGroup, backendGroup], 'system');
|
|
583
696
|
}
|
|
584
697
|
|
|
585
|
-
|
|
698
|
+
if (logicGroup && cliGroup && groups.length <= 3) {
|
|
699
|
+
return mergeSelectedGroups(groups, [logicGroup, cliGroup], 'cli_system');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return groups;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function mergeSelectedGroups(groups, groupsToMerge, mergedArea) {
|
|
706
|
+
const mergeSet = new Set(groupsToMerge);
|
|
707
|
+
const remainingGroups = groups.filter((group) => !mergeSet.has(group));
|
|
708
|
+
const mergedGroup = {
|
|
709
|
+
area: mergedArea,
|
|
710
|
+
rootDirectory: mergedArea,
|
|
711
|
+
events: groupsToMerge.flatMap((group) => group.events)
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
remainingGroups.push(mergedGroup);
|
|
715
|
+
return remainingGroups;
|
|
586
716
|
}
|
|
587
717
|
|
|
588
|
-
function
|
|
589
|
-
if (
|
|
590
|
-
return
|
|
718
|
+
function interpretIntentGroup(group) {
|
|
719
|
+
if (!group || !Array.isArray(group.events) || group.events.length === 0) {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const latestTimestamp = group.events.reduce((latest, event) => {
|
|
724
|
+
return new Date(event.timestamp) > new Date(latest) ? event.timestamp : latest;
|
|
725
|
+
}, group.events[0].timestamp);
|
|
726
|
+
const featureKey = determineFeatureKey(group);
|
|
727
|
+
const featureMeta = getFeatureMeta(featureKey);
|
|
728
|
+
const type = determineGroupedUpdateType(group);
|
|
729
|
+
const subject = describeIntentSubject(group, featureMeta.subject);
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
timestamp: latestTimestamp,
|
|
733
|
+
scope: describeIntentScope(group),
|
|
734
|
+
title: buildIntentTitle(type, subject),
|
|
735
|
+
type,
|
|
736
|
+
impact: describeIntentImpact(type, featureKey, subject),
|
|
737
|
+
feature_key: featureKey,
|
|
738
|
+
feature_name: featureMeta.name,
|
|
739
|
+
source: 'event'
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function determineFeatureKey(group) {
|
|
744
|
+
const areas = new Set(group.events.map((event) => event.area));
|
|
745
|
+
|
|
746
|
+
if (group.area === 'system' || (areas.has('logic') && areas.has('backend'))) {
|
|
747
|
+
return 'context_delivery_system';
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (group.area === 'cli_system' || (areas.has('logic') && areas.has('cli'))) {
|
|
751
|
+
return 'cli_orchestration';
|
|
591
752
|
}
|
|
592
753
|
|
|
593
|
-
|
|
594
|
-
|
|
754
|
+
const themeCounts = new Map();
|
|
755
|
+
|
|
756
|
+
for (const event of group.events) {
|
|
757
|
+
themeCounts.set(event.theme, (themeCounts.get(event.theme) || 0) + 1);
|
|
595
758
|
}
|
|
596
759
|
|
|
597
|
-
return
|
|
760
|
+
return Array.from(themeCounts.entries()).sort((left, right) => {
|
|
761
|
+
if (right[1] !== left[1]) {
|
|
762
|
+
return right[1] - left[1];
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return getFeaturePriority(right[0]) - getFeaturePriority(left[0]);
|
|
766
|
+
})[0][0];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function getFeatureMeta(featureKey) {
|
|
770
|
+
return FEATURE_CATALOG[featureKey] || FEATURE_CATALOG.project_workflow;
|
|
598
771
|
}
|
|
599
772
|
|
|
600
|
-
function
|
|
601
|
-
|
|
602
|
-
|
|
773
|
+
function getFeaturePriority(featureKey) {
|
|
774
|
+
const priorities = {
|
|
775
|
+
project_intelligence: 7,
|
|
776
|
+
github_sync: 6,
|
|
777
|
+
local_context_server: 5,
|
|
778
|
+
change_tracking: 4,
|
|
779
|
+
cli_workflow: 3,
|
|
780
|
+
project_setup: 2,
|
|
781
|
+
project_workflow: 1
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
return priorities[featureKey] || 0;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function determineGroupedUpdateType(group) {
|
|
788
|
+
const actions = new Set(group.events.map((event) => event.action));
|
|
789
|
+
const fileCount = group.events.length;
|
|
790
|
+
|
|
791
|
+
if (actions.has('add')) {
|
|
792
|
+
return 'feature';
|
|
603
793
|
}
|
|
604
794
|
|
|
605
|
-
if (
|
|
606
|
-
return '
|
|
795
|
+
if (hasFixSignals(group.events)) {
|
|
796
|
+
return 'fix';
|
|
607
797
|
}
|
|
608
798
|
|
|
609
|
-
if (
|
|
610
|
-
return '
|
|
799
|
+
if (fileCount > 2 || group.area === 'system' || group.area === 'cli_system') {
|
|
800
|
+
return 'refactor';
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return 'improvement';
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function hasFixSignals(events) {
|
|
807
|
+
return events.some((event) =>
|
|
808
|
+
/(fix|bug|error|guard|validate|sanitize|safe|stabilize)/i.test(event.file)
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function describeIntentSubject(group, fallbackSubject) {
|
|
813
|
+
const areas = new Set(group.events.map((event) => event.area));
|
|
814
|
+
|
|
815
|
+
if (group.area === 'system' || (areas.has('logic') && areas.has('backend'))) {
|
|
816
|
+
return 'AI context delivery system';
|
|
611
817
|
}
|
|
612
818
|
|
|
613
|
-
if (
|
|
614
|
-
return '
|
|
819
|
+
if (group.area === 'cli_system' || (areas.has('logic') && areas.has('cli'))) {
|
|
820
|
+
return 'CLI workflow';
|
|
615
821
|
}
|
|
616
822
|
|
|
617
|
-
if (area === 'backend') {
|
|
618
|
-
return '
|
|
823
|
+
if (group.area === 'backend' && group.events.length > 1) {
|
|
824
|
+
return 'AI context delivery service';
|
|
619
825
|
}
|
|
620
826
|
|
|
621
|
-
|
|
622
|
-
|
|
827
|
+
return fallbackSubject || 'project workflow';
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function describeIntentScope(group) {
|
|
831
|
+
if (group.area === 'system') {
|
|
832
|
+
return 'system';
|
|
623
833
|
}
|
|
624
834
|
|
|
625
|
-
if (area === '
|
|
626
|
-
return '
|
|
835
|
+
if (group.area === 'cli_system') {
|
|
836
|
+
return 'CLI';
|
|
627
837
|
}
|
|
628
838
|
|
|
629
|
-
|
|
630
|
-
|
|
839
|
+
return group.area;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function buildIntentTitle(type, subject) {
|
|
843
|
+
const verbs = {
|
|
844
|
+
feature: 'Expanded',
|
|
845
|
+
improvement: 'Improved',
|
|
846
|
+
refactor: 'Refactored',
|
|
847
|
+
fix: 'Stabilized'
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
return `${verbs[type] || 'Improved'} ${subject}`;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function describeIntentImpact(type, featureKey, subject) {
|
|
854
|
+
const impactByFeature = {
|
|
855
|
+
cli_workflow: 'Improves how developers initialize and manage AI context from the command line.',
|
|
856
|
+
github_sync: 'Improves reliability of publishing AI-readable project context to GitHub.',
|
|
857
|
+
project_intelligence: 'Improves how project progress is summarized for AI systems.',
|
|
858
|
+
change_tracking: 'Improves how meaningful project evolution is detected without noise.',
|
|
859
|
+
local_context_server: 'Improves how AI tools consume project context through local endpoints.',
|
|
860
|
+
context_delivery_system: 'Improves reliability and structure of the end-to-end AI context delivery system.',
|
|
861
|
+
cli_orchestration: 'Improves how CLI actions drive the project intelligence workflow.',
|
|
862
|
+
project_setup: 'Improves first-run setup and configuration clarity for teams adopting AI context.',
|
|
863
|
+
documentation: 'Improves onboarding and usage clarity for developers and AI collaborators.',
|
|
864
|
+
package_configuration: 'Improves package installation and distribution behavior.',
|
|
865
|
+
context_templates: 'Improves the default AI context generated for new projects.',
|
|
866
|
+
project_workflow: 'Improves the overall project workflow for maintaining AI-readable context.'
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
if (type === 'feature') {
|
|
870
|
+
return impactByFeature[featureKey]
|
|
871
|
+
.replace(/^Improves /, 'Adds ')
|
|
872
|
+
.replace(/^Improves how /, 'Adds ')
|
|
873
|
+
.replace(/^Improves reliability of /, 'Adds ')
|
|
874
|
+
.replace(/^Improves first-run setup and configuration clarity for teams adopting /, 'Adds ')
|
|
875
|
+
.replace(/^Improves the default AI context generated for /, 'Adds ')
|
|
876
|
+
.replace(/^Improves the overall project workflow for maintaining /, 'Adds ');
|
|
631
877
|
}
|
|
632
878
|
|
|
633
|
-
if (
|
|
634
|
-
return
|
|
879
|
+
if (type === 'fix') {
|
|
880
|
+
return `Resolves reliability issues in the ${subject.toLowerCase()}.`;
|
|
635
881
|
}
|
|
636
882
|
|
|
637
|
-
return 'Improves
|
|
883
|
+
return impactByFeature[featureKey] || 'Improves the overall project workflow for maintaining AI-readable context.';
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function interpretChange(event) {
|
|
887
|
+
return groupEventsByIntent([event])[0] || null;
|
|
638
888
|
}
|
|
639
889
|
|
|
640
890
|
function normalizeStoredUpdates(updates) {
|
|
@@ -642,11 +892,7 @@ function normalizeStoredUpdates(updates) {
|
|
|
642
892
|
return [];
|
|
643
893
|
}
|
|
644
894
|
|
|
645
|
-
return dedupeRecentUpdates(
|
|
646
|
-
updates
|
|
647
|
-
.map((update) => normalizeStoredUpdate(update))
|
|
648
|
-
.filter(Boolean)
|
|
649
|
-
);
|
|
895
|
+
return dedupeRecentUpdates(updates.map((update) => normalizeStoredUpdate(update)).filter(Boolean));
|
|
650
896
|
}
|
|
651
897
|
|
|
652
898
|
function normalizeStoredUpdate(update) {
|
|
@@ -657,7 +903,7 @@ function normalizeStoredUpdate(update) {
|
|
|
657
903
|
if (update.title && update.type && update.impact) {
|
|
658
904
|
return {
|
|
659
905
|
title: update.title,
|
|
660
|
-
type: update.type,
|
|
906
|
+
type: normalizeUpdateType(update.type),
|
|
661
907
|
impact: update.impact
|
|
662
908
|
};
|
|
663
909
|
}
|
|
@@ -687,12 +933,20 @@ function normalizeStoredHistoryEntry(entry) {
|
|
|
687
933
|
}
|
|
688
934
|
|
|
689
935
|
if (entry.title && entry.type && entry.impact) {
|
|
936
|
+
const inferredFeature = inferFeatureFromEntry(entry);
|
|
937
|
+
const featureKey = entry.feature_key || inferredFeature.featureKey;
|
|
938
|
+
const normalizedType = normalizeUpdateType(entry.type);
|
|
939
|
+
const subject = describeCanonicalSubject(featureKey);
|
|
940
|
+
|
|
690
941
|
return {
|
|
691
942
|
timestamp: entry.timestamp || new Date(0).toISOString(),
|
|
692
|
-
|
|
693
|
-
title:
|
|
694
|
-
type:
|
|
695
|
-
impact:
|
|
943
|
+
scope: entry.scope || inferredFeature.scope,
|
|
944
|
+
title: buildIntentTitle(normalizedType, subject),
|
|
945
|
+
type: normalizedType,
|
|
946
|
+
impact: describeIntentImpact(normalizedType, featureKey, subject),
|
|
947
|
+
feature_key: featureKey,
|
|
948
|
+
feature_name: entry.feature_name || getFeatureMeta(featureKey).name,
|
|
949
|
+
source: entry.source || 'history'
|
|
696
950
|
};
|
|
697
951
|
}
|
|
698
952
|
|
|
@@ -703,10 +957,97 @@ function normalizeStoredHistoryEntry(entry) {
|
|
|
703
957
|
return null;
|
|
704
958
|
}
|
|
705
959
|
|
|
960
|
+
function inferFeatureFromEntry(entry) {
|
|
961
|
+
const combinedText = `${entry.title || ''} ${entry.impact || ''}`.toLowerCase();
|
|
962
|
+
|
|
963
|
+
if (combinedText.includes('github') || combinedText.includes('sync')) {
|
|
964
|
+
return {
|
|
965
|
+
featureKey: 'github_sync',
|
|
966
|
+
featureName: getFeatureMeta('github_sync').name,
|
|
967
|
+
scope: 'logic'
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (combinedText.includes('state') || combinedText.includes('intelligence')) {
|
|
972
|
+
return {
|
|
973
|
+
featureKey: 'project_intelligence',
|
|
974
|
+
featureName: getFeatureMeta('project_intelligence').name,
|
|
975
|
+
scope: 'logic'
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (combinedText.includes('watch') || combinedText.includes('change tracking')) {
|
|
980
|
+
return {
|
|
981
|
+
featureKey: 'change_tracking',
|
|
982
|
+
featureName: getFeatureMeta('change_tracking').name,
|
|
983
|
+
scope: 'logic'
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (
|
|
988
|
+
combinedText.includes('server') ||
|
|
989
|
+
combinedText.includes('backend') ||
|
|
990
|
+
combinedText.includes('endpoint') ||
|
|
991
|
+
combinedText.includes('delivery')
|
|
992
|
+
) {
|
|
993
|
+
return {
|
|
994
|
+
featureKey: 'local_context_server',
|
|
995
|
+
featureName: getFeatureMeta('local_context_server').name,
|
|
996
|
+
scope: 'backend'
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (combinedText.includes('cli') || combinedText.includes('command line')) {
|
|
1001
|
+
return {
|
|
1002
|
+
featureKey: 'cli_workflow',
|
|
1003
|
+
featureName: getFeatureMeta('cli_workflow').name,
|
|
1004
|
+
scope: 'cli'
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (combinedText.includes('documentation') || combinedText.includes('onboarding')) {
|
|
1009
|
+
return {
|
|
1010
|
+
featureKey: 'documentation',
|
|
1011
|
+
featureName: getFeatureMeta('documentation').name,
|
|
1012
|
+
scope: 'documentation'
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (combinedText.includes('package') || combinedText.includes('dependency')) {
|
|
1017
|
+
return {
|
|
1018
|
+
featureKey: 'package_configuration',
|
|
1019
|
+
featureName: getFeatureMeta('package_configuration').name,
|
|
1020
|
+
scope: 'dependencies'
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return {
|
|
1025
|
+
featureKey: 'project_workflow',
|
|
1026
|
+
featureName: getFeatureMeta('project_workflow').name,
|
|
1027
|
+
scope: 'project'
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function normalizeUpdateType(type) {
|
|
1032
|
+
if (type === 'removal') {
|
|
1033
|
+
return 'refactor';
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (type === 'feature' || type === 'improvement' || type === 'refactor' || type === 'fix') {
|
|
1037
|
+
return type;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return 'improvement';
|
|
1041
|
+
}
|
|
1042
|
+
|
|
706
1043
|
function toStateUpdate(update) {
|
|
1044
|
+
if (!update) {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
707
1048
|
return {
|
|
708
1049
|
title: update.title,
|
|
709
|
-
type: update.type,
|
|
1050
|
+
type: normalizeUpdateType(update.type),
|
|
710
1051
|
impact: update.impact
|
|
711
1052
|
};
|
|
712
1053
|
}
|
|
@@ -715,7 +1056,7 @@ function dedupeRecentUpdates(updates) {
|
|
|
715
1056
|
const seenUpdates = new Set();
|
|
716
1057
|
const result = [];
|
|
717
1058
|
|
|
718
|
-
for (const update of updates) {
|
|
1059
|
+
for (const update of updates.filter(Boolean)) {
|
|
719
1060
|
const key = `${update.title}::${update.type}::${update.impact}`;
|
|
720
1061
|
|
|
721
1062
|
if (seenUpdates.has(key)) {
|
|
@@ -733,8 +1074,8 @@ function dedupeHistoryEntries(entries) {
|
|
|
733
1074
|
const seenEntries = new Set();
|
|
734
1075
|
const result = [];
|
|
735
1076
|
|
|
736
|
-
for (const entry of entries) {
|
|
737
|
-
const key = `${entry.title}::${entry.type}::${entry.
|
|
1077
|
+
for (const entry of entries.filter(Boolean)) {
|
|
1078
|
+
const key = `${entry.title}::${entry.type}::${entry.feature_key}`;
|
|
738
1079
|
|
|
739
1080
|
if (seenEntries.has(key)) {
|
|
740
1081
|
continue;
|
|
@@ -744,39 +1085,158 @@ function dedupeHistoryEntries(entries) {
|
|
|
744
1085
|
result.push(entry);
|
|
745
1086
|
}
|
|
746
1087
|
|
|
747
|
-
return result;
|
|
1088
|
+
return result.sort((left, right) => new Date(right.timestamp) - new Date(left.timestamp));
|
|
748
1089
|
}
|
|
749
1090
|
|
|
750
|
-
function
|
|
751
|
-
|
|
1091
|
+
function promoteFeatures(history) {
|
|
1092
|
+
if (!Array.isArray(history) || history.length === 0) {
|
|
1093
|
+
return [];
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const featureStats = new Map();
|
|
1097
|
+
|
|
1098
|
+
for (const entry of history) {
|
|
1099
|
+
const featureKey = entry.feature_key || inferFeatureFromEntry(entry).featureKey;
|
|
1100
|
+
const featureMeta = getFeatureMeta(featureKey);
|
|
1101
|
+
const existing = featureStats.get(featureKey) || {
|
|
1102
|
+
featureKey,
|
|
1103
|
+
featureName: featureMeta.name,
|
|
1104
|
+
count: 0,
|
|
1105
|
+
score: 0,
|
|
1106
|
+
lastTimestamp: new Date(0).toISOString()
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
existing.count += 1;
|
|
1110
|
+
existing.score += scoreFeatureEntry(entry);
|
|
1111
|
+
if (new Date(entry.timestamp) > new Date(existing.lastTimestamp)) {
|
|
1112
|
+
existing.lastTimestamp = entry.timestamp;
|
|
1113
|
+
}
|
|
752
1114
|
|
|
753
|
-
|
|
754
|
-
features.push('CLI commands for initializing, linking, starting, and updating AI context');
|
|
1115
|
+
featureStats.set(featureKey, existing);
|
|
755
1116
|
}
|
|
756
1117
|
|
|
757
|
-
|
|
758
|
-
|
|
1118
|
+
const rankedFeatures = Array.from(featureStats.values()).sort((left, right) => {
|
|
1119
|
+
if (right.count !== left.count) {
|
|
1120
|
+
return right.count - left.count;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (right.score !== left.score) {
|
|
1124
|
+
return right.score - left.score;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
if (getFeaturePriority(right.featureKey) !== getFeaturePriority(left.featureKey)) {
|
|
1128
|
+
return getFeaturePriority(right.featureKey) - getFeaturePriority(left.featureKey);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
return new Date(right.lastTimestamp) - new Date(left.lastTimestamp);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
const promoted = [];
|
|
1135
|
+
const seenFeatureNames = new Set();
|
|
1136
|
+
|
|
1137
|
+
for (const feature of rankedFeatures.filter((item) => item.count >= 3)) {
|
|
1138
|
+
if (LOW_VALUE_FEATURE_KEYS.has(feature.featureKey)) {
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
promoted.push(feature.featureName);
|
|
1143
|
+
seenFeatureNames.add(feature.featureName);
|
|
759
1144
|
}
|
|
760
1145
|
|
|
1146
|
+
for (const feature of rankedFeatures) {
|
|
1147
|
+
if (promoted.length >= MAX_KEY_FEATURES) {
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (LOW_VALUE_FEATURE_KEYS.has(feature.featureKey)) {
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
if (seenFeatureNames.has(feature.featureName)) {
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
promoted.push(feature.featureName);
|
|
1160
|
+
seenFeatureNames.add(feature.featureName);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
for (const feature of rankedFeatures) {
|
|
1164
|
+
if (promoted.length >= MAX_KEY_FEATURES) {
|
|
1165
|
+
break;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
if (seenFeatureNames.has(feature.featureName)) {
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
promoted.push(feature.featureName);
|
|
1173
|
+
seenFeatureNames.add(feature.featureName);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
return promoted.slice(0, MAX_KEY_FEATURES);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function scoreFeatureEntry(entry) {
|
|
1180
|
+
const typeWeights = {
|
|
1181
|
+
feature: 4,
|
|
1182
|
+
refactor: 3,
|
|
1183
|
+
improvement: 2,
|
|
1184
|
+
fix: 2
|
|
1185
|
+
};
|
|
1186
|
+
|
|
1187
|
+
return typeWeights[normalizeUpdateType(entry.type)] || 1;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function describeCanonicalSubject(featureKey) {
|
|
1191
|
+
return getFeatureMeta(featureKey).subject;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function createCapabilitySnapshotEntry(featureKey, fileCount, timestamp) {
|
|
1195
|
+
const subject = describeCanonicalSubject(featureKey);
|
|
1196
|
+
const type = fileCount > 2 ? 'refactor' : 'improvement';
|
|
1197
|
+
|
|
1198
|
+
return {
|
|
1199
|
+
timestamp,
|
|
1200
|
+
scope: inferScopeFromFeature(featureKey),
|
|
1201
|
+
title: buildIntentTitle(type, subject),
|
|
1202
|
+
type,
|
|
1203
|
+
impact: describeIntentImpact(type, featureKey, subject),
|
|
1204
|
+
feature_key: featureKey,
|
|
1205
|
+
feature_name: getFeatureMeta(featureKey).name,
|
|
1206
|
+
source: 'project_snapshot'
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function inferScopeFromFeature(featureKey) {
|
|
761
1211
|
if (
|
|
762
|
-
|
|
763
|
-
|
|
1212
|
+
featureKey === 'github_sync' ||
|
|
1213
|
+
featureKey === 'project_intelligence' ||
|
|
1214
|
+
featureKey === 'change_tracking' ||
|
|
1215
|
+
featureKey === 'project_setup'
|
|
764
1216
|
) {
|
|
765
|
-
|
|
1217
|
+
return 'logic';
|
|
766
1218
|
}
|
|
767
1219
|
|
|
768
|
-
if (
|
|
769
|
-
|
|
1220
|
+
if (featureKey === 'cli_workflow' || featureKey === 'cli_orchestration') {
|
|
1221
|
+
return 'cli';
|
|
770
1222
|
}
|
|
771
1223
|
|
|
772
|
-
if (
|
|
773
|
-
|
|
1224
|
+
if (featureKey === 'local_context_server' || featureKey === 'context_delivery_system') {
|
|
1225
|
+
return 'backend';
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
if (featureKey === 'package_configuration') {
|
|
1229
|
+
return 'dependencies';
|
|
774
1230
|
}
|
|
775
1231
|
|
|
776
|
-
|
|
1232
|
+
if (featureKey === 'documentation') {
|
|
1233
|
+
return 'documentation';
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
return 'project';
|
|
777
1237
|
}
|
|
778
1238
|
|
|
779
|
-
function deriveKnownIssues(projectRoot, techStack) {
|
|
1239
|
+
function deriveKnownIssues(projectRoot, techStack, keyFeatures) {
|
|
780
1240
|
const knownIssues = [];
|
|
781
1241
|
|
|
782
1242
|
if (!hasTestIndicators(projectRoot)) {
|
|
@@ -787,6 +1247,10 @@ function deriveKnownIssues(projectRoot, techStack) {
|
|
|
787
1247
|
knownIssues.push('No common application framework dependency is currently detected.');
|
|
788
1248
|
}
|
|
789
1249
|
|
|
1250
|
+
if (keyFeatures.length < 2) {
|
|
1251
|
+
knownIssues.push('Project intelligence history is still sparse, so AI context may omit mature capabilities.');
|
|
1252
|
+
}
|
|
1253
|
+
|
|
790
1254
|
return knownIssues;
|
|
791
1255
|
}
|
|
792
1256
|
|
|
@@ -804,71 +1268,106 @@ function hasTestIndicators(projectRoot) {
|
|
|
804
1268
|
return testPaths.some((relativePath) => fs.existsSync(path.join(projectRoot, relativePath)));
|
|
805
1269
|
}
|
|
806
1270
|
|
|
807
|
-
function determineCurrentStage(keyFeatures) {
|
|
808
|
-
|
|
809
|
-
const hasSync = keyFeatures.some((feature) => feature.includes('GitHub sync'));
|
|
810
|
-
const hasServer = keyFeatures.some((feature) => feature.includes('Express server'));
|
|
811
|
-
const hasWatcher = keyFeatures.some((feature) => feature.includes('watcher'));
|
|
812
|
-
const hasIntelligence = keyFeatures.some((feature) => feature.includes('Intelligent state engine'));
|
|
813
|
-
|
|
814
|
-
if (hasCli && hasSync && hasServer && hasWatcher && hasIntelligence) {
|
|
1271
|
+
function determineCurrentStage(keyFeatures, historyEntries) {
|
|
1272
|
+
if (keyFeatures.length >= 4 && historyEntries.length >= 4) {
|
|
815
1273
|
return 'Production-ready';
|
|
816
1274
|
}
|
|
817
1275
|
|
|
818
|
-
if (
|
|
1276
|
+
if (keyFeatures.length >= 2 || historyEntries.length >= 2) {
|
|
819
1277
|
return 'Functional prototype';
|
|
820
1278
|
}
|
|
821
1279
|
|
|
822
1280
|
return 'Early development';
|
|
823
1281
|
}
|
|
824
1282
|
|
|
825
|
-
function generateAiSummary(state) {
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
1283
|
+
function generateAiSummary(state, historyEntries) {
|
|
1284
|
+
const featureSignals = collectFeatureSignals(historyEntries, state.key_features);
|
|
1285
|
+
const projectType = featureSignals.has('cli_workflow') || featureSignals.has('cli_orchestration')
|
|
1286
|
+
? 'CLI tool'
|
|
1287
|
+
: 'project system';
|
|
1288
|
+
let coreCapability = 'maintains an AI-readable view of project progress';
|
|
1289
|
+
let uniqueValue = 'so AI collaborators can understand the current project state immediately';
|
|
830
1290
|
|
|
831
|
-
if (
|
|
832
|
-
|
|
1291
|
+
if (featureSignals.has('project_intelligence') && featureSignals.has('change_tracking')) {
|
|
1292
|
+
coreCapability = 'turns meaningful project activity into AI-readable context';
|
|
1293
|
+
} else if (featureSignals.has('project_intelligence')) {
|
|
1294
|
+
coreCapability = 'converts development work into AI-readable project state';
|
|
1295
|
+
} else if (featureSignals.has('local_context_server')) {
|
|
1296
|
+
coreCapability = 'delivers AI-readable project context through clear endpoints';
|
|
833
1297
|
}
|
|
834
1298
|
|
|
835
|
-
if (
|
|
836
|
-
|
|
1299
|
+
if (
|
|
1300
|
+
featureSignals.has('github_sync') ||
|
|
1301
|
+
featureSignals.has('context_delivery_system') ||
|
|
1302
|
+
featureSignals.has('cli_orchestration')
|
|
1303
|
+
) {
|
|
1304
|
+
uniqueValue = 'and enables public AI collaboration through GitHub-synced context endpoints';
|
|
1305
|
+
} else if (featureSignals.has('local_context_server')) {
|
|
1306
|
+
uniqueValue = 'and keeps current context available through local AI endpoints';
|
|
837
1307
|
}
|
|
838
1308
|
|
|
839
|
-
return
|
|
1309
|
+
return `${capitalize(projectType)} that ${coreCapability} ${uniqueValue}.`;
|
|
840
1310
|
}
|
|
841
1311
|
|
|
842
|
-
function
|
|
1312
|
+
function collectFeatureSignals(historyEntries, keyFeatures) {
|
|
1313
|
+
const featureSignals = new Set();
|
|
1314
|
+
|
|
1315
|
+
for (const entry of historyEntries) {
|
|
1316
|
+
if (entry.feature_key) {
|
|
1317
|
+
featureSignals.add(entry.feature_key);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
for (const featureName of keyFeatures || []) {
|
|
1322
|
+
for (const [featureKey, featureMeta] of Object.entries(FEATURE_CATALOG)) {
|
|
1323
|
+
if (featureMeta.name === featureName) {
|
|
1324
|
+
featureSignals.add(featureKey);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
return featureSignals;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function generateNextSteps(state, historyEntries) {
|
|
843
1333
|
const nextSteps = [];
|
|
1334
|
+
const featureSignals = collectFeatureSignals(historyEntries, state.key_features);
|
|
844
1335
|
|
|
845
1336
|
if (state.key_features.length === 0) {
|
|
846
|
-
nextSteps.push('
|
|
1337
|
+
nextSteps.push('Capture a few meaningful project milestones so stable AI-visible features can emerge from real development history.');
|
|
847
1338
|
}
|
|
848
1339
|
|
|
849
1340
|
if (state.known_issues.includes('No automated test suite is detected yet.')) {
|
|
850
|
-
nextSteps.push('Add automated tests for the
|
|
1341
|
+
nextSteps.push('Add automated tests for the intelligence engine, watcher, and GitHub sync workflow.');
|
|
851
1342
|
}
|
|
852
1343
|
|
|
853
|
-
if (
|
|
854
|
-
nextSteps.push('
|
|
1344
|
+
if (!featureSignals.has('project_intelligence')) {
|
|
1345
|
+
nextSteps.push('Strengthen the intelligence engine so more project-level capabilities are captured automatically.');
|
|
855
1346
|
}
|
|
856
1347
|
|
|
857
|
-
if (
|
|
858
|
-
nextSteps.push('
|
|
1348
|
+
if (!featureSignals.has('local_context_server')) {
|
|
1349
|
+
nextSteps.push('Expand context delivery coverage so AI consumers can reliably read current project state.');
|
|
859
1350
|
}
|
|
860
1351
|
|
|
861
|
-
if (!
|
|
862
|
-
nextSteps.push('
|
|
1352
|
+
if (!featureSignals.has('github_sync')) {
|
|
1353
|
+
nextSteps.push('Validate public sync behavior so AI tools can safely consume the latest project context from GitHub.');
|
|
863
1354
|
}
|
|
864
1355
|
|
|
865
|
-
if (state.
|
|
866
|
-
nextSteps.push('
|
|
1356
|
+
if (state.current_stage === 'Early development') {
|
|
1357
|
+
nextSteps.push('Ship the next core workflow milestone to turn the project into a functional prototype.');
|
|
867
1358
|
}
|
|
868
1359
|
|
|
869
1360
|
return Array.from(new Set(nextSteps)).slice(0, 4);
|
|
870
1361
|
}
|
|
871
1362
|
|
|
1363
|
+
function capitalize(value) {
|
|
1364
|
+
if (!value) {
|
|
1365
|
+
return '';
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
872
1371
|
function createDebouncedStateUpdater(projectRoot, options) {
|
|
873
1372
|
const settings = Object.assign(
|
|
874
1373
|
{
|
|
@@ -938,8 +1437,10 @@ module.exports = {
|
|
|
938
1437
|
detectProjectMetadata,
|
|
939
1438
|
ensureContextDirectory,
|
|
940
1439
|
getContextPaths,
|
|
1440
|
+
groupEventsByIntent,
|
|
941
1441
|
interpretChange,
|
|
942
1442
|
loadRuntimeConfig,
|
|
1443
|
+
promoteFeatures,
|
|
943
1444
|
readJsonFile,
|
|
944
1445
|
renderTemplate,
|
|
945
1446
|
scoreEvent,
|
package/package.json
CHANGED