cap-pro 1.0.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/.claude-plugin/README.md +26 -0
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +806 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/cap-architect.md +269 -0
- package/agents/cap-brainstormer.md +207 -0
- package/agents/cap-curator.md +276 -0
- package/agents/cap-debugger.md +365 -0
- package/agents/cap-designer.md +246 -0
- package/agents/cap-historian.md +464 -0
- package/agents/cap-migrator.md +291 -0
- package/agents/cap-prototyper.md +197 -0
- package/agents/cap-validator.md +308 -0
- package/bin/install.js +5433 -0
- package/cap/bin/cap-tools.cjs +853 -0
- package/cap/bin/lib/arc-scanner.cjs +344 -0
- package/cap/bin/lib/cap-affinity-engine.cjs +862 -0
- package/cap/bin/lib/cap-anchor.cjs +228 -0
- package/cap/bin/lib/cap-annotation-writer.cjs +340 -0
- package/cap/bin/lib/cap-checkpoint.cjs +434 -0
- package/cap/bin/lib/cap-cluster-detect.cjs +945 -0
- package/cap/bin/lib/cap-cluster-display.cjs +52 -0
- package/cap/bin/lib/cap-cluster-format.cjs +245 -0
- package/cap/bin/lib/cap-cluster-helpers.cjs +295 -0
- package/cap/bin/lib/cap-cluster-io.cjs +212 -0
- package/cap/bin/lib/cap-completeness.cjs +540 -0
- package/cap/bin/lib/cap-deps.cjs +583 -0
- package/cap/bin/lib/cap-design-families.cjs +332 -0
- package/cap/bin/lib/cap-design.cjs +966 -0
- package/cap/bin/lib/cap-divergence-detector.cjs +400 -0
- package/cap/bin/lib/cap-doctor.cjs +752 -0
- package/cap/bin/lib/cap-feature-map-internals.cjs +19 -0
- package/cap/bin/lib/cap-feature-map-migrate.cjs +335 -0
- package/cap/bin/lib/cap-feature-map-monorepo.cjs +885 -0
- package/cap/bin/lib/cap-feature-map-shard.cjs +315 -0
- package/cap/bin/lib/cap-feature-map.cjs +1943 -0
- package/cap/bin/lib/cap-fitness-score.cjs +1075 -0
- package/cap/bin/lib/cap-impact-analysis.cjs +652 -0
- package/cap/bin/lib/cap-learn-review.cjs +1072 -0
- package/cap/bin/lib/cap-learning-signals.cjs +627 -0
- package/cap/bin/lib/cap-loader.cjs +227 -0
- package/cap/bin/lib/cap-logger.cjs +57 -0
- package/cap/bin/lib/cap-memory-bridge.cjs +764 -0
- package/cap/bin/lib/cap-memory-confidence.cjs +452 -0
- package/cap/bin/lib/cap-memory-dir.cjs +987 -0
- package/cap/bin/lib/cap-memory-engine.cjs +698 -0
- package/cap/bin/lib/cap-memory-extends.cjs +398 -0
- package/cap/bin/lib/cap-memory-graph.cjs +790 -0
- package/cap/bin/lib/cap-memory-migrate.cjs +2015 -0
- package/cap/bin/lib/cap-memory-pin.cjs +183 -0
- package/cap/bin/lib/cap-memory-platform.cjs +490 -0
- package/cap/bin/lib/cap-memory-prune.cjs +707 -0
- package/cap/bin/lib/cap-memory-schema.cjs +812 -0
- package/cap/bin/lib/cap-migrate-tags.cjs +309 -0
- package/cap/bin/lib/cap-migrate.cjs +540 -0
- package/cap/bin/lib/cap-pattern-apply.cjs +1203 -0
- package/cap/bin/lib/cap-pattern-pipeline.cjs +1034 -0
- package/cap/bin/lib/cap-plugin-manifest.cjs +80 -0
- package/cap/bin/lib/cap-realtime-affinity.cjs +399 -0
- package/cap/bin/lib/cap-reconcile.cjs +570 -0
- package/cap/bin/lib/cap-research-gate.cjs +218 -0
- package/cap/bin/lib/cap-scope-filter.cjs +402 -0
- package/cap/bin/lib/cap-semantic-pipeline.cjs +1038 -0
- package/cap/bin/lib/cap-session-extract.cjs +987 -0
- package/cap/bin/lib/cap-session.cjs +445 -0
- package/cap/bin/lib/cap-snapshot-linkage.cjs +963 -0
- package/cap/bin/lib/cap-stack-docs.cjs +646 -0
- package/cap/bin/lib/cap-tag-observer.cjs +371 -0
- package/cap/bin/lib/cap-tag-scanner.cjs +1766 -0
- package/cap/bin/lib/cap-telemetry.cjs +466 -0
- package/cap/bin/lib/cap-test-audit.cjs +1438 -0
- package/cap/bin/lib/cap-thread-migrator.cjs +307 -0
- package/cap/bin/lib/cap-thread-synthesis.cjs +545 -0
- package/cap/bin/lib/cap-thread-tracker.cjs +519 -0
- package/cap/bin/lib/cap-trace.cjs +399 -0
- package/cap/bin/lib/cap-trust-mode.cjs +336 -0
- package/cap/bin/lib/cap-ui-design-editor.cjs +642 -0
- package/cap/bin/lib/cap-ui-mind-map.cjs +712 -0
- package/cap/bin/lib/cap-ui-thread-nav.cjs +693 -0
- package/cap/bin/lib/cap-ui.cjs +1245 -0
- package/cap/bin/lib/cap-upgrade.cjs +1028 -0
- package/cap/bin/lib/cli/arg-helpers.cjs +49 -0
- package/cap/bin/lib/cli/frontmatter-router.cjs +31 -0
- package/cap/bin/lib/cli/init-router.cjs +68 -0
- package/cap/bin/lib/cli/phase-router.cjs +102 -0
- package/cap/bin/lib/cli/state-router.cjs +61 -0
- package/cap/bin/lib/cli/template-router.cjs +37 -0
- package/cap/bin/lib/cli/uat-router.cjs +29 -0
- package/cap/bin/lib/cli/validation-router.cjs +26 -0
- package/cap/bin/lib/cli/verification-router.cjs +31 -0
- package/cap/bin/lib/cli/workstream-router.cjs +39 -0
- package/cap/bin/lib/commands.cjs +961 -0
- package/cap/bin/lib/config.cjs +467 -0
- package/cap/bin/lib/convention-reader.cjs +258 -0
- package/cap/bin/lib/core.cjs +1241 -0
- package/cap/bin/lib/feature-aggregator.cjs +423 -0
- package/cap/bin/lib/frontmatter.cjs +337 -0
- package/cap/bin/lib/init.cjs +1443 -0
- package/cap/bin/lib/manifest-generator.cjs +383 -0
- package/cap/bin/lib/milestone.cjs +253 -0
- package/cap/bin/lib/model-profiles.cjs +69 -0
- package/cap/bin/lib/monorepo-context.cjs +226 -0
- package/cap/bin/lib/monorepo-migrator.cjs +509 -0
- package/cap/bin/lib/phase.cjs +889 -0
- package/cap/bin/lib/profile-output.cjs +989 -0
- package/cap/bin/lib/profile-pipeline.cjs +540 -0
- package/cap/bin/lib/roadmap.cjs +330 -0
- package/cap/bin/lib/security.cjs +394 -0
- package/cap/bin/lib/session-manager.cjs +292 -0
- package/cap/bin/lib/skeleton-generator.cjs +179 -0
- package/cap/bin/lib/state.cjs +1032 -0
- package/cap/bin/lib/template.cjs +231 -0
- package/cap/bin/lib/test-detector.cjs +62 -0
- package/cap/bin/lib/uat.cjs +283 -0
- package/cap/bin/lib/verify.cjs +889 -0
- package/cap/bin/lib/workspace-detector.cjs +371 -0
- package/cap/bin/lib/workstream.cjs +492 -0
- package/cap/commands/gsd/workstreams.md +63 -0
- package/cap/references/arc-standard.md +315 -0
- package/cap/references/cap-agent-architecture.md +101 -0
- package/cap/references/cap-gitignore-template +9 -0
- package/cap/references/cap-zero-deps.md +158 -0
- package/cap/references/checkpoints.md +778 -0
- package/cap/references/continuation-format.md +249 -0
- package/cap/references/contract-test-templates.md +312 -0
- package/cap/references/feature-map-template.md +25 -0
- package/cap/references/git-integration.md +295 -0
- package/cap/references/git-planning-commit.md +38 -0
- package/cap/references/model-profiles.md +174 -0
- package/cap/references/phase-numbering.md +126 -0
- package/cap/references/planning-config.md +202 -0
- package/cap/references/property-test-templates.md +316 -0
- package/cap/references/security-test-templates.md +347 -0
- package/cap/references/session-template.json +8 -0
- package/cap/references/tdd.md +263 -0
- package/cap/references/user-profiling.md +681 -0
- package/cap/references/verification-patterns.md +612 -0
- package/cap/templates/UAT.md +265 -0
- package/cap/templates/claude-md.md +175 -0
- package/cap/templates/codebase/architecture.md +255 -0
- package/cap/templates/codebase/concerns.md +310 -0
- package/cap/templates/codebase/conventions.md +307 -0
- package/cap/templates/codebase/integrations.md +280 -0
- package/cap/templates/codebase/stack.md +186 -0
- package/cap/templates/codebase/structure.md +285 -0
- package/cap/templates/codebase/testing.md +480 -0
- package/cap/templates/config.json +44 -0
- package/cap/templates/context.md +352 -0
- package/cap/templates/continue-here.md +78 -0
- package/cap/templates/copilot-instructions.md +7 -0
- package/cap/templates/debug-subagent-prompt.md +91 -0
- package/cap/templates/discussion-log.md +63 -0
- package/cap/templates/milestone-archive.md +123 -0
- package/cap/templates/milestone.md +115 -0
- package/cap/templates/phase-prompt.md +610 -0
- package/cap/templates/planner-subagent-prompt.md +117 -0
- package/cap/templates/project.md +186 -0
- package/cap/templates/requirements.md +231 -0
- package/cap/templates/research-project/ARCHITECTURE.md +204 -0
- package/cap/templates/research-project/FEATURES.md +147 -0
- package/cap/templates/research-project/PITFALLS.md +200 -0
- package/cap/templates/research-project/STACK.md +120 -0
- package/cap/templates/research-project/SUMMARY.md +170 -0
- package/cap/templates/research.md +552 -0
- package/cap/templates/roadmap.md +202 -0
- package/cap/templates/state.md +176 -0
- package/cap/templates/summary.md +364 -0
- package/cap/templates/user-preferences.md +498 -0
- package/cap/templates/verification-report.md +322 -0
- package/cap/workflows/add-phase.md +112 -0
- package/cap/workflows/add-tests.md +351 -0
- package/cap/workflows/add-todo.md +158 -0
- package/cap/workflows/audit-milestone.md +340 -0
- package/cap/workflows/audit-uat.md +109 -0
- package/cap/workflows/autonomous.md +891 -0
- package/cap/workflows/check-todos.md +177 -0
- package/cap/workflows/cleanup.md +152 -0
- package/cap/workflows/complete-milestone.md +767 -0
- package/cap/workflows/diagnose-issues.md +231 -0
- package/cap/workflows/discovery-phase.md +289 -0
- package/cap/workflows/discuss-phase-assumptions.md +653 -0
- package/cap/workflows/discuss-phase.md +1049 -0
- package/cap/workflows/do.md +104 -0
- package/cap/workflows/execute-phase.md +846 -0
- package/cap/workflows/execute-plan.md +514 -0
- package/cap/workflows/fast.md +105 -0
- package/cap/workflows/forensics.md +265 -0
- package/cap/workflows/health.md +181 -0
- package/cap/workflows/help.md +660 -0
- package/cap/workflows/insert-phase.md +130 -0
- package/cap/workflows/list-phase-assumptions.md +178 -0
- package/cap/workflows/list-workspaces.md +56 -0
- package/cap/workflows/manager.md +362 -0
- package/cap/workflows/map-codebase.md +377 -0
- package/cap/workflows/milestone-summary.md +223 -0
- package/cap/workflows/new-milestone.md +486 -0
- package/cap/workflows/new-project.md +1250 -0
- package/cap/workflows/new-workspace.md +237 -0
- package/cap/workflows/next.md +97 -0
- package/cap/workflows/node-repair.md +92 -0
- package/cap/workflows/note.md +156 -0
- package/cap/workflows/pause-work.md +176 -0
- package/cap/workflows/plan-milestone-gaps.md +273 -0
- package/cap/workflows/plan-phase.md +857 -0
- package/cap/workflows/plant-seed.md +169 -0
- package/cap/workflows/pr-branch.md +129 -0
- package/cap/workflows/profile-user.md +449 -0
- package/cap/workflows/progress.md +507 -0
- package/cap/workflows/quick.md +757 -0
- package/cap/workflows/remove-phase.md +155 -0
- package/cap/workflows/remove-workspace.md +90 -0
- package/cap/workflows/research-phase.md +82 -0
- package/cap/workflows/resume-project.md +326 -0
- package/cap/workflows/review.md +228 -0
- package/cap/workflows/session-report.md +146 -0
- package/cap/workflows/settings.md +283 -0
- package/cap/workflows/ship.md +228 -0
- package/cap/workflows/stats.md +60 -0
- package/cap/workflows/transition.md +671 -0
- package/cap/workflows/ui-phase.md +298 -0
- package/cap/workflows/ui-review.md +161 -0
- package/cap/workflows/update.md +323 -0
- package/cap/workflows/validate-phase.md +170 -0
- package/cap/workflows/verify-phase.md +254 -0
- package/cap/workflows/verify-work.md +637 -0
- package/commands/cap/annotate.md +165 -0
- package/commands/cap/brainstorm.md +393 -0
- package/commands/cap/checkpoint.md +106 -0
- package/commands/cap/completeness.md +94 -0
- package/commands/cap/continue.md +72 -0
- package/commands/cap/debug.md +588 -0
- package/commands/cap/deps.md +169 -0
- package/commands/cap/design.md +479 -0
- package/commands/cap/init.md +354 -0
- package/commands/cap/iterate.md +249 -0
- package/commands/cap/learn.md +459 -0
- package/commands/cap/memory.md +275 -0
- package/commands/cap/migrate-feature-map.md +91 -0
- package/commands/cap/migrate-memory.md +108 -0
- package/commands/cap/migrate-tags.md +91 -0
- package/commands/cap/migrate.md +131 -0
- package/commands/cap/prototype.md +510 -0
- package/commands/cap/reconcile.md +121 -0
- package/commands/cap/review.md +360 -0
- package/commands/cap/save.md +72 -0
- package/commands/cap/scan.md +404 -0
- package/commands/cap/start.md +356 -0
- package/commands/cap/status.md +118 -0
- package/commands/cap/test-audit.md +262 -0
- package/commands/cap/test.md +394 -0
- package/commands/cap/trace.md +133 -0
- package/commands/cap/ui.md +167 -0
- package/hooks/dist/cap-check-update.js +115 -0
- package/hooks/dist/cap-context-monitor.js +185 -0
- package/hooks/dist/cap-learn-review-hook.js +114 -0
- package/hooks/dist/cap-learning-hook.js +192 -0
- package/hooks/dist/cap-memory.js +299 -0
- package/hooks/dist/cap-prompt-guard.js +97 -0
- package/hooks/dist/cap-statusline.js +157 -0
- package/hooks/dist/cap-tag-observer.js +115 -0
- package/hooks/dist/cap-version-check.js +112 -0
- package/hooks/dist/cap-workflow-guard.js +175 -0
- package/hooks/hooks.json +55 -0
- package/package.json +58 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +93 -0
- package/scripts/cap-removal-checklist.md +202 -0
- package/scripts/prompt-injection-scan.sh +199 -0
- package/scripts/run-tests.cjs +181 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// @cap-feature(feature:F-040, primary:true) Cluster display orchestrator -- thin module that wires
|
|
4
|
+
// the pure formatters (cap-cluster-format.cjs), helper utilities (cap-cluster-helpers.cjs), and the
|
|
5
|
+
// I/O layer (cap-cluster-io.cjs) into a single public surface. This is the canonical module for
|
|
6
|
+
// /cap:cluster and /cap:status integration.
|
|
7
|
+
// @cap-feature(feature:F-050) Refactored from a 696-line single file into a 4-module split:
|
|
8
|
+
// - cap-cluster-format.cjs : 4 public format* functions (no I/O)
|
|
9
|
+
// - cap-cluster-helpers.cjs : 11 pure _* helpers shared by formatters and tests
|
|
10
|
+
// - cap-cluster-io.cjs : disk loaders, affinity + clustering pipeline, structured diagnostics
|
|
11
|
+
// - cap-cluster-display.cjs : this orchestrator -- re-exports the union for backward compatibility
|
|
12
|
+
// @cap-decision Public API of cap-cluster-display.cjs is preserved exactly -- callers see the same 19
|
|
13
|
+
// exports as before refactor (verified via Object.keys diff in F-050/AC-4 acceptance gate).
|
|
14
|
+
// @cap-decision Re-export pattern (explicit property assignment) chosen over `Object.assign(...)` so the
|
|
15
|
+
// canonical export list is greppable in this file -- a future reader can verify the public surface
|
|
16
|
+
// without having to load three other modules.
|
|
17
|
+
|
|
18
|
+
// @cap-todo(ac:F-050/AC-1) Split into format/helpers/io/orchestrator modules; orchestrator stays under 60 lines.
|
|
19
|
+
// @cap-todo(ac:F-050/AC-4) Public API unchanged -- this module re-exports the union of format + helpers + io.
|
|
20
|
+
|
|
21
|
+
const format = require('./cap-cluster-format.cjs');
|
|
22
|
+
const helpers = require('./cap-cluster-helpers.cjs');
|
|
23
|
+
const io = require('./cap-cluster-io.cjs');
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
// --- Pure formatting (re-exported from cap-cluster-format.cjs) ---
|
|
27
|
+
formatClusterOverview: format.formatClusterOverview,
|
|
28
|
+
formatClusterDetail: format.formatClusterDetail,
|
|
29
|
+
formatNeuralMemoryStatus: format.formatNeuralMemoryStatus,
|
|
30
|
+
formatRealtimeNotifications: format.formatRealtimeNotifications,
|
|
31
|
+
|
|
32
|
+
// --- I/O convenience (re-exported from cap-cluster-io.cjs) ---
|
|
33
|
+
loadAndFormatOverview: io.loadAndFormatOverview,
|
|
34
|
+
loadAndFormatDetail: io.loadAndFormatDetail,
|
|
35
|
+
loadAndFormatStatus: io.loadAndFormatStatus,
|
|
36
|
+
|
|
37
|
+
// --- Internal helpers (re-exported from cap-cluster-helpers.cjs for testing + backward compat) ---
|
|
38
|
+
_buildAffinityMap: helpers._buildAffinityMap,
|
|
39
|
+
_pairKey: helpers._pairKey,
|
|
40
|
+
_computeAvgAffinity: helpers._computeAvgAffinity,
|
|
41
|
+
_countDormantMembers: helpers._countDormantMembers,
|
|
42
|
+
_isNodeDormant: helpers._isNodeDormant,
|
|
43
|
+
_countAllDormantNodes: helpers._countAllDormantNodes,
|
|
44
|
+
_getJoinedDate: helpers._getJoinedDate,
|
|
45
|
+
_buildPairwiseRows: helpers._buildPairwiseRows,
|
|
46
|
+
_extractSharedConcepts: helpers._extractSharedConcepts,
|
|
47
|
+
_computeDriftStatus: helpers._computeDriftStatus,
|
|
48
|
+
_findHighestAffinityPair: helpers._findHighestAffinityPair,
|
|
49
|
+
|
|
50
|
+
// --- I/O internal (re-exported from cap-cluster-io.cjs for testing + backward compat) ---
|
|
51
|
+
_loadClusterData: io._loadClusterData,
|
|
52
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// @cap-feature(feature:F-050) Pure formatting layer extracted from cap-cluster-display.cjs.
|
|
4
|
+
// This module contains only the 4 public format* functions. Internal helpers live in cap-cluster-helpers.cjs.
|
|
5
|
+
// All functions take data as input and return strings -- no I/O, no requires of detect/affinity/graph modules.
|
|
6
|
+
// @cap-feature(feature:F-040) Cluster display formatting -- overview, detail, status, realtime notifications.
|
|
7
|
+
// @cap-decision Helpers separated into cap-cluster-helpers.cjs to keep this file under the F-050/AC-1
|
|
8
|
+
// 300-line limit. Trade-off: one extra require per format function. Cost is negligible because helpers
|
|
9
|
+
// are loaded once at module initialization.
|
|
10
|
+
|
|
11
|
+
const helpers = require('./cap-cluster-helpers.cjs');
|
|
12
|
+
|
|
13
|
+
// --- JSDoc Typedefs ---
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} Cluster
|
|
17
|
+
* @property {string} id - Cluster ID (hash of sorted member IDs)
|
|
18
|
+
* @property {string[]} members - Thread IDs in this cluster
|
|
19
|
+
* @property {string} label - Human-readable label (e.g., "auth . session . cookies")
|
|
20
|
+
* @property {string} createdAt - ISO timestamp
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// --- Pure Formatting Functions (no I/O) ---
|
|
24
|
+
|
|
25
|
+
// @cap-todo(ac:F-050/AC-1) Pure formatter module -- no I/O, no requires of detect/affinity/graph modules.
|
|
26
|
+
// @cap-todo(ac:F-040/AC-1) Format overview table of all clusters with labels, member counts, avg affinity, dormant count
|
|
27
|
+
// @cap-todo(ac:F-040/AC-7) Consistent markdown table formatting with aligned headers
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format a cluster overview table showing all detected clusters.
|
|
31
|
+
*
|
|
32
|
+
* @param {Cluster[]} clusters - Detected clusters with labels and members
|
|
33
|
+
* @param {Object} graph - MemoryGraph instance
|
|
34
|
+
* @param {Object[]} [affinityResults] - Pairwise affinity results for avg score calculation
|
|
35
|
+
* @returns {string} Formatted markdown overview
|
|
36
|
+
*/
|
|
37
|
+
function formatClusterOverview(clusters, graph, affinityResults) {
|
|
38
|
+
if (!clusters || clusters.length === 0) {
|
|
39
|
+
return [
|
|
40
|
+
'Neural Memory Clusters',
|
|
41
|
+
'',
|
|
42
|
+
'No clusters detected. Run /cap:iterate or /cap:prototype to build thread history,',
|
|
43
|
+
'then affinity scores will be computed automatically.',
|
|
44
|
+
].join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const affinityMap = helpers._buildAffinityMap(affinityResults || []);
|
|
48
|
+
const rows = [];
|
|
49
|
+
let totalThreads = 0;
|
|
50
|
+
let totalDormant = 0;
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < clusters.length; i++) {
|
|
53
|
+
const cluster = clusters[i];
|
|
54
|
+
const memberCount = cluster.members.length;
|
|
55
|
+
const avgAffinity = helpers._computeAvgAffinity(cluster.members, affinityMap);
|
|
56
|
+
const dormantCount = helpers._countDormantMembers(cluster.members, graph);
|
|
57
|
+
|
|
58
|
+
totalThreads += memberCount;
|
|
59
|
+
totalDormant += dormantCount;
|
|
60
|
+
|
|
61
|
+
rows.push({
|
|
62
|
+
index: i + 1,
|
|
63
|
+
label: cluster.label,
|
|
64
|
+
memberCount,
|
|
65
|
+
avgAffinity,
|
|
66
|
+
dormantCount,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const lines = [
|
|
71
|
+
'Neural Memory Clusters',
|
|
72
|
+
'',
|
|
73
|
+
'| # | Label | Members | Avg Affinity | Dormant |',
|
|
74
|
+
'|---|-------|---------|-------------|---------|',
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const row of rows) {
|
|
78
|
+
lines.push(
|
|
79
|
+
`| ${row.index} | ${row.label} | ${row.memberCount} threads | ${row.avgAffinity.toFixed(2)} | ${row.dormantCount} |`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
lines.push('');
|
|
84
|
+
lines.push(`Total: ${clusters.length} clusters, ${totalThreads} threads, ${totalDormant} dormant nodes`);
|
|
85
|
+
|
|
86
|
+
return lines.join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// @cap-todo(ac:F-040/AC-2) Format detail view: members table, pairwise affinity, shared concepts, drift status
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Format a cluster detail view with members, pairwise scores, shared concepts, drift status.
|
|
93
|
+
*
|
|
94
|
+
* @param {Cluster} cluster - The cluster to display
|
|
95
|
+
* @param {Object[]} affinityResults - All pairwise affinity results
|
|
96
|
+
* @param {Object} graph - MemoryGraph instance
|
|
97
|
+
* @param {Object[]} threads - All thread objects (from thread tracker)
|
|
98
|
+
* @returns {string} Formatted markdown detail view
|
|
99
|
+
*/
|
|
100
|
+
function formatClusterDetail(cluster, affinityResults, graph, threads) {
|
|
101
|
+
if (!cluster) {
|
|
102
|
+
return 'Cluster not found. Run /cap:cluster to see available clusters.';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const threadMap = new Map();
|
|
106
|
+
for (const t of threads) {
|
|
107
|
+
threadMap.set(t.id, t);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const lines = [];
|
|
111
|
+
|
|
112
|
+
// Header
|
|
113
|
+
lines.push(`Cluster: ${cluster.label}`);
|
|
114
|
+
lines.push('');
|
|
115
|
+
|
|
116
|
+
// Members table
|
|
117
|
+
lines.push('Members:');
|
|
118
|
+
lines.push('| Thread | Name | Joined | Dormant |');
|
|
119
|
+
lines.push('|--------|------|--------|---------|');
|
|
120
|
+
|
|
121
|
+
for (const memberId of cluster.members) {
|
|
122
|
+
const thread = threadMap.get(memberId);
|
|
123
|
+
const name = thread ? (thread.name || thread.id) : memberId;
|
|
124
|
+
const joined = helpers._getJoinedDate(memberId, cluster, graph);
|
|
125
|
+
const isDormant = helpers._isNodeDormant(memberId, graph);
|
|
126
|
+
lines.push(`| ${memberId} | ${name} | ${joined} | ${isDormant ? 'yes' : 'no'} |`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
lines.push('');
|
|
130
|
+
|
|
131
|
+
// Pairwise affinity table
|
|
132
|
+
const pairRows = helpers._buildPairwiseRows(cluster.members, affinityResults);
|
|
133
|
+
|
|
134
|
+
if (pairRows.length > 0) {
|
|
135
|
+
lines.push('Pairwise Affinity:');
|
|
136
|
+
lines.push('| Thread A | Thread B | Score | Strongest Signal |');
|
|
137
|
+
lines.push('|----------|----------|-------|-----------------|');
|
|
138
|
+
|
|
139
|
+
for (const row of pairRows) {
|
|
140
|
+
lines.push(`| ${row.threadA} | ${row.threadB} | ${row.score.toFixed(2)} | ${row.strongestSignal} |`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
lines.push('');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Shared concepts
|
|
147
|
+
const concepts = helpers._extractSharedConcepts(cluster.members, threads);
|
|
148
|
+
if (concepts.length > 0) {
|
|
149
|
+
lines.push(`Shared Concepts: ${concepts.join(', ')}`);
|
|
150
|
+
} else {
|
|
151
|
+
lines.push('Shared Concepts: (none detected)');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Drift status
|
|
155
|
+
const driftStatus = helpers._computeDriftStatus(cluster.members, graph);
|
|
156
|
+
lines.push(`Drift Status: ${driftStatus}`);
|
|
157
|
+
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// @cap-todo(ac:F-040/AC-3) Format Neural Memory section for /cap:status integration
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format the Neural Memory status section for /cap:status.
|
|
165
|
+
*
|
|
166
|
+
* @param {Cluster[]} clusters - Detected clusters
|
|
167
|
+
* @param {Object} graph - MemoryGraph instance
|
|
168
|
+
* @param {Object[]} [affinityResults] - Pairwise affinity results for highest pair
|
|
169
|
+
* @returns {string} Formatted status section
|
|
170
|
+
*/
|
|
171
|
+
function formatNeuralMemoryStatus(clusters, graph, affinityResults) {
|
|
172
|
+
const activeClusters = (clusters || []).length;
|
|
173
|
+
const dormantCount = helpers._countAllDormantNodes(graph);
|
|
174
|
+
|
|
175
|
+
// Find highest affinity pair
|
|
176
|
+
const highestPair = helpers._findHighestAffinityPair(affinityResults || []);
|
|
177
|
+
|
|
178
|
+
// Last clustering timestamp from graph metadata
|
|
179
|
+
const lastClustering = (graph && graph.metadata && graph.metadata.lastClusteredAt)
|
|
180
|
+
? graph.metadata.lastClusteredAt
|
|
181
|
+
: (graph && graph.lastUpdated) || 'never';
|
|
182
|
+
|
|
183
|
+
const lines = [
|
|
184
|
+
'Neural Memory',
|
|
185
|
+
` Active clusters: ${activeClusters}`,
|
|
186
|
+
` Dormant nodes: ${dormantCount}`,
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
if (highestPair) {
|
|
190
|
+
lines.push(
|
|
191
|
+
` Highest affinity: ${highestPair.sourceThreadId} \u2194 ${highestPair.targetThreadId} (${highestPair.compositeScore.toFixed(2)}, ${highestPair.band || 'unknown'})`
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
lines.push(' Highest affinity: (no pairs computed)');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
lines.push(` Last clustering: ${lastClustering}`);
|
|
198
|
+
|
|
199
|
+
return lines.join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// @cap-todo(ac:F-040/AC-4) Format realtime notifications for /cap:start passive check
|
|
203
|
+
// @cap-todo(ac:F-040/AC-5) Format realtime notifications for /cap:brainstorm passive check
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Format realtime affinity notifications for display during /cap:start or /cap:brainstorm.
|
|
207
|
+
* Shows urgent and notify-band threads before session work begins.
|
|
208
|
+
*
|
|
209
|
+
* @param {Array<{band: string, text: string, threadId: string}>} notifications - Formatted notification objects
|
|
210
|
+
* @returns {string} Formatted notification block, or empty string if nothing to show
|
|
211
|
+
*/
|
|
212
|
+
function formatRealtimeNotifications(notifications) {
|
|
213
|
+
if (!notifications || notifications.length === 0) return '';
|
|
214
|
+
|
|
215
|
+
const urgent = notifications.filter(n => n.band === 'urgent');
|
|
216
|
+
const notify = notifications.filter(n => n.band === 'notify');
|
|
217
|
+
|
|
218
|
+
if (urgent.length === 0 && notify.length === 0) return '';
|
|
219
|
+
|
|
220
|
+
const lines = ['Related Threads (Neural Memory)'];
|
|
221
|
+
lines.push('');
|
|
222
|
+
|
|
223
|
+
if (urgent.length > 0) {
|
|
224
|
+
for (const u of urgent) {
|
|
225
|
+
lines.push(u.text);
|
|
226
|
+
}
|
|
227
|
+
lines.push('');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (notify.length > 0) {
|
|
231
|
+
for (const n of notify) {
|
|
232
|
+
lines.push(n.text);
|
|
233
|
+
}
|
|
234
|
+
lines.push('');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return lines.join('\n');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
formatClusterOverview,
|
|
242
|
+
formatClusterDetail,
|
|
243
|
+
formatNeuralMemoryStatus,
|
|
244
|
+
formatRealtimeNotifications,
|
|
245
|
+
};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// @cap-feature(feature:F-050) Pure helper utilities for cluster display formatting.
|
|
4
|
+
// Extracted from the original 696-line cap-cluster-display.cjs as part of the F-050 split. These
|
|
5
|
+
// functions take graph/affinity/thread data as input and return primitive values (numbers, strings,
|
|
6
|
+
// arrays, Maps). Zero I/O. Zero requires of detect/affinity/graph/thread modules.
|
|
7
|
+
// @cap-decision Helpers live in their own module to keep both the formatter (cap-cluster-format.cjs)
|
|
8
|
+
// and the orchestrator (cap-cluster-display.cjs) under the F-050/AC-1 300-line cap.
|
|
9
|
+
|
|
10
|
+
// --- JSDoc Typedefs ---
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} PairwiseRow
|
|
14
|
+
* @property {string} threadA - Thread ID A
|
|
15
|
+
* @property {string} threadB - Thread ID B
|
|
16
|
+
* @property {number} score - Composite affinity score
|
|
17
|
+
* @property {string} strongestSignal - Name of strongest signal with score
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// @cap-todo(ac:F-050/AC-1) Pure helpers in their own module to keep formatter file under 300 lines.
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build a fast lookup map: "tidA|tidB" -> AffinityResult. Higher-scoring duplicates win.
|
|
24
|
+
* @param {Object[]} affinityResults
|
|
25
|
+
* @returns {Map<string, Object>}
|
|
26
|
+
*/
|
|
27
|
+
function _buildAffinityMap(affinityResults) {
|
|
28
|
+
const map = new Map();
|
|
29
|
+
for (const r of affinityResults) {
|
|
30
|
+
const key = _pairKey(r.sourceThreadId, r.targetThreadId);
|
|
31
|
+
const existing = map.get(key);
|
|
32
|
+
if (!existing || r.compositeScore > existing.compositeScore) {
|
|
33
|
+
map.set(key, r);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return map;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a canonical pair key from two thread IDs (lexicographically smaller ID first).
|
|
41
|
+
* @param {string} tidA
|
|
42
|
+
* @param {string} tidB
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
function _pairKey(tidA, tidB) {
|
|
46
|
+
return tidA < tidB ? `${tidA}|${tidB}` : `${tidB}|${tidA}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compute average pairwise affinity for cluster members.
|
|
51
|
+
* @param {string[]} members - Thread IDs
|
|
52
|
+
* @param {Map<string, Object>} affinityMap
|
|
53
|
+
* @returns {number}
|
|
54
|
+
*/
|
|
55
|
+
function _computeAvgAffinity(members, affinityMap) {
|
|
56
|
+
if (members.length < 2) return 0;
|
|
57
|
+
|
|
58
|
+
let total = 0;
|
|
59
|
+
let count = 0;
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < members.length; i++) {
|
|
62
|
+
for (let j = i + 1; j < members.length; j++) {
|
|
63
|
+
const key = _pairKey(members[i], members[j]);
|
|
64
|
+
const result = affinityMap.get(key);
|
|
65
|
+
if (result) {
|
|
66
|
+
total += result.compositeScore;
|
|
67
|
+
count++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return count > 0 ? total / count : 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Count dormant members in a cluster.
|
|
77
|
+
* @param {string[]} members - Thread IDs
|
|
78
|
+
* @param {Object} graph - MemoryGraph
|
|
79
|
+
* @returns {number}
|
|
80
|
+
*/
|
|
81
|
+
function _countDormantMembers(members, graph) {
|
|
82
|
+
let count = 0;
|
|
83
|
+
for (const threadId of members) {
|
|
84
|
+
if (_isNodeDormant(threadId, graph)) count++;
|
|
85
|
+
}
|
|
86
|
+
return count;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if a thread node is marked dormant in the graph.
|
|
91
|
+
* @param {string} threadId - Thread ID
|
|
92
|
+
* @param {Object} graph - MemoryGraph
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
function _isNodeDormant(threadId, graph) {
|
|
96
|
+
if (!graph || !graph.nodes) return false;
|
|
97
|
+
|
|
98
|
+
for (const node of Object.values(graph.nodes)) {
|
|
99
|
+
if (node.type === 'thread' && node.metadata && node.metadata.threadId === threadId) {
|
|
100
|
+
return node.metadata.dormant === true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Count all dormant nodes in the graph.
|
|
108
|
+
* @param {Object} graph - MemoryGraph
|
|
109
|
+
* @returns {number}
|
|
110
|
+
*/
|
|
111
|
+
function _countAllDormantNodes(graph) {
|
|
112
|
+
if (!graph || !graph.nodes) return 0;
|
|
113
|
+
|
|
114
|
+
let count = 0;
|
|
115
|
+
for (const node of Object.values(graph.nodes)) {
|
|
116
|
+
if (node.type === 'thread' && node.metadata && node.metadata.dormant === true) {
|
|
117
|
+
count++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return count;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the date a thread joined a cluster (from graph metadata or cluster creation time).
|
|
125
|
+
* @param {string} threadId
|
|
126
|
+
* @param {Object} cluster - Object with createdAt fallback
|
|
127
|
+
* @param {Object} graph
|
|
128
|
+
* @returns {string} ISO date (YYYY-MM-DD) or 'unknown'
|
|
129
|
+
*/
|
|
130
|
+
function _getJoinedDate(threadId, cluster, graph) {
|
|
131
|
+
// Try to get join date from graph node metadata
|
|
132
|
+
if (graph && graph.nodes) {
|
|
133
|
+
for (const node of Object.values(graph.nodes)) {
|
|
134
|
+
if (node.type === 'thread' && node.metadata && node.metadata.threadId === threadId) {
|
|
135
|
+
if (node.metadata.cluster && node.metadata.cluster.joinedAt) {
|
|
136
|
+
return node.metadata.cluster.joinedAt.slice(0, 10);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Fallback to cluster creation time
|
|
143
|
+
if (cluster.createdAt) {
|
|
144
|
+
return cluster.createdAt.slice(0, 10);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return 'unknown';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Build pairwise affinity rows for cluster members. Sorted by score descending.
|
|
152
|
+
* @param {string[]} members - Thread IDs
|
|
153
|
+
* @param {Object[]} affinityResults - All affinity results
|
|
154
|
+
* @returns {PairwiseRow[]}
|
|
155
|
+
*/
|
|
156
|
+
function _buildPairwiseRows(members, affinityResults) {
|
|
157
|
+
const memberSet = new Set(members);
|
|
158
|
+
const rows = [];
|
|
159
|
+
|
|
160
|
+
for (const r of affinityResults) {
|
|
161
|
+
if (memberSet.has(r.sourceThreadId) && memberSet.has(r.targetThreadId)) {
|
|
162
|
+
// Find strongest signal
|
|
163
|
+
let strongestSignal = 'composite';
|
|
164
|
+
let strongestScore = r.compositeScore;
|
|
165
|
+
|
|
166
|
+
if (r.signals && r.signals.length > 0) {
|
|
167
|
+
const best = r.signals.reduce((a, b) => (b.score > a.score ? b : a), r.signals[0]);
|
|
168
|
+
strongestSignal = `${best.name} (${best.score.toFixed(2)})`;
|
|
169
|
+
strongestScore = r.compositeScore;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
rows.push({
|
|
173
|
+
threadA: r.sourceThreadId,
|
|
174
|
+
threadB: r.targetThreadId,
|
|
175
|
+
score: strongestScore,
|
|
176
|
+
strongestSignal,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Sort by score descending
|
|
182
|
+
rows.sort((a, b) => b.score - a.score);
|
|
183
|
+
return rows;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Extract shared concepts across cluster member threads using keyword overlap (>= 2 threads).
|
|
188
|
+
* @param {string[]} members - Thread IDs
|
|
189
|
+
* @param {Object[]} threads - All thread objects
|
|
190
|
+
* @returns {string[]} Top 10 shared concepts/keywords sorted by frequency descending
|
|
191
|
+
*/
|
|
192
|
+
function _extractSharedConcepts(members, threads) {
|
|
193
|
+
const memberSet = new Set(members);
|
|
194
|
+
const memberThreads = threads.filter(t => memberSet.has(t.id));
|
|
195
|
+
|
|
196
|
+
if (memberThreads.length < 2) return [];
|
|
197
|
+
|
|
198
|
+
// Count keyword frequency across member threads
|
|
199
|
+
const kwFreq = new Map();
|
|
200
|
+
for (const t of memberThreads) {
|
|
201
|
+
const keywords = t.keywords || [];
|
|
202
|
+
for (const kw of keywords) {
|
|
203
|
+
kwFreq.set(kw, (kwFreq.get(kw) || 0) + 1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Keep keywords appearing in at least 2 member threads
|
|
208
|
+
const shared = [];
|
|
209
|
+
for (const [kw, count] of kwFreq) {
|
|
210
|
+
if (count >= 2) {
|
|
211
|
+
shared.push(kw);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Sort by frequency descending, take top 10
|
|
216
|
+
shared.sort((a, b) => (kwFreq.get(b) || 0) - (kwFreq.get(a) || 0));
|
|
217
|
+
return shared.slice(0, 10);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Compute drift status for a cluster by checking affinity edge decay in the graph.
|
|
222
|
+
* @param {string[]} members - Thread IDs
|
|
223
|
+
* @param {Object} graph - MemoryGraph
|
|
224
|
+
* @returns {string} Human-readable drift status
|
|
225
|
+
*/
|
|
226
|
+
function _computeDriftStatus(members, graph) {
|
|
227
|
+
if (!graph || !graph.edges || members.length < 2) {
|
|
228
|
+
return 'stable (insufficient data)';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Find thread node IDs for members
|
|
232
|
+
const memberNodeIds = new Set();
|
|
233
|
+
|
|
234
|
+
for (const [nodeId, node] of Object.entries(graph.nodes || {})) {
|
|
235
|
+
if (node.type === 'thread' && node.metadata && node.metadata.threadId) {
|
|
236
|
+
if (members.includes(node.metadata.threadId)) {
|
|
237
|
+
memberNodeIds.add(nodeId);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check affinity edges between cluster members for decay signals
|
|
243
|
+
let decayedCount = 0;
|
|
244
|
+
let totalEdges = 0;
|
|
245
|
+
|
|
246
|
+
for (const edge of graph.edges) {
|
|
247
|
+
if (edge.type !== 'affinity' || !edge.active) continue;
|
|
248
|
+
if (memberNodeIds.has(edge.source) && memberNodeIds.has(edge.target)) {
|
|
249
|
+
totalEdges++;
|
|
250
|
+
if (edge.metadata && edge.metadata.decayApplied) {
|
|
251
|
+
decayedCount++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (totalEdges === 0) return 'stable (no edges)';
|
|
257
|
+
if (decayedCount === 0) return 'stable (no divergence detected)';
|
|
258
|
+
|
|
259
|
+
const decayRatio = decayedCount / totalEdges;
|
|
260
|
+
if (decayRatio > 0.5) return `diverging (${decayedCount}/${totalEdges} edges decayed)`;
|
|
261
|
+
if (decayRatio > 0) return `minor drift (${decayedCount}/${totalEdges} edges decayed)`;
|
|
262
|
+
|
|
263
|
+
return 'stable (no divergence detected)';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Find the highest-scoring affinity pair across all results.
|
|
268
|
+
* @param {Object[]} affinityResults
|
|
269
|
+
* @returns {Object|null} The highest-scoring AffinityResult, or null
|
|
270
|
+
*/
|
|
271
|
+
function _findHighestAffinityPair(affinityResults) {
|
|
272
|
+
if (!affinityResults || affinityResults.length === 0) return null;
|
|
273
|
+
|
|
274
|
+
let best = affinityResults[0];
|
|
275
|
+
for (let i = 1; i < affinityResults.length; i++) {
|
|
276
|
+
if (affinityResults[i].compositeScore > best.compositeScore) {
|
|
277
|
+
best = affinityResults[i];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return best;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = {
|
|
284
|
+
_buildAffinityMap,
|
|
285
|
+
_pairKey,
|
|
286
|
+
_computeAvgAffinity,
|
|
287
|
+
_countDormantMembers,
|
|
288
|
+
_isNodeDormant,
|
|
289
|
+
_countAllDormantNodes,
|
|
290
|
+
_getJoinedDate,
|
|
291
|
+
_buildPairwiseRows,
|
|
292
|
+
_extractSharedConcepts,
|
|
293
|
+
_computeDriftStatus,
|
|
294
|
+
_findHighestAffinityPair,
|
|
295
|
+
};
|