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,80 @@
|
|
|
1
|
+
// @cap-feature(feature:F-058) Claude-Code Plugin Manifest — shared constants + helpers for plugin / marketplace / npx name handling.
|
|
2
|
+
// @cap-decision CAP Pro 1.0 rebrand: plugin/marketplace/npm names all unified to `cap-pro`. The
|
|
3
|
+
// /cap:* slash-command namespace is preserved at the file-layout level (commands/cap/*.md), not
|
|
4
|
+
// via the plugin name itself. Centralising the names in this module so cap-doctor, installer, and
|
|
5
|
+
// tests reference the same source of truth.
|
|
6
|
+
// @cap-constraint Zero external deps — node: built-ins only.
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Plugin name in `.claude-plugin/plugin.json` and the value Claude's plugin
|
|
12
|
+
* cache prefixes its directory entries with (`cap-pro@<source>/`).
|
|
13
|
+
* The /cap:* slash-command namespace is preserved by the file layout
|
|
14
|
+
* (commands/cap/*.md), not by this name.
|
|
15
|
+
*/
|
|
16
|
+
const PLUGIN_NAME = 'cap-pro';
|
|
17
|
+
|
|
18
|
+
/** Marketplace slug — the value `/plugin install <x>` resolves against. */
|
|
19
|
+
const MARKETPLACE_NAME = 'cap-pro';
|
|
20
|
+
|
|
21
|
+
/** npm package name (matches MARKETPLACE_NAME deliberately — one distribution, two channels). */
|
|
22
|
+
const NPM_PACKAGE_NAME = 'cap-pro';
|
|
23
|
+
|
|
24
|
+
/** Legacy npm/plugin/marketplace name kept for cleanup/migration logic. Frozen at code-as-plan@7.x. */
|
|
25
|
+
const LEGACY_NPM_PACKAGE_NAME = 'code-as-plan';
|
|
26
|
+
const LEGACY_PLUGIN_NAME = 'cap';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Names reserved by Anthropic / Claude Code — MUST NOT be used as the marketplace `name` field
|
|
30
|
+
* per the Claude Code plugin specification. Kept as an array so consumers can `.includes()`
|
|
31
|
+
* or enumerate for error messages.
|
|
32
|
+
* @cap-decision Reserved list is hard-coded rather than fetched — the upstream list changes
|
|
33
|
+
* rarely and a network fetch here would couple doctor health checks to registry availability.
|
|
34
|
+
*/
|
|
35
|
+
const RESERVED_MARKETPLACE_NAMES = Object.freeze([
|
|
36
|
+
'claude-code-marketplace',
|
|
37
|
+
'claude-code-plugins',
|
|
38
|
+
'claude-plugins-official',
|
|
39
|
+
'anthropic-marketplace',
|
|
40
|
+
'anthropic-plugins',
|
|
41
|
+
'agent-skills',
|
|
42
|
+
'knowledge-work-plugins',
|
|
43
|
+
'life-sciences',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const RESERVED_SET = new Set(RESERVED_MARKETPLACE_NAMES);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} name
|
|
50
|
+
* @returns {boolean} true if `name` is on the reserved marketplace name list
|
|
51
|
+
*/
|
|
52
|
+
function isReservedMarketplaceName(name) {
|
|
53
|
+
return typeof name === 'string' && RESERVED_SET.has(name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A `.claude-plugin/plugin.json` is a CAP footprint if its `name` matches PLUGIN_NAME
|
|
58
|
+
* (current — `cap-pro`) or LEGACY_PLUGIN_NAME (pre-1.0 — `cap`). Backward-compat is
|
|
59
|
+
* deliberate: a user who installed `code-as-plan@7.x` still has `cap` plugin manifests
|
|
60
|
+
* in `~/.claude/plugins/cache/`, and the doctor should detect them so the cleanup pass
|
|
61
|
+
* can offer to remove them. A differently-named manifest belongs to another plugin and
|
|
62
|
+
* must not be counted as a CAP install.
|
|
63
|
+
* @param {unknown} parsedManifest
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
function isCapPluginManifest(parsedManifest) {
|
|
67
|
+
if (!parsedManifest || typeof parsedManifest !== 'object') return false;
|
|
68
|
+
return parsedManifest.name === PLUGIN_NAME || parsedManifest.name === LEGACY_PLUGIN_NAME;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
PLUGIN_NAME,
|
|
73
|
+
MARKETPLACE_NAME,
|
|
74
|
+
NPM_PACKAGE_NAME,
|
|
75
|
+
LEGACY_PLUGIN_NAME,
|
|
76
|
+
LEGACY_NPM_PACKAGE_NAME,
|
|
77
|
+
RESERVED_MARKETPLACE_NAMES,
|
|
78
|
+
isReservedMarketplaceName,
|
|
79
|
+
isCapPluginManifest,
|
|
80
|
+
};
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// @cap-feature(feature:F-039) Realtime Affinity Detection — evaluates 4 realtime signals against all existing threads during an active session, surfaces results via gradient UX bands, caches in SESSION.json
|
|
2
|
+
// @cap-decision I/O module — loads threads, graph, and session state to orchestrate realtime affinity detection. Pure logic (detect, format) separated from I/O (load, cache).
|
|
3
|
+
// @cap-decision Gradient UX — presentation scales with affinity weight: urgent gets full context block, notify gets one-liner, silent is stored only, discard is dropped entirely.
|
|
4
|
+
// @cap-decision Session caching under realtimeAffinity key — persists results across agent hand-offs within the same session without recomputation.
|
|
5
|
+
// @cap-constraint Zero external dependencies — uses only Node.js built-ins (fs, path) and sibling CAP modules.
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
|
|
11
|
+
// --- Lazy Requires ---
|
|
12
|
+
// @cap-decision Lazy require pattern avoids circular dependency issues and keeps startup fast — modules loaded only when needed.
|
|
13
|
+
|
|
14
|
+
/** @returns {import('./cap-affinity-engine.cjs')} */
|
|
15
|
+
function _engine() { return require('./cap-affinity-engine.cjs'); }
|
|
16
|
+
|
|
17
|
+
/** @returns {import('./cap-thread-tracker.cjs')} */
|
|
18
|
+
function _tracker() { return require('./cap-thread-tracker.cjs'); }
|
|
19
|
+
|
|
20
|
+
/** @returns {import('./cap-memory-graph.cjs')} */
|
|
21
|
+
function _graph() { return require('./cap-memory-graph.cjs'); }
|
|
22
|
+
|
|
23
|
+
/** @returns {import('./cap-session.cjs')} */
|
|
24
|
+
function _session() { return require('./cap-session.cjs'); }
|
|
25
|
+
|
|
26
|
+
// --- Types ---
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} RealtimeMatch
|
|
30
|
+
* @property {string} threadId - Target thread ID
|
|
31
|
+
* @property {string} threadName - Target thread name
|
|
32
|
+
* @property {number} score - Composite affinity score (0.0-1.0)
|
|
33
|
+
* @property {string} band - Affinity band: 'urgent'|'notify'|'silent'
|
|
34
|
+
* @property {string} strongestSignal - Name of the highest-scoring signal
|
|
35
|
+
* @property {number} strongestScore - Score of the strongest signal (0.0-1.0)
|
|
36
|
+
* @property {string} strongestReason - Human-readable reason from the strongest signal
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} RealtimeResult
|
|
41
|
+
* @property {string} activeThreadId - The thread being compared against
|
|
42
|
+
* @property {string} computedAt - ISO timestamp of computation
|
|
43
|
+
* @property {RealtimeMatch[]} matches - Matches sorted by score descending (excludes discard band)
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {Object} FormattedNotification
|
|
48
|
+
* @property {string} band - 'urgent'|'notify'|'silent'
|
|
49
|
+
* @property {string} text - Formatted display text
|
|
50
|
+
* @property {string} threadId - Thread ID for follow-up actions
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// --- Core Detection ---
|
|
54
|
+
|
|
55
|
+
// @cap-todo(ac:F-039/AC-1) During an active session, evaluate 4 realtime signals against all existing threads whenever the active thread context changes
|
|
56
|
+
// @cap-todo(ac:F-039/AC-2) Realtime evaluation of all 4 signals against full thread index shall complete within 200ms
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Detect realtime affinity between the active thread and all other threads.
|
|
60
|
+
* Evaluates only the 4 realtime signals (feature-id-overlap, shared-files,
|
|
61
|
+
* temporal-proximity, causal-chains) for speed.
|
|
62
|
+
*
|
|
63
|
+
* @param {import('./cap-thread-tracker.cjs').Thread} activeThread - The currently active thread
|
|
64
|
+
* @param {import('./cap-thread-tracker.cjs').Thread[]} allThreads - All known threads (self is filtered out)
|
|
65
|
+
* @param {import('./cap-affinity-engine.cjs').AffinityContext} context - Graph and thread data
|
|
66
|
+
* @param {import('./cap-affinity-engine.cjs').AffinityConfig} [config] - Optional affinity config
|
|
67
|
+
* @returns {RealtimeResult}
|
|
68
|
+
*/
|
|
69
|
+
function detectRealtimeAffinity(activeThread, allThreads, context, config) {
|
|
70
|
+
const engine = _engine();
|
|
71
|
+
const cfg = config || engine.loadConfig(process.cwd());
|
|
72
|
+
const computedAt = new Date().toISOString();
|
|
73
|
+
const matches = [];
|
|
74
|
+
|
|
75
|
+
for (const thread of allThreads) {
|
|
76
|
+
// Skip self-comparison
|
|
77
|
+
if (thread.id === activeThread.id) continue;
|
|
78
|
+
|
|
79
|
+
const result = engine.computeRealtimeAffinity(activeThread, thread, context, cfg);
|
|
80
|
+
|
|
81
|
+
// Discard band is not stored
|
|
82
|
+
if (result.band === 'discard') continue;
|
|
83
|
+
|
|
84
|
+
// Find strongest signal
|
|
85
|
+
const strongest = _findStrongestSignal(result.signals);
|
|
86
|
+
|
|
87
|
+
matches.push({
|
|
88
|
+
threadId: thread.id,
|
|
89
|
+
threadName: thread.name || thread.id,
|
|
90
|
+
score: result.compositeScore,
|
|
91
|
+
band: result.band,
|
|
92
|
+
strongestSignal: strongest.signal,
|
|
93
|
+
strongestScore: strongest.score,
|
|
94
|
+
strongestReason: strongest.reason,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Sort by score descending
|
|
99
|
+
matches.sort((a, b) => b.score - a.score);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
activeThreadId: activeThread.id,
|
|
103
|
+
computedAt,
|
|
104
|
+
matches,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find the highest-scoring signal from a signal results array.
|
|
110
|
+
* @param {import('./cap-affinity-engine.cjs').SignalResult[]} signals
|
|
111
|
+
* @returns {import('./cap-affinity-engine.cjs').SignalResult}
|
|
112
|
+
*/
|
|
113
|
+
function _findStrongestSignal(signals) {
|
|
114
|
+
if (!signals || signals.length === 0) {
|
|
115
|
+
return { signal: 'unknown', score: 0, reason: 'No signals computed' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let best = signals[0];
|
|
119
|
+
for (let i = 1; i < signals.length; i++) {
|
|
120
|
+
if (signals[i].score > best.score) {
|
|
121
|
+
best = signals[i];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return best;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Formatting ---
|
|
128
|
+
|
|
129
|
+
// @cap-todo(ac:F-039/AC-3) Threads scoring in urgent band (>=0.90) surfaced as full context block with thread name, strongest signal, and load-context offer
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Format an urgent-band match as a full context block.
|
|
133
|
+
* @param {RealtimeMatch} match
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
function formatUrgentBlock(match) {
|
|
137
|
+
return [
|
|
138
|
+
'',
|
|
139
|
+
'--- Strongly related thread found ---',
|
|
140
|
+
`Thread: "${match.threadName}" (${match.threadId})`,
|
|
141
|
+
`Strongest signal: ${match.strongestSignal} (${match.strongestScore.toFixed(2)}) — ${match.strongestReason}`,
|
|
142
|
+
'',
|
|
143
|
+
'Load this thread\'s context? [yes/no]',
|
|
144
|
+
'',
|
|
145
|
+
].join('\n');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// @cap-todo(ac:F-039/AC-4) Threads scoring in notify band (0.75-0.89) surfaced as compact single-line notification
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Format a notify-band match as a compact one-liner.
|
|
152
|
+
* @param {RealtimeMatch} match
|
|
153
|
+
* @returns {string}
|
|
154
|
+
*/
|
|
155
|
+
function formatNotifyLine(match) {
|
|
156
|
+
return `Related: "${match.threadName}" — ${match.strongestSignal} (${match.strongestScore.toFixed(2)})`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// @cap-todo(ac:F-039/AC-5) Threads scoring in silent band (0.40-0.74) produce no visible output — only queryable via /cap:status
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Format all results according to their band.
|
|
163
|
+
* Silent-band matches are included in the output array but marked as silent
|
|
164
|
+
* so callers can choose whether to display them.
|
|
165
|
+
*
|
|
166
|
+
* @param {RealtimeResult} result - Detection result
|
|
167
|
+
* @returns {FormattedNotification[]}
|
|
168
|
+
*/
|
|
169
|
+
function formatResults(result) {
|
|
170
|
+
const notifications = [];
|
|
171
|
+
|
|
172
|
+
for (const match of result.matches) {
|
|
173
|
+
switch (match.band) {
|
|
174
|
+
case 'urgent':
|
|
175
|
+
notifications.push({
|
|
176
|
+
band: 'urgent',
|
|
177
|
+
text: formatUrgentBlock(match),
|
|
178
|
+
threadId: match.threadId,
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
case 'notify':
|
|
182
|
+
notifications.push({
|
|
183
|
+
band: 'notify',
|
|
184
|
+
text: formatNotifyLine(match),
|
|
185
|
+
threadId: match.threadId,
|
|
186
|
+
});
|
|
187
|
+
break;
|
|
188
|
+
case 'silent':
|
|
189
|
+
// Silent: no visible output, but included for queryability
|
|
190
|
+
notifications.push({
|
|
191
|
+
band: 'silent',
|
|
192
|
+
text: '',
|
|
193
|
+
threadId: match.threadId,
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
// discard: never reaches here (filtered in detectRealtimeAffinity)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return notifications;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --- Session Cache ---
|
|
204
|
+
|
|
205
|
+
// @cap-todo(ac:F-039/AC-7) Realtime affinity results cached in SESSION.json under key realtimeAffinity so they persist across agent hand-offs
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Cache realtime affinity results in SESSION.json under the realtimeAffinity key.
|
|
209
|
+
* @param {string} cwd - Project root directory
|
|
210
|
+
* @param {RealtimeResult} result - Detection results to cache
|
|
211
|
+
*/
|
|
212
|
+
function cacheResults(cwd, result) {
|
|
213
|
+
const session = _session();
|
|
214
|
+
session.updateSession(cwd, {
|
|
215
|
+
realtimeAffinity: {
|
|
216
|
+
activeThreadId: result.activeThreadId,
|
|
217
|
+
computedAt: result.computedAt,
|
|
218
|
+
results: result.matches.map(m => ({
|
|
219
|
+
threadId: m.threadId,
|
|
220
|
+
threadName: m.threadName,
|
|
221
|
+
score: m.score,
|
|
222
|
+
band: m.band,
|
|
223
|
+
strongestSignal: m.strongestSignal,
|
|
224
|
+
strongestScore: m.strongestScore,
|
|
225
|
+
strongestReason: m.strongestReason,
|
|
226
|
+
})),
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Load cached realtime affinity results from SESSION.json.
|
|
233
|
+
* @param {string} cwd - Project root directory
|
|
234
|
+
* @returns {RealtimeResult|null} Cached result or null if not present
|
|
235
|
+
*/
|
|
236
|
+
function loadCachedResults(cwd) {
|
|
237
|
+
const session = _session();
|
|
238
|
+
const data = session.loadSession(cwd);
|
|
239
|
+
const cached = data.realtimeAffinity;
|
|
240
|
+
|
|
241
|
+
if (!cached || !cached.activeThreadId) return null;
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
activeThreadId: cached.activeThreadId,
|
|
245
|
+
computedAt: cached.computedAt,
|
|
246
|
+
matches: (cached.results || []).map(r => ({
|
|
247
|
+
threadId: r.threadId,
|
|
248
|
+
threadName: r.threadName,
|
|
249
|
+
score: r.score,
|
|
250
|
+
band: r.band,
|
|
251
|
+
strongestSignal: r.strongestSignal,
|
|
252
|
+
strongestScore: r.strongestScore,
|
|
253
|
+
strongestReason: r.strongestReason,
|
|
254
|
+
})),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Clear cached realtime affinity results from SESSION.json.
|
|
260
|
+
* @param {string} cwd - Project root directory
|
|
261
|
+
*/
|
|
262
|
+
function clearCache(cwd) {
|
|
263
|
+
const session = _session();
|
|
264
|
+
session.updateSession(cwd, { realtimeAffinity: null });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// --- Integration Hooks ---
|
|
268
|
+
|
|
269
|
+
// @cap-todo(ac:F-039/AC-6) Realtime detector integrates with cap-brainstormer at session start and with cap-thread-tracker when thread context is updated
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Called by cap-brainstormer at session start.
|
|
273
|
+
* Loads all threads, runs realtime affinity detection, caches results,
|
|
274
|
+
* and returns formatted notifications for display.
|
|
275
|
+
*
|
|
276
|
+
* @param {string} cwd - Project root directory
|
|
277
|
+
* @param {import('./cap-thread-tracker.cjs').Thread} activeThread - The active thread for this session
|
|
278
|
+
* @returns {FormattedNotification[]}
|
|
279
|
+
*/
|
|
280
|
+
function onSessionStart(cwd, activeThread) {
|
|
281
|
+
const { allThreads, context, config } = _loadDetectionContext(cwd);
|
|
282
|
+
|
|
283
|
+
const result = detectRealtimeAffinity(activeThread, allThreads, context, config);
|
|
284
|
+
cacheResults(cwd, result);
|
|
285
|
+
return formatResults(result);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Called when thread context changes mid-session (e.g., user switches threads).
|
|
290
|
+
* If the active thread has not changed, returns cached results.
|
|
291
|
+
* Otherwise, recomputes and caches fresh results.
|
|
292
|
+
*
|
|
293
|
+
* @param {string} cwd - Project root directory
|
|
294
|
+
* @param {import('./cap-thread-tracker.cjs').Thread} activeThread - The new active thread
|
|
295
|
+
* @returns {FormattedNotification[]}
|
|
296
|
+
*/
|
|
297
|
+
function onThreadContextChange(cwd, activeThread) {
|
|
298
|
+
// Check if we can reuse cached results
|
|
299
|
+
const cached = loadCachedResults(cwd);
|
|
300
|
+
if (cached && cached.activeThreadId === activeThread.id) {
|
|
301
|
+
return formatResults(cached);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Recompute
|
|
305
|
+
return onSessionStart(cwd, activeThread);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Load all data needed for realtime affinity detection.
|
|
310
|
+
* Consolidates thread index loading, thread data loading, graph loading,
|
|
311
|
+
* and config loading into a single helper.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} cwd - Project root directory
|
|
314
|
+
* @returns {{ allThreads: import('./cap-thread-tracker.cjs').Thread[], context: import('./cap-affinity-engine.cjs').AffinityContext, config: import('./cap-affinity-engine.cjs').AffinityConfig }}
|
|
315
|
+
*/
|
|
316
|
+
function _loadDetectionContext(cwd) {
|
|
317
|
+
const tracker = _tracker();
|
|
318
|
+
const graphMod = _graph();
|
|
319
|
+
const engine = _engine();
|
|
320
|
+
|
|
321
|
+
const index = tracker.loadIndex(cwd);
|
|
322
|
+
const graph = graphMod.loadGraph(cwd);
|
|
323
|
+
const config = engine.loadConfig(cwd);
|
|
324
|
+
|
|
325
|
+
// Load full thread data for each entry in the index
|
|
326
|
+
const allThreads = [];
|
|
327
|
+
for (const entry of index.threads || []) {
|
|
328
|
+
const thread = tracker.loadThread(cwd, entry.id);
|
|
329
|
+
if (thread) {
|
|
330
|
+
allThreads.push(thread);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const context = {
|
|
335
|
+
graph,
|
|
336
|
+
allThreads,
|
|
337
|
+
threadIndex: index.threads || [],
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
return { allThreads, context, config };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// --- Query ---
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Return cached silent-band matches for /cap:status display.
|
|
347
|
+
* Silent matches produce no visible output during normal flow but
|
|
348
|
+
* are accessible on demand.
|
|
349
|
+
*
|
|
350
|
+
* @param {string} cwd - Project root directory
|
|
351
|
+
* @returns {RealtimeMatch[]} Silent-band matches, or empty array if no cache
|
|
352
|
+
*/
|
|
353
|
+
function getSilentMatches(cwd) {
|
|
354
|
+
const cached = loadCachedResults(cwd);
|
|
355
|
+
if (!cached) return [];
|
|
356
|
+
return cached.matches.filter(m => m.band === 'silent');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Return all cached matches regardless of band, for diagnostic use.
|
|
361
|
+
* @param {string} cwd - Project root directory
|
|
362
|
+
* @returns {RealtimeMatch[]} All cached matches, or empty array
|
|
363
|
+
*/
|
|
364
|
+
function getAllCachedMatches(cwd) {
|
|
365
|
+
const cached = loadCachedResults(cwd);
|
|
366
|
+
if (!cached) return [];
|
|
367
|
+
return cached.matches;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// --- Module Exports ---
|
|
371
|
+
|
|
372
|
+
// @cap-decision Exporting internal helpers prefixed with _ for testing, following project convention.
|
|
373
|
+
|
|
374
|
+
module.exports = {
|
|
375
|
+
// Core detection
|
|
376
|
+
detectRealtimeAffinity,
|
|
377
|
+
|
|
378
|
+
// Formatting
|
|
379
|
+
formatUrgentBlock,
|
|
380
|
+
formatNotifyLine,
|
|
381
|
+
formatResults,
|
|
382
|
+
|
|
383
|
+
// Session cache
|
|
384
|
+
cacheResults,
|
|
385
|
+
loadCachedResults,
|
|
386
|
+
clearCache,
|
|
387
|
+
|
|
388
|
+
// Integration hooks
|
|
389
|
+
onSessionStart,
|
|
390
|
+
onThreadContextChange,
|
|
391
|
+
|
|
392
|
+
// Query
|
|
393
|
+
getSilentMatches,
|
|
394
|
+
getAllCachedMatches,
|
|
395
|
+
|
|
396
|
+
// Internal (for testing)
|
|
397
|
+
_findStrongestSignal,
|
|
398
|
+
_loadDetectionContext,
|
|
399
|
+
};
|