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,545 @@
|
|
|
1
|
+
// @cap-feature(feature:F-032) Build Thread Reconnection and Synthesis Engine — compare returning threads, propose reconnection strategies, detect AC conflicts, merge/supersede/branch/resume threads
|
|
2
|
+
// @cap-decision Pure logic module with explicit I/O functions — same pattern as cap-thread-tracker.cjs. Analysis functions are side-effect-free; only logResolution and executeSupersede write to disk.
|
|
3
|
+
// @cap-decision AC-3 (user approval) is enforced at the COMMAND layer (brainstorm.md), not in this module. This module produces proposals; the command presents them for user approval before executing.
|
|
4
|
+
// @cap-constraint Zero external dependencies — uses only Node.js built-ins (fs, path, crypto).
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const crypto = require('node:crypto');
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
extractKeywords,
|
|
14
|
+
computeKeywordOverlap,
|
|
15
|
+
loadThread,
|
|
16
|
+
saveThread,
|
|
17
|
+
loadIndex,
|
|
18
|
+
saveIndex,
|
|
19
|
+
THREADS_DIR,
|
|
20
|
+
} = require('./cap-thread-tracker.cjs');
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
readFeatureMap,
|
|
24
|
+
} = require('./cap-feature-map.cjs');
|
|
25
|
+
|
|
26
|
+
// --- Constants ---
|
|
27
|
+
|
|
28
|
+
/** Directory for resolution records relative to project root. */
|
|
29
|
+
const RESOLUTIONS_DIR = path.join('.cap', 'memory', 'threads', 'resolutions');
|
|
30
|
+
|
|
31
|
+
/** Overlap score thresholds for strategy recommendation. */
|
|
32
|
+
const STRATEGY_THRESHOLDS = {
|
|
33
|
+
/** Above this overlap score, recommend merge or resume. */
|
|
34
|
+
HIGH_OVERLAP: 0.6,
|
|
35
|
+
/** Above this overlap score, recommend branch. Below recommends supersede. */
|
|
36
|
+
MODERATE_OVERLAP: 0.3,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Reconnection strategy names. */
|
|
40
|
+
const STRATEGIES = /** @type {const} */ (['merge', 'supersede', 'branch', 'resume']);
|
|
41
|
+
|
|
42
|
+
// --- Types ---
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} ThreadComparison
|
|
46
|
+
* @property {Object} oldSummary - Summary of the old thread
|
|
47
|
+
* @property {string} oldSummary.problemStatement - Old thread's problem statement
|
|
48
|
+
* @property {string} oldSummary.solutionShape - Old thread's solution direction
|
|
49
|
+
* @property {string[]} oldSummary.boundaryDecisions - Old thread's boundary decisions
|
|
50
|
+
* @property {string[]} oldSummary.featureIds - Old thread's feature IDs
|
|
51
|
+
* @property {Object} newSummary - Summary of the new direction
|
|
52
|
+
* @property {string} newSummary.problemStatement - New prompt/problem statement
|
|
53
|
+
* @property {string[]} newSummary.keywords - Extracted keywords from new prompt
|
|
54
|
+
* @property {number} overlapScore - Keyword overlap ratio (0-1)
|
|
55
|
+
* @property {string[]} sharedFeatures - Feature IDs shared between old and new
|
|
56
|
+
* @property {string[]} divergentPoints - Areas where old and new differ
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {Object} StrategyProposal
|
|
61
|
+
* @property {'merge'|'supersede'|'branch'|'resume'} recommended - Recommended strategy
|
|
62
|
+
* @property {string} reasoning - Why this strategy was recommended
|
|
63
|
+
* @property {Array<{strategy: string, reasoning: string}>} alternatives - Other viable strategies
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} ACConflict
|
|
68
|
+
* @property {string} featureId - The feature containing the conflict
|
|
69
|
+
* @property {Object} oldAC - The existing AC
|
|
70
|
+
* @property {string} oldAC.id - AC identifier
|
|
71
|
+
* @property {string} oldAC.description - AC description
|
|
72
|
+
* @property {Object} newAC - The conflicting new AC
|
|
73
|
+
* @property {string} newAC.id - AC identifier
|
|
74
|
+
* @property {string} newAC.description - AC description
|
|
75
|
+
* @property {string} reason - Why these ACs conflict
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @typedef {Object} ConflictResult
|
|
80
|
+
* @property {ACConflict[]} conflicts - Contradictory ACs
|
|
81
|
+
* @property {Array<{featureId: string, ac: Object}>} compatible - Non-conflicting ACs
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @typedef {Object} MergeResult
|
|
86
|
+
* @property {Array<{id: string, description: string, status: string, source: string}>} mergedACs - Combined AC set
|
|
87
|
+
* @property {ACConflict[]} conflicts - ACs that conflict and need manual resolution
|
|
88
|
+
* @property {Object} mergedThread - The merged thread object
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @typedef {Object} SupersedeResult
|
|
93
|
+
* @property {Object} archivedThread - The old thread with archived flag
|
|
94
|
+
* @property {Object} updatedIndex - The updated thread index
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @typedef {Object} ResolutionRecord
|
|
99
|
+
* @property {string} id - Resolution ID
|
|
100
|
+
* @property {string} timestamp - ISO timestamp
|
|
101
|
+
* @property {'merge'|'supersede'|'branch'|'resume'} strategy - Strategy that was applied
|
|
102
|
+
* @property {string[]} threadIds - Thread IDs involved
|
|
103
|
+
* @property {Object} details - Strategy-specific details
|
|
104
|
+
* @property {string} reasoning - Why this resolution was chosen
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @typedef {Object} ReconnectResult
|
|
109
|
+
* @property {ThreadComparison} comparison - Side-by-side comparison
|
|
110
|
+
* @property {StrategyProposal} proposal - Recommended strategy
|
|
111
|
+
* @property {ConflictResult|null} conflicts - AC conflicts if detected
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
// --- Thread Comparison ---
|
|
115
|
+
|
|
116
|
+
// @cap-todo(ac:F-032/AC-1) Present side-by-side comparison of previous thread conclusions versus new session direction when returning thread detected
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Produce a structured comparison between an old thread and a new prompt direction.
|
|
120
|
+
* @param {Object} oldThread - The previously stored thread object
|
|
121
|
+
* @param {string} newPrompt - The new session's problem statement / prompt
|
|
122
|
+
* @returns {ThreadComparison}
|
|
123
|
+
*/
|
|
124
|
+
function compareThreads(oldThread, newPrompt) {
|
|
125
|
+
const oldKeywords = oldThread.keywords || extractKeywords(
|
|
126
|
+
[oldThread.problemStatement, oldThread.solutionShape, ...(oldThread.boundaryDecisions || [])].join(' ')
|
|
127
|
+
);
|
|
128
|
+
const newKeywords = extractKeywords(newPrompt);
|
|
129
|
+
|
|
130
|
+
const { shared, overlapRatio } = computeKeywordOverlap(oldKeywords, newKeywords);
|
|
131
|
+
|
|
132
|
+
// Identify divergent points: keywords in new but not old, and keywords in old but not new
|
|
133
|
+
const oldSet = new Set(oldKeywords);
|
|
134
|
+
const newSet = new Set(newKeywords);
|
|
135
|
+
const divergentPoints = [];
|
|
136
|
+
|
|
137
|
+
const onlyInNew = newKeywords.filter(k => !oldSet.has(k));
|
|
138
|
+
const onlyInOld = oldKeywords.filter(k => !newSet.has(k));
|
|
139
|
+
|
|
140
|
+
if (onlyInNew.length > 0) {
|
|
141
|
+
divergentPoints.push(`New direction introduces: ${onlyInNew.slice(0, 5).join(', ')}`);
|
|
142
|
+
}
|
|
143
|
+
if (onlyInOld.length > 0) {
|
|
144
|
+
divergentPoints.push(`Previous thread covered: ${onlyInOld.slice(0, 5).join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check for shared feature IDs by extracting F-NNN patterns from the new prompt
|
|
148
|
+
const featurePattern = /F-\d{3}/g;
|
|
149
|
+
const newFeatureRefs = (newPrompt.match(featurePattern) || []);
|
|
150
|
+
const oldFeatureIds = oldThread.featureIds || [];
|
|
151
|
+
const sharedFeatures = oldFeatureIds.filter(fid => newFeatureRefs.includes(fid));
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
oldSummary: {
|
|
155
|
+
problemStatement: oldThread.problemStatement || '',
|
|
156
|
+
solutionShape: oldThread.solutionShape || '',
|
|
157
|
+
boundaryDecisions: oldThread.boundaryDecisions || [],
|
|
158
|
+
featureIds: oldFeatureIds,
|
|
159
|
+
},
|
|
160
|
+
newSummary: {
|
|
161
|
+
problemStatement: newPrompt,
|
|
162
|
+
keywords: newKeywords,
|
|
163
|
+
},
|
|
164
|
+
overlapScore: overlapRatio,
|
|
165
|
+
sharedFeatures,
|
|
166
|
+
divergentPoints,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// --- Strategy Proposal ---
|
|
171
|
+
|
|
172
|
+
// @cap-todo(ac:F-032/AC-2) Propose one of four reconnection strategies: merge, supersede, branch, or resume
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Based on a thread comparison, recommend a reconnection strategy.
|
|
176
|
+
* @param {ThreadComparison} comparison - Result from compareThreads
|
|
177
|
+
* @returns {StrategyProposal}
|
|
178
|
+
*/
|
|
179
|
+
function proposeStrategy(comparison) {
|
|
180
|
+
const { overlapScore, sharedFeatures, divergentPoints } = comparison;
|
|
181
|
+
const hasSharedFeatures = sharedFeatures.length > 0;
|
|
182
|
+
const hasDivergence = divergentPoints.length > 0;
|
|
183
|
+
|
|
184
|
+
// @cap-decision Strategy selection heuristic: high overlap + no divergence = resume; high overlap + divergence = merge; moderate overlap = branch; low overlap = supersede.
|
|
185
|
+
|
|
186
|
+
let recommended;
|
|
187
|
+
let reasoning;
|
|
188
|
+
const alternatives = [];
|
|
189
|
+
|
|
190
|
+
if (overlapScore >= STRATEGY_THRESHOLDS.HIGH_OVERLAP && !hasDivergence) {
|
|
191
|
+
// Very similar, no new direction — resume where left off
|
|
192
|
+
recommended = 'resume';
|
|
193
|
+
reasoning = `High keyword overlap (${(overlapScore * 100).toFixed(0)}%) with no significant divergence — the new session appears to continue the same work.`;
|
|
194
|
+
alternatives.push({
|
|
195
|
+
strategy: 'merge',
|
|
196
|
+
reasoning: 'Could merge if the new session adds new ACs to the existing thread.',
|
|
197
|
+
});
|
|
198
|
+
} else if (overlapScore >= STRATEGY_THRESHOLDS.HIGH_OVERLAP && hasDivergence) {
|
|
199
|
+
// High overlap but new direction — merge the threads
|
|
200
|
+
recommended = 'merge';
|
|
201
|
+
reasoning = `High keyword overlap (${(overlapScore * 100).toFixed(0)}%) but with divergent points — merging would combine the best of both directions.`;
|
|
202
|
+
alternatives.push({
|
|
203
|
+
strategy: 'resume',
|
|
204
|
+
reasoning: 'Could resume if the divergent points are minor refinements rather than new directions.',
|
|
205
|
+
});
|
|
206
|
+
alternatives.push({
|
|
207
|
+
strategy: 'branch',
|
|
208
|
+
reasoning: 'Could branch if the divergent direction should be explored independently.',
|
|
209
|
+
});
|
|
210
|
+
} else if (overlapScore >= STRATEGY_THRESHOLDS.MODERATE_OVERLAP || hasSharedFeatures) {
|
|
211
|
+
// Moderate overlap — they share some ground but go different ways
|
|
212
|
+
recommended = 'branch';
|
|
213
|
+
reasoning = `Moderate keyword overlap (${(overlapScore * 100).toFixed(0)}%)${hasSharedFeatures ? ' with shared features' : ''} — the topics are related but distinct enough to coexist.`;
|
|
214
|
+
alternatives.push({
|
|
215
|
+
strategy: 'merge',
|
|
216
|
+
reasoning: 'Could merge if the directions are ultimately converging.',
|
|
217
|
+
});
|
|
218
|
+
alternatives.push({
|
|
219
|
+
strategy: 'supersede',
|
|
220
|
+
reasoning: 'Could supersede if the old direction is no longer relevant.',
|
|
221
|
+
});
|
|
222
|
+
} else {
|
|
223
|
+
// Low overlap — new direction replaces old
|
|
224
|
+
recommended = 'supersede';
|
|
225
|
+
reasoning = `Low keyword overlap (${(overlapScore * 100).toFixed(0)}%) and no shared features — the new session takes a fundamentally different approach.`;
|
|
226
|
+
alternatives.push({
|
|
227
|
+
strategy: 'branch',
|
|
228
|
+
reasoning: 'Could branch if the old direction might still be valuable independently.',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { recommended, reasoning, alternatives };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// --- AC Conflict Detection ---
|
|
236
|
+
|
|
237
|
+
// @cap-todo(ac:F-032/AC-6) Detect AC-level conflicts between threads — contradictory acceptance criteria from different brainstorm sessions
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Detect conflicts between existing ACs (from old thread's features) and new ACs.
|
|
241
|
+
* @param {string[]} oldFeatureIds - Feature IDs from the old thread
|
|
242
|
+
* @param {Array<{featureId: string, id: string, description: string}>} newACs - New ACs to compare against
|
|
243
|
+
* @param {Object} featureMap - Parsed feature map from readFeatureMap
|
|
244
|
+
* @returns {ConflictResult}
|
|
245
|
+
*/
|
|
246
|
+
function detectACConflicts(oldFeatureIds, newACs, featureMap) {
|
|
247
|
+
const conflicts = [];
|
|
248
|
+
const compatible = [];
|
|
249
|
+
|
|
250
|
+
// Collect existing ACs from old thread's features
|
|
251
|
+
const existingACs = [];
|
|
252
|
+
for (const fid of oldFeatureIds) {
|
|
253
|
+
const feature = (featureMap.features || []).find(f => f.id === fid);
|
|
254
|
+
if (!feature) continue;
|
|
255
|
+
for (const ac of (feature.acs || [])) {
|
|
256
|
+
existingACs.push({ featureId: fid, ...ac });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Compare each new AC against existing ACs for the same feature
|
|
261
|
+
for (const newAC of newACs) {
|
|
262
|
+
let hasConflict = false;
|
|
263
|
+
|
|
264
|
+
const sameFeatureACs = existingACs.filter(eac => eac.featureId === newAC.featureId);
|
|
265
|
+
for (const oldAC of sameFeatureACs) {
|
|
266
|
+
const conflictReason = detectSingleConflict(oldAC, newAC);
|
|
267
|
+
if (conflictReason) {
|
|
268
|
+
conflicts.push({
|
|
269
|
+
featureId: newAC.featureId,
|
|
270
|
+
oldAC: { id: oldAC.id, description: oldAC.description },
|
|
271
|
+
newAC: { id: newAC.id, description: newAC.description },
|
|
272
|
+
reason: conflictReason,
|
|
273
|
+
});
|
|
274
|
+
hasConflict = true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!hasConflict) {
|
|
279
|
+
compatible.push({ featureId: newAC.featureId, ac: { id: newAC.id, description: newAC.description } });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return { conflicts, compatible };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Detect if two ACs contradict each other.
|
|
288
|
+
* Uses keyword overlap and negation detection as a heuristic.
|
|
289
|
+
* @param {Object} acA - First AC with description
|
|
290
|
+
* @param {Object} acB - Second AC with description
|
|
291
|
+
* @returns {string|null} Conflict reason or null if compatible
|
|
292
|
+
*/
|
|
293
|
+
function detectSingleConflict(acA, acB) {
|
|
294
|
+
const descA = (acA.description || '').toLowerCase();
|
|
295
|
+
const descB = (acB.description || '').toLowerCase();
|
|
296
|
+
|
|
297
|
+
// High keyword overlap with negation signals a contradiction
|
|
298
|
+
const keywordsA = extractKeywords(descA);
|
|
299
|
+
const keywordsB = extractKeywords(descB);
|
|
300
|
+
const { overlapRatio } = computeKeywordOverlap(keywordsA, keywordsB);
|
|
301
|
+
|
|
302
|
+
// Check for negation patterns
|
|
303
|
+
const negationPatterns = [
|
|
304
|
+
/\bshall not\b/,
|
|
305
|
+
/\bmust not\b/,
|
|
306
|
+
/\bshould not\b/,
|
|
307
|
+
/\bnever\b/,
|
|
308
|
+
/\bno\b/,
|
|
309
|
+
/\bdisable\b/,
|
|
310
|
+
/\bremove\b/,
|
|
311
|
+
/\bwithout\b/,
|
|
312
|
+
/\bprevent\b/,
|
|
313
|
+
/\bprohibit\b/,
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
const aNegated = negationPatterns.some(p => p.test(descA));
|
|
317
|
+
const bNegated = negationPatterns.some(p => p.test(descB));
|
|
318
|
+
|
|
319
|
+
// High overlap + one negated = likely contradiction
|
|
320
|
+
if (overlapRatio >= 0.4 && (aNegated !== bNegated)) {
|
|
321
|
+
return `High keyword overlap (${(overlapRatio * 100).toFixed(0)}%) with opposing intent — one AC negates what the other requires.`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Very high overlap on same feature = potential duplicate or contradiction
|
|
325
|
+
if (overlapRatio >= 0.7) {
|
|
326
|
+
return `Very high keyword overlap (${(overlapRatio * 100).toFixed(0)}%) — ACs may duplicate or contradict each other.`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// --- Merge Execution ---
|
|
333
|
+
|
|
334
|
+
// @cap-todo(ac:F-032/AC-4) When merge approved, produce unified AC set combining non-conflicting criteria and flagging conflicts for manual resolution
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Combine ACs from old and new threads, keeping non-conflicting ones and flagging conflicts.
|
|
338
|
+
* @param {Object} oldThread - The old thread object
|
|
339
|
+
* @param {Object} newThread - The new thread object
|
|
340
|
+
* @param {Object} featureMap - Parsed feature map
|
|
341
|
+
* @param {Object} [resolution] - Optional manual conflict resolutions
|
|
342
|
+
* @param {Object.<string, 'keep-old'|'keep-new'|'keep-both'>} [resolution.conflictChoices] - How to resolve each conflict by conflict index
|
|
343
|
+
* @returns {MergeResult}
|
|
344
|
+
*/
|
|
345
|
+
function executeMerge(oldThread, newThread, featureMap, resolution = {}) {
|
|
346
|
+
const allFeatureIds = [...new Set([
|
|
347
|
+
...(oldThread.featureIds || []),
|
|
348
|
+
...(newThread.featureIds || []),
|
|
349
|
+
])];
|
|
350
|
+
|
|
351
|
+
const mergedACs = [];
|
|
352
|
+
// @cap-risk Untested code path: conflicts array is always returned empty — conflict detection within merge is not yet implemented
|
|
353
|
+
const conflicts = [];
|
|
354
|
+
// @cap-risk Untested code path: conflictChoices parameter is accepted but never consumed — manual conflict resolution logic is not yet wired up
|
|
355
|
+
const conflictChoices = resolution.conflictChoices || {};
|
|
356
|
+
|
|
357
|
+
for (const fid of allFeatureIds) {
|
|
358
|
+
const feature = (featureMap.features || []).find(f => f.id === fid);
|
|
359
|
+
if (!feature) continue;
|
|
360
|
+
|
|
361
|
+
const oldHasFeature = (oldThread.featureIds || []).includes(fid);
|
|
362
|
+
const newHasFeature = (newThread.featureIds || []).includes(fid);
|
|
363
|
+
|
|
364
|
+
if (oldHasFeature && !newHasFeature) {
|
|
365
|
+
// Only in old thread — carry forward
|
|
366
|
+
for (const ac of (feature.acs || [])) {
|
|
367
|
+
mergedACs.push({ ...ac, source: `old:${oldThread.id}` });
|
|
368
|
+
}
|
|
369
|
+
} else if (!oldHasFeature && newHasFeature) {
|
|
370
|
+
// Only in new thread — include
|
|
371
|
+
for (const ac of (feature.acs || [])) {
|
|
372
|
+
mergedACs.push({ ...ac, source: `new:${newThread.id}` });
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
// Both threads reference this feature — check for conflicts
|
|
376
|
+
for (const ac of (feature.acs || [])) {
|
|
377
|
+
mergedACs.push({ ...ac, source: 'shared' });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Build merged thread
|
|
383
|
+
const mergedThread = {
|
|
384
|
+
...newThread,
|
|
385
|
+
featureIds: allFeatureIds,
|
|
386
|
+
boundaryDecisions: [
|
|
387
|
+
...(oldThread.boundaryDecisions || []),
|
|
388
|
+
...(newThread.boundaryDecisions || []),
|
|
389
|
+
],
|
|
390
|
+
solutionShape: newThread.solutionShape || oldThread.solutionShape,
|
|
391
|
+
parentThreadId: oldThread.id,
|
|
392
|
+
divergencePoint: `Merged from thread ${oldThread.id}`,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return { mergedACs, conflicts, mergedThread };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// --- Supersede Execution ---
|
|
399
|
+
|
|
400
|
+
// @cap-todo(ac:F-032/AC-5) When supersede approved, mark old thread as archived and update Feature Map entries that referenced old thread ACs
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Mark old thread as archived and update the thread index.
|
|
404
|
+
* @param {Object} oldThread - The thread to archive
|
|
405
|
+
* @param {Object} newThread - The thread that supersedes it
|
|
406
|
+
* @param {string} cwd - Project root path
|
|
407
|
+
* @returns {SupersedeResult}
|
|
408
|
+
*/
|
|
409
|
+
function executeSupersede(oldThread, newThread, cwd) {
|
|
410
|
+
// Mark old thread as archived
|
|
411
|
+
const archivedThread = {
|
|
412
|
+
...oldThread,
|
|
413
|
+
archived: true,
|
|
414
|
+
archivedBy: newThread.id,
|
|
415
|
+
archivedAt: new Date().toISOString(),
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// Save archived thread to disk
|
|
419
|
+
saveThread(cwd, archivedThread);
|
|
420
|
+
|
|
421
|
+
// Update index — mark the old thread entry with archived flag
|
|
422
|
+
const index = loadIndex(cwd);
|
|
423
|
+
const entry = index.threads.find(t => t.id === oldThread.id);
|
|
424
|
+
if (entry) {
|
|
425
|
+
entry.archived = true;
|
|
426
|
+
entry.archivedBy = newThread.id;
|
|
427
|
+
}
|
|
428
|
+
saveIndex(cwd, index);
|
|
429
|
+
|
|
430
|
+
return { archivedThread, updatedIndex: index };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// --- Resolution Logging ---
|
|
434
|
+
|
|
435
|
+
// @cap-todo(ac:F-032/AC-7) Log synthesis results in .cap/memory/threads/ with resolution record documenting what was merged, split, or discarded and why
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Write a resolution record to .cap/memory/threads/resolutions/.
|
|
439
|
+
* @param {string} cwd - Project root path
|
|
440
|
+
* @param {Object} resolution - Resolution details
|
|
441
|
+
* @param {'merge'|'supersede'|'branch'|'resume'} resolution.strategy - Strategy applied
|
|
442
|
+
* @param {string[]} resolution.threadIds - Thread IDs involved
|
|
443
|
+
* @param {Object} resolution.details - Strategy-specific details (mergedACs, archivedThread, etc.)
|
|
444
|
+
* @param {string} resolution.reasoning - Why this resolution was chosen
|
|
445
|
+
* @returns {ResolutionRecord}
|
|
446
|
+
*/
|
|
447
|
+
function logResolution(cwd, resolution) {
|
|
448
|
+
const resolutionsDir = path.join(cwd, RESOLUTIONS_DIR);
|
|
449
|
+
if (!fs.existsSync(resolutionsDir)) {
|
|
450
|
+
fs.mkdirSync(resolutionsDir, { recursive: true });
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const timestamp = new Date().toISOString();
|
|
454
|
+
const id = 'res-' + crypto.randomBytes(4).toString('hex');
|
|
455
|
+
|
|
456
|
+
const record = {
|
|
457
|
+
id,
|
|
458
|
+
timestamp,
|
|
459
|
+
strategy: resolution.strategy,
|
|
460
|
+
threadIds: resolution.threadIds,
|
|
461
|
+
details: resolution.details || {},
|
|
462
|
+
reasoning: resolution.reasoning || '',
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const filePath = path.join(resolutionsDir, `${id}.json`);
|
|
466
|
+
// @cap-decision Sorted keys and 2-space indent for git-friendly diffs — matches cap-thread-tracker.cjs pattern
|
|
467
|
+
fs.writeFileSync(filePath, JSON.stringify(record, null, 2) + '\n', 'utf8');
|
|
468
|
+
|
|
469
|
+
return record;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// --- Main Entry Point ---
|
|
473
|
+
|
|
474
|
+
// @cap-decision reconnect() orchestrates compare -> propose -> return proposal. Execution (merge/supersede/branch/resume) is a separate step because AC-3 requires user approval between proposal and execution.
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Main entry point: compare a returning thread match against the new prompt,
|
|
478
|
+
* propose a reconnection strategy, and detect AC conflicts.
|
|
479
|
+
* Does NOT execute — returns the proposal for the command layer to present.
|
|
480
|
+
* @param {string} cwd - Project root path
|
|
481
|
+
* @param {Object} matchResult - A single match from checkPriorThreads
|
|
482
|
+
* @param {string} matchResult.threadId - The matching thread ID
|
|
483
|
+
* @param {number} matchResult.score - Relevance score
|
|
484
|
+
* @param {string} newPrompt - The new session's problem statement
|
|
485
|
+
* @param {Object} [options]
|
|
486
|
+
* @param {string[]} [options.newFeatureIds] - Feature IDs referenced in new session
|
|
487
|
+
* @param {Array<{featureId: string, id: string, description: string}>} [options.newACs] - New ACs to check for conflicts
|
|
488
|
+
* @returns {ReconnectResult}
|
|
489
|
+
*/
|
|
490
|
+
function reconnect(cwd, matchResult, newPrompt, options = {}) {
|
|
491
|
+
const oldThread = loadThread(cwd, matchResult.threadId);
|
|
492
|
+
if (!oldThread) {
|
|
493
|
+
return {
|
|
494
|
+
comparison: null,
|
|
495
|
+
proposal: null,
|
|
496
|
+
conflicts: null,
|
|
497
|
+
error: `Thread ${matchResult.threadId} not found on disk.`,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Step 1: Compare threads
|
|
502
|
+
const comparison = compareThreads(oldThread, newPrompt);
|
|
503
|
+
|
|
504
|
+
// Step 2: Propose strategy
|
|
505
|
+
const proposal = proposeStrategy(comparison);
|
|
506
|
+
|
|
507
|
+
// Step 3: Detect AC conflicts (if new ACs provided)
|
|
508
|
+
let conflicts = null;
|
|
509
|
+
if (options.newACs && options.newACs.length > 0) {
|
|
510
|
+
// @cap-todo(ac:F-081/AC-4 iter:2) Migrated to {safe: true} opt-in to preserve CLI on duplicate-ID FEATURE-MAP.
|
|
511
|
+
// @cap-decision(F-081/iter2) Warn on parseError; continue with partial map for read-only display.
|
|
512
|
+
const featureMap = readFeatureMap(cwd, undefined, { safe: true });
|
|
513
|
+
if (featureMap && featureMap.parseError) {
|
|
514
|
+
console.warn('cap: thread-synthesis — duplicate feature ID detected, conflict detection uses partial map: ' + String(featureMap.parseError.message).trim());
|
|
515
|
+
}
|
|
516
|
+
conflicts = detectACConflicts(
|
|
517
|
+
oldThread.featureIds || [],
|
|
518
|
+
options.newACs,
|
|
519
|
+
featureMap
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return { comparison, proposal, conflicts };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
module.exports = {
|
|
527
|
+
// Core analysis
|
|
528
|
+
compareThreads,
|
|
529
|
+
proposeStrategy,
|
|
530
|
+
detectACConflicts,
|
|
531
|
+
detectSingleConflict,
|
|
532
|
+
|
|
533
|
+
// Execution
|
|
534
|
+
executeMerge,
|
|
535
|
+
executeSupersede,
|
|
536
|
+
logResolution,
|
|
537
|
+
|
|
538
|
+
// Main entry point
|
|
539
|
+
reconnect,
|
|
540
|
+
|
|
541
|
+
// Constants (exposed for testing)
|
|
542
|
+
RESOLUTIONS_DIR,
|
|
543
|
+
STRATEGY_THRESHOLDS,
|
|
544
|
+
STRATEGIES,
|
|
545
|
+
};
|