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.
Files changed (275) hide show
  1. package/.claude-plugin/README.md +26 -0
  2. package/.claude-plugin/marketplace.json +24 -0
  3. package/.claude-plugin/plugin.json +24 -0
  4. package/LICENSE +21 -0
  5. package/README.ja-JP.md +834 -0
  6. package/README.ko-KR.md +823 -0
  7. package/README.md +806 -0
  8. package/README.pt-BR.md +452 -0
  9. package/README.zh-CN.md +800 -0
  10. package/agents/cap-architect.md +269 -0
  11. package/agents/cap-brainstormer.md +207 -0
  12. package/agents/cap-curator.md +276 -0
  13. package/agents/cap-debugger.md +365 -0
  14. package/agents/cap-designer.md +246 -0
  15. package/agents/cap-historian.md +464 -0
  16. package/agents/cap-migrator.md +291 -0
  17. package/agents/cap-prototyper.md +197 -0
  18. package/agents/cap-validator.md +308 -0
  19. package/bin/install.js +5433 -0
  20. package/cap/bin/cap-tools.cjs +853 -0
  21. package/cap/bin/lib/arc-scanner.cjs +344 -0
  22. package/cap/bin/lib/cap-affinity-engine.cjs +862 -0
  23. package/cap/bin/lib/cap-anchor.cjs +228 -0
  24. package/cap/bin/lib/cap-annotation-writer.cjs +340 -0
  25. package/cap/bin/lib/cap-checkpoint.cjs +434 -0
  26. package/cap/bin/lib/cap-cluster-detect.cjs +945 -0
  27. package/cap/bin/lib/cap-cluster-display.cjs +52 -0
  28. package/cap/bin/lib/cap-cluster-format.cjs +245 -0
  29. package/cap/bin/lib/cap-cluster-helpers.cjs +295 -0
  30. package/cap/bin/lib/cap-cluster-io.cjs +212 -0
  31. package/cap/bin/lib/cap-completeness.cjs +540 -0
  32. package/cap/bin/lib/cap-deps.cjs +583 -0
  33. package/cap/bin/lib/cap-design-families.cjs +332 -0
  34. package/cap/bin/lib/cap-design.cjs +966 -0
  35. package/cap/bin/lib/cap-divergence-detector.cjs +400 -0
  36. package/cap/bin/lib/cap-doctor.cjs +752 -0
  37. package/cap/bin/lib/cap-feature-map-internals.cjs +19 -0
  38. package/cap/bin/lib/cap-feature-map-migrate.cjs +335 -0
  39. package/cap/bin/lib/cap-feature-map-monorepo.cjs +885 -0
  40. package/cap/bin/lib/cap-feature-map-shard.cjs +315 -0
  41. package/cap/bin/lib/cap-feature-map.cjs +1943 -0
  42. package/cap/bin/lib/cap-fitness-score.cjs +1075 -0
  43. package/cap/bin/lib/cap-impact-analysis.cjs +652 -0
  44. package/cap/bin/lib/cap-learn-review.cjs +1072 -0
  45. package/cap/bin/lib/cap-learning-signals.cjs +627 -0
  46. package/cap/bin/lib/cap-loader.cjs +227 -0
  47. package/cap/bin/lib/cap-logger.cjs +57 -0
  48. package/cap/bin/lib/cap-memory-bridge.cjs +764 -0
  49. package/cap/bin/lib/cap-memory-confidence.cjs +452 -0
  50. package/cap/bin/lib/cap-memory-dir.cjs +987 -0
  51. package/cap/bin/lib/cap-memory-engine.cjs +698 -0
  52. package/cap/bin/lib/cap-memory-extends.cjs +398 -0
  53. package/cap/bin/lib/cap-memory-graph.cjs +790 -0
  54. package/cap/bin/lib/cap-memory-migrate.cjs +2015 -0
  55. package/cap/bin/lib/cap-memory-pin.cjs +183 -0
  56. package/cap/bin/lib/cap-memory-platform.cjs +490 -0
  57. package/cap/bin/lib/cap-memory-prune.cjs +707 -0
  58. package/cap/bin/lib/cap-memory-schema.cjs +812 -0
  59. package/cap/bin/lib/cap-migrate-tags.cjs +309 -0
  60. package/cap/bin/lib/cap-migrate.cjs +540 -0
  61. package/cap/bin/lib/cap-pattern-apply.cjs +1203 -0
  62. package/cap/bin/lib/cap-pattern-pipeline.cjs +1034 -0
  63. package/cap/bin/lib/cap-plugin-manifest.cjs +80 -0
  64. package/cap/bin/lib/cap-realtime-affinity.cjs +399 -0
  65. package/cap/bin/lib/cap-reconcile.cjs +570 -0
  66. package/cap/bin/lib/cap-research-gate.cjs +218 -0
  67. package/cap/bin/lib/cap-scope-filter.cjs +402 -0
  68. package/cap/bin/lib/cap-semantic-pipeline.cjs +1038 -0
  69. package/cap/bin/lib/cap-session-extract.cjs +987 -0
  70. package/cap/bin/lib/cap-session.cjs +445 -0
  71. package/cap/bin/lib/cap-snapshot-linkage.cjs +963 -0
  72. package/cap/bin/lib/cap-stack-docs.cjs +646 -0
  73. package/cap/bin/lib/cap-tag-observer.cjs +371 -0
  74. package/cap/bin/lib/cap-tag-scanner.cjs +1766 -0
  75. package/cap/bin/lib/cap-telemetry.cjs +466 -0
  76. package/cap/bin/lib/cap-test-audit.cjs +1438 -0
  77. package/cap/bin/lib/cap-thread-migrator.cjs +307 -0
  78. package/cap/bin/lib/cap-thread-synthesis.cjs +545 -0
  79. package/cap/bin/lib/cap-thread-tracker.cjs +519 -0
  80. package/cap/bin/lib/cap-trace.cjs +399 -0
  81. package/cap/bin/lib/cap-trust-mode.cjs +336 -0
  82. package/cap/bin/lib/cap-ui-design-editor.cjs +642 -0
  83. package/cap/bin/lib/cap-ui-mind-map.cjs +712 -0
  84. package/cap/bin/lib/cap-ui-thread-nav.cjs +693 -0
  85. package/cap/bin/lib/cap-ui.cjs +1245 -0
  86. package/cap/bin/lib/cap-upgrade.cjs +1028 -0
  87. package/cap/bin/lib/cli/arg-helpers.cjs +49 -0
  88. package/cap/bin/lib/cli/frontmatter-router.cjs +31 -0
  89. package/cap/bin/lib/cli/init-router.cjs +68 -0
  90. package/cap/bin/lib/cli/phase-router.cjs +102 -0
  91. package/cap/bin/lib/cli/state-router.cjs +61 -0
  92. package/cap/bin/lib/cli/template-router.cjs +37 -0
  93. package/cap/bin/lib/cli/uat-router.cjs +29 -0
  94. package/cap/bin/lib/cli/validation-router.cjs +26 -0
  95. package/cap/bin/lib/cli/verification-router.cjs +31 -0
  96. package/cap/bin/lib/cli/workstream-router.cjs +39 -0
  97. package/cap/bin/lib/commands.cjs +961 -0
  98. package/cap/bin/lib/config.cjs +467 -0
  99. package/cap/bin/lib/convention-reader.cjs +258 -0
  100. package/cap/bin/lib/core.cjs +1241 -0
  101. package/cap/bin/lib/feature-aggregator.cjs +423 -0
  102. package/cap/bin/lib/frontmatter.cjs +337 -0
  103. package/cap/bin/lib/init.cjs +1443 -0
  104. package/cap/bin/lib/manifest-generator.cjs +383 -0
  105. package/cap/bin/lib/milestone.cjs +253 -0
  106. package/cap/bin/lib/model-profiles.cjs +69 -0
  107. package/cap/bin/lib/monorepo-context.cjs +226 -0
  108. package/cap/bin/lib/monorepo-migrator.cjs +509 -0
  109. package/cap/bin/lib/phase.cjs +889 -0
  110. package/cap/bin/lib/profile-output.cjs +989 -0
  111. package/cap/bin/lib/profile-pipeline.cjs +540 -0
  112. package/cap/bin/lib/roadmap.cjs +330 -0
  113. package/cap/bin/lib/security.cjs +394 -0
  114. package/cap/bin/lib/session-manager.cjs +292 -0
  115. package/cap/bin/lib/skeleton-generator.cjs +179 -0
  116. package/cap/bin/lib/state.cjs +1032 -0
  117. package/cap/bin/lib/template.cjs +231 -0
  118. package/cap/bin/lib/test-detector.cjs +62 -0
  119. package/cap/bin/lib/uat.cjs +283 -0
  120. package/cap/bin/lib/verify.cjs +889 -0
  121. package/cap/bin/lib/workspace-detector.cjs +371 -0
  122. package/cap/bin/lib/workstream.cjs +492 -0
  123. package/cap/commands/gsd/workstreams.md +63 -0
  124. package/cap/references/arc-standard.md +315 -0
  125. package/cap/references/cap-agent-architecture.md +101 -0
  126. package/cap/references/cap-gitignore-template +9 -0
  127. package/cap/references/cap-zero-deps.md +158 -0
  128. package/cap/references/checkpoints.md +778 -0
  129. package/cap/references/continuation-format.md +249 -0
  130. package/cap/references/contract-test-templates.md +312 -0
  131. package/cap/references/feature-map-template.md +25 -0
  132. package/cap/references/git-integration.md +295 -0
  133. package/cap/references/git-planning-commit.md +38 -0
  134. package/cap/references/model-profiles.md +174 -0
  135. package/cap/references/phase-numbering.md +126 -0
  136. package/cap/references/planning-config.md +202 -0
  137. package/cap/references/property-test-templates.md +316 -0
  138. package/cap/references/security-test-templates.md +347 -0
  139. package/cap/references/session-template.json +8 -0
  140. package/cap/references/tdd.md +263 -0
  141. package/cap/references/user-profiling.md +681 -0
  142. package/cap/references/verification-patterns.md +612 -0
  143. package/cap/templates/UAT.md +265 -0
  144. package/cap/templates/claude-md.md +175 -0
  145. package/cap/templates/codebase/architecture.md +255 -0
  146. package/cap/templates/codebase/concerns.md +310 -0
  147. package/cap/templates/codebase/conventions.md +307 -0
  148. package/cap/templates/codebase/integrations.md +280 -0
  149. package/cap/templates/codebase/stack.md +186 -0
  150. package/cap/templates/codebase/structure.md +285 -0
  151. package/cap/templates/codebase/testing.md +480 -0
  152. package/cap/templates/config.json +44 -0
  153. package/cap/templates/context.md +352 -0
  154. package/cap/templates/continue-here.md +78 -0
  155. package/cap/templates/copilot-instructions.md +7 -0
  156. package/cap/templates/debug-subagent-prompt.md +91 -0
  157. package/cap/templates/discussion-log.md +63 -0
  158. package/cap/templates/milestone-archive.md +123 -0
  159. package/cap/templates/milestone.md +115 -0
  160. package/cap/templates/phase-prompt.md +610 -0
  161. package/cap/templates/planner-subagent-prompt.md +117 -0
  162. package/cap/templates/project.md +186 -0
  163. package/cap/templates/requirements.md +231 -0
  164. package/cap/templates/research-project/ARCHITECTURE.md +204 -0
  165. package/cap/templates/research-project/FEATURES.md +147 -0
  166. package/cap/templates/research-project/PITFALLS.md +200 -0
  167. package/cap/templates/research-project/STACK.md +120 -0
  168. package/cap/templates/research-project/SUMMARY.md +170 -0
  169. package/cap/templates/research.md +552 -0
  170. package/cap/templates/roadmap.md +202 -0
  171. package/cap/templates/state.md +176 -0
  172. package/cap/templates/summary.md +364 -0
  173. package/cap/templates/user-preferences.md +498 -0
  174. package/cap/templates/verification-report.md +322 -0
  175. package/cap/workflows/add-phase.md +112 -0
  176. package/cap/workflows/add-tests.md +351 -0
  177. package/cap/workflows/add-todo.md +158 -0
  178. package/cap/workflows/audit-milestone.md +340 -0
  179. package/cap/workflows/audit-uat.md +109 -0
  180. package/cap/workflows/autonomous.md +891 -0
  181. package/cap/workflows/check-todos.md +177 -0
  182. package/cap/workflows/cleanup.md +152 -0
  183. package/cap/workflows/complete-milestone.md +767 -0
  184. package/cap/workflows/diagnose-issues.md +231 -0
  185. package/cap/workflows/discovery-phase.md +289 -0
  186. package/cap/workflows/discuss-phase-assumptions.md +653 -0
  187. package/cap/workflows/discuss-phase.md +1049 -0
  188. package/cap/workflows/do.md +104 -0
  189. package/cap/workflows/execute-phase.md +846 -0
  190. package/cap/workflows/execute-plan.md +514 -0
  191. package/cap/workflows/fast.md +105 -0
  192. package/cap/workflows/forensics.md +265 -0
  193. package/cap/workflows/health.md +181 -0
  194. package/cap/workflows/help.md +660 -0
  195. package/cap/workflows/insert-phase.md +130 -0
  196. package/cap/workflows/list-phase-assumptions.md +178 -0
  197. package/cap/workflows/list-workspaces.md +56 -0
  198. package/cap/workflows/manager.md +362 -0
  199. package/cap/workflows/map-codebase.md +377 -0
  200. package/cap/workflows/milestone-summary.md +223 -0
  201. package/cap/workflows/new-milestone.md +486 -0
  202. package/cap/workflows/new-project.md +1250 -0
  203. package/cap/workflows/new-workspace.md +237 -0
  204. package/cap/workflows/next.md +97 -0
  205. package/cap/workflows/node-repair.md +92 -0
  206. package/cap/workflows/note.md +156 -0
  207. package/cap/workflows/pause-work.md +176 -0
  208. package/cap/workflows/plan-milestone-gaps.md +273 -0
  209. package/cap/workflows/plan-phase.md +857 -0
  210. package/cap/workflows/plant-seed.md +169 -0
  211. package/cap/workflows/pr-branch.md +129 -0
  212. package/cap/workflows/profile-user.md +449 -0
  213. package/cap/workflows/progress.md +507 -0
  214. package/cap/workflows/quick.md +757 -0
  215. package/cap/workflows/remove-phase.md +155 -0
  216. package/cap/workflows/remove-workspace.md +90 -0
  217. package/cap/workflows/research-phase.md +82 -0
  218. package/cap/workflows/resume-project.md +326 -0
  219. package/cap/workflows/review.md +228 -0
  220. package/cap/workflows/session-report.md +146 -0
  221. package/cap/workflows/settings.md +283 -0
  222. package/cap/workflows/ship.md +228 -0
  223. package/cap/workflows/stats.md +60 -0
  224. package/cap/workflows/transition.md +671 -0
  225. package/cap/workflows/ui-phase.md +298 -0
  226. package/cap/workflows/ui-review.md +161 -0
  227. package/cap/workflows/update.md +323 -0
  228. package/cap/workflows/validate-phase.md +170 -0
  229. package/cap/workflows/verify-phase.md +254 -0
  230. package/cap/workflows/verify-work.md +637 -0
  231. package/commands/cap/annotate.md +165 -0
  232. package/commands/cap/brainstorm.md +393 -0
  233. package/commands/cap/checkpoint.md +106 -0
  234. package/commands/cap/completeness.md +94 -0
  235. package/commands/cap/continue.md +72 -0
  236. package/commands/cap/debug.md +588 -0
  237. package/commands/cap/deps.md +169 -0
  238. package/commands/cap/design.md +479 -0
  239. package/commands/cap/init.md +354 -0
  240. package/commands/cap/iterate.md +249 -0
  241. package/commands/cap/learn.md +459 -0
  242. package/commands/cap/memory.md +275 -0
  243. package/commands/cap/migrate-feature-map.md +91 -0
  244. package/commands/cap/migrate-memory.md +108 -0
  245. package/commands/cap/migrate-tags.md +91 -0
  246. package/commands/cap/migrate.md +131 -0
  247. package/commands/cap/prototype.md +510 -0
  248. package/commands/cap/reconcile.md +121 -0
  249. package/commands/cap/review.md +360 -0
  250. package/commands/cap/save.md +72 -0
  251. package/commands/cap/scan.md +404 -0
  252. package/commands/cap/start.md +356 -0
  253. package/commands/cap/status.md +118 -0
  254. package/commands/cap/test-audit.md +262 -0
  255. package/commands/cap/test.md +394 -0
  256. package/commands/cap/trace.md +133 -0
  257. package/commands/cap/ui.md +167 -0
  258. package/hooks/dist/cap-check-update.js +115 -0
  259. package/hooks/dist/cap-context-monitor.js +185 -0
  260. package/hooks/dist/cap-learn-review-hook.js +114 -0
  261. package/hooks/dist/cap-learning-hook.js +192 -0
  262. package/hooks/dist/cap-memory.js +299 -0
  263. package/hooks/dist/cap-prompt-guard.js +97 -0
  264. package/hooks/dist/cap-statusline.js +157 -0
  265. package/hooks/dist/cap-tag-observer.js +115 -0
  266. package/hooks/dist/cap-version-check.js +112 -0
  267. package/hooks/dist/cap-workflow-guard.js +175 -0
  268. package/hooks/hooks.json +55 -0
  269. package/package.json +58 -0
  270. package/scripts/base64-scan.sh +262 -0
  271. package/scripts/build-hooks.js +93 -0
  272. package/scripts/cap-removal-checklist.md +202 -0
  273. package/scripts/prompt-injection-scan.sh +199 -0
  274. package/scripts/run-tests.cjs +181 -0
  275. package/scripts/secret-scan.sh +227 -0
@@ -0,0 +1,452 @@
1
+ // @cap-feature(feature:F-055) Confidence and Evidence Fields for Memory Entries — pure-logic module
2
+ // @cap-decision Bullet-list extension of the existing Entry block (not YAML frontmatter) so .cap/memory/*.md stays grep-friendly and diff-readable.
3
+ // @cap-decision Jaccard on word tokens for re-observation similarity: zero-dep, deterministic, cheap. No Levenshtein, no embeddings — we only need coarse "same gist" detection.
4
+ // @cap-constraint Zero external dependencies — only node:-prefixed built-ins (no imports needed here).
5
+
6
+ 'use strict';
7
+
8
+ // @cap-history(sessions:2, edits:8, since:2026-04-21, learned:2026-05-08) Frequently modified — 2 sessions, 8 edits
9
+ // --- Constants ---
10
+
11
+ /** Starting confidence for a freshly observed entry. */
12
+ const DEFAULT_CONFIDENCE = 0.5;
13
+
14
+ /** Starting evidence count for a freshly observed entry. */
15
+ const DEFAULT_EVIDENCE = 1;
16
+
17
+ /** Hard cap on confidence so re-observation never certifies an entry as "known truth". */
18
+ const CONFIDENCE_CAP = 0.95;
19
+
20
+ /** Hard floor on confidence — contradictions can drive it to zero but not below. */
21
+ const CONFIDENCE_FLOOR = 0.0;
22
+
23
+ /** Jaccard threshold at or above which two contents are treated as the same observation. */
24
+ const SIMILARITY_THRESHOLD = 0.8;
25
+
26
+ /** Confidence strictly below this renders as "low confidence" (dimmed). */
27
+ const DIM_THRESHOLD = 0.3;
28
+
29
+ /** Increment applied to confidence on re-observation. */
30
+ const REOBSERVATION_BUMP = 0.1;
31
+
32
+ /** Penalty applied to confidence on contradiction. */
33
+ const CONTRADICTION_DAMP = 0.2;
34
+
35
+ // @cap-decision Leading/trailing spaces in negation markers are deliberate — prevents 'not' from matching inside 'notation', 'nie' from matching 'niemals' boundaries wrongly, etc.
36
+ const NEGATION_MARKERS = [
37
+ "don't",
38
+ 'do not',
39
+ 'never',
40
+ 'avoid',
41
+ 'not ',
42
+ 'no longer',
43
+ 'stop ',
44
+ 'nicht',
45
+ 'nie ',
46
+ 'kein ',
47
+ 'keinen ',
48
+ 'keine ',
49
+ ];
50
+
51
+ // --- Types ---
52
+
53
+ /**
54
+ * @typedef {Object} ConfidenceFields
55
+ * @property {number} confidence - Float in [0.0, 1.0]
56
+ * @property {number} evidence_count - Integer >= 1
57
+ * @property {string} [last_seen] - ISO timestamp of the most recent observation (AC-3, F-056)
58
+ */
59
+
60
+ /** Epoch sentinel used when no last_seen and no source exist — behaves as "very old" for decay. */
61
+ const EPOCH_ZERO = '1970-01-01T00:00:00.000Z';
62
+
63
+ /**
64
+ * Normalize a Date-or-ISO-string to an ISO timestamp.
65
+ * @param {Date|undefined} now
66
+ * @returns {string}
67
+ */
68
+ function nowIso(now) {
69
+ const d = now instanceof Date ? now : new Date();
70
+ return d.toISOString();
71
+ }
72
+
73
+ // --- Tokenization + Similarity (AC-4) ---
74
+
75
+ /**
76
+ * Tokenize a string into a lowercase, deduped word-token array.
77
+ * Splits on any run of non-letter/non-number (Unicode-aware) and drops empties.
78
+ * @param {string} s
79
+ * @returns {string[]}
80
+ */
81
+ function tokenize(s) {
82
+ if (!s || typeof s !== 'string') return [];
83
+ const parts = s.toLowerCase().split(/[^\p{L}\p{N}]+/u);
84
+ const out = [];
85
+ const seen = new Set();
86
+ for (const p of parts) {
87
+ if (!p) continue;
88
+ if (seen.has(p)) continue;
89
+ seen.add(p);
90
+ out.push(p);
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /**
96
+ * Jaccard similarity on word-token sets: |A ∩ B| / |A ∪ B|.
97
+ * Returns 0 for empty inputs so the threshold check is always meaningful.
98
+ * @param {string} a
99
+ * @param {string} b
100
+ * @returns {number}
101
+ */
102
+ function jaccardSimilarity(a, b) {
103
+ const ta = new Set(tokenize(a));
104
+ const tb = new Set(tokenize(b));
105
+ if (ta.size === 0 && tb.size === 0) return 0;
106
+ let intersection = 0;
107
+ for (const t of ta) if (tb.has(t)) intersection++;
108
+ const union = ta.size + tb.size - intersection;
109
+ return union === 0 ? 0 : intersection / union;
110
+ }
111
+
112
+ /**
113
+ * Check whether a new observation is the "same" as an existing one
114
+ * (similarity at or above SIMILARITY_THRESHOLD).
115
+ * @param {string} newContent
116
+ * @param {string} existingContent
117
+ * @returns {boolean}
118
+ */
119
+ function isReObservation(newContent, existingContent) {
120
+ return jaccardSimilarity(newContent, existingContent) >= SIMILARITY_THRESHOLD;
121
+ }
122
+
123
+ // --- Contradiction Detection (AC-5) ---
124
+
125
+ /**
126
+ * Lowercase normalize a content string and check whether any negation marker appears.
127
+ * @param {string} content
128
+ * @returns {boolean}
129
+ */
130
+ function hasNegationMarker(content) {
131
+ if (!content) return false;
132
+ const lc = content.toLowerCase();
133
+ return NEGATION_MARKERS.some((m) => lc.includes(m));
134
+ }
135
+
136
+ /**
137
+ * Intersect two arrays of file paths. Tolerant of null/undefined.
138
+ * @param {string[]|undefined|null} a
139
+ * @param {string[]|undefined|null} b
140
+ * @returns {boolean} true if at least one file appears in both arrays
141
+ */
142
+ function filesOverlap(a, b) {
143
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length === 0 || b.length === 0) return false;
144
+ const sa = new Set(a);
145
+ for (const f of b) if (sa.has(f)) return true;
146
+ return false;
147
+ }
148
+
149
+ // @cap-risk Contradiction detection is a best-effort heuristic. Two failure modes:
150
+ // 1. False-positives when one entry uses negation stylistically ("don't just ..., do ...") while the counter-entry reads as a positive claim.
151
+ // 2. False-negatives when a contradiction is phrased without any NEGATION_MARKER token — e.g. "prefer X over Y" / "prefer Y over X" both lack explicit negation and slip through the asymmetry gate.
152
+ // We intentionally under-detect rather than over-detect. Unit tests cover synthetic pairs; real-world misclassification is bounded by the file-scope + category + negation-asymmetry gate.
153
+ /**
154
+ * Heuristically detect whether two entries contradict each other.
155
+ * Requires all of:
156
+ * 1. Same category.
157
+ * 2. Overlapping relatedFiles (shared file-scope).
158
+ * 3. Negation asymmetry — exactly one of the two contents contains a negation marker.
159
+ * 4. The non-negation content-tokens overlap by >= 50 % (they're talking about the same thing).
160
+ * @param {{category:string, content:string, metadata?:Object}} newEntry
161
+ * @param {{category:string, content:string, metadata?:Object}} existingEntry
162
+ * @returns {boolean}
163
+ */
164
+ function isContradiction(newEntry, existingEntry) {
165
+ if (!newEntry || !existingEntry) return false;
166
+ if (newEntry.category !== existingEntry.category) return false;
167
+
168
+ const newFiles = newEntry.metadata?.relatedFiles || (newEntry.file ? [newEntry.file] : []);
169
+ const existingFiles = existingEntry.metadata?.relatedFiles || (existingEntry.file ? [existingEntry.file] : []);
170
+ if (!filesOverlap(newFiles, existingFiles)) return false;
171
+
172
+ const newNeg = hasNegationMarker(newEntry.content);
173
+ const existingNeg = hasNegationMarker(existingEntry.content);
174
+ // Exactly one side must carry the negation — otherwise they either agree (both positive / both negative) or are unrelated.
175
+ if (newNeg === existingNeg) return false;
176
+
177
+ // Token-overlap sanity: strip negation markers then require 50 % token overlap.
178
+ const stripNeg = (s) => {
179
+ let out = s.toLowerCase();
180
+ for (const m of NEGATION_MARKERS) {
181
+ out = out.split(m).join(' ');
182
+ }
183
+ return out;
184
+ };
185
+ const ta = new Set(tokenize(stripNeg(newEntry.content)));
186
+ const tb = new Set(tokenize(stripNeg(existingEntry.content)));
187
+ if (ta.size === 0 || tb.size === 0) return false;
188
+ let inter = 0;
189
+ for (const t of ta) if (tb.has(t)) inter++;
190
+ const smaller = Math.min(ta.size, tb.size);
191
+ return inter / smaller >= 0.5;
192
+ }
193
+
194
+ // --- Field Operations ---
195
+
196
+ /**
197
+ * @cap-todo(ac:F-056/AC-3) last_seen seeded on first observation so decay has a reference point.
198
+ * @cap-feature(feature:F-091) Source-aware initial confidence — explicit user-annotated entries
199
+ * start higher than heuristic-extracted ones so the F-090 confidence-filter (threshold 0.6)
200
+ * admits legitimate decisions immediately instead of waiting for re-observation.
201
+ * @param {Date|number|{ now?: Date|number, initialConfidence?: number }} [optsOrNow]
202
+ * @returns {ConfidenceFields}
203
+ */
204
+ function initFields(optsOrNow) {
205
+ // Backwards-compat: legacy signature accepts a Date|number directly. Detect by absence
206
+ // of initialConfidence key.
207
+ let now;
208
+ let initialConfidence = DEFAULT_CONFIDENCE;
209
+ if (
210
+ optsOrNow !== undefined &&
211
+ optsOrNow !== null &&
212
+ typeof optsOrNow === 'object' &&
213
+ !(optsOrNow instanceof Date) &&
214
+ 'initialConfidence' in optsOrNow
215
+ ) {
216
+ now = optsOrNow.now;
217
+ if (typeof optsOrNow.initialConfidence === 'number') {
218
+ // Clamp to [FLOOR, CAP] so callers can't accidentally write 1.5 into the field.
219
+ initialConfidence = Math.max(
220
+ CONFIDENCE_FLOOR,
221
+ Math.min(CONFIDENCE_CAP, optsOrNow.initialConfidence)
222
+ );
223
+ }
224
+ } else {
225
+ now = optsOrNow;
226
+ }
227
+ return {
228
+ confidence: round2(initialConfidence),
229
+ evidence_count: DEFAULT_EVIDENCE,
230
+ last_seen: nowIso(now),
231
+ };
232
+ }
233
+
234
+ // @cap-feature(feature:F-091) Per-source initial confidence schedule. Higher values for entries
235
+ // the user explicitly annotated (so they bypass the F-090 0.6 filter immediately); default
236
+ // stays 0.5 for heuristic-extracted entries that need re-observation to prove themselves.
237
+ // @cap-decision(F-091) Schedule lives in confidence.cjs (single source of truth) so future
238
+ // adjustments touch one constant table rather than scattered branches in the engine.
239
+ const SOURCE_INITIAL_CONFIDENCE = {
240
+ 'cap-decision': 0.8, // Explicit @cap-decision(...) tag — user marked it as a decision
241
+ 'cap-todo-decision': 0.7, // Explicit @cap-todo decision: subtype
242
+ 'cap-todo-risk': 0.7, // Explicit @cap-todo risk: subtype (F-091 — was 0.5)
243
+ 'cap-risk': 0.7, // Standalone @cap-risk tag
244
+ 'session-extract': 0.5, // Heuristic regex-match in conversation transcript (status quo)
245
+ 'heuristic': 0.5, // Generic heuristic — comment-block extraction etc.
246
+ };
247
+
248
+ /**
249
+ * Resolve the initial confidence value for a given source.
250
+ * @param {string} source - one of the keys in SOURCE_INITIAL_CONFIDENCE
251
+ * @returns {number}
252
+ */
253
+ function initialConfidenceForSource(source) {
254
+ if (typeof source === 'string' && Object.prototype.hasOwnProperty.call(SOURCE_INITIAL_CONFIDENCE, source)) {
255
+ return SOURCE_INITIAL_CONFIDENCE[source];
256
+ }
257
+ return DEFAULT_CONFIDENCE;
258
+ }
259
+
260
+ /**
261
+ * Round to 2 decimal places to avoid 0.30000000000000004 floating-point noise in markdown.
262
+ * @param {number} n
263
+ * @returns {number}
264
+ */
265
+ function round2(n) {
266
+ return Math.round(n * 100) / 100;
267
+ }
268
+
269
+ /**
270
+ * Apply a re-observation: +1 evidence, +0.1 confidence, capped at CONFIDENCE_CAP.
271
+ * @cap-todo(ac:F-056/AC-3) last_seen refreshed so the decay clock resets on reaffirmation.
272
+ * @param {ConfidenceFields} fields
273
+ * @param {Date} [now]
274
+ * @returns {ConfidenceFields}
275
+ */
276
+ function bumpOnReObservation(fields, now) {
277
+ const f = ensureFields(fields);
278
+ return {
279
+ ...f,
280
+ confidence: round2(Math.min(CONFIDENCE_CAP, f.confidence + REOBSERVATION_BUMP)),
281
+ evidence_count: f.evidence_count + 1,
282
+ last_seen: nowIso(now),
283
+ };
284
+ }
285
+
286
+ // @cap-decision Contradiction does NOT refresh last_seen — a rebuttal is not a reaffirmation, and resetting the decay clock on disagreement would reward stale-but-contested entries.
287
+ /**
288
+ * Apply a contradiction: -0.2 confidence, floored at CONFIDENCE_FLOOR.
289
+ * Evidence count is NOT incremented — a contradiction is not a confirmation.
290
+ * @param {ConfidenceFields} fields
291
+ * @param {Date} [_now] - accepted for signature parity; not used (see decision above).
292
+ * @returns {ConfidenceFields}
293
+ */
294
+ function dampOnContradiction(fields, _now) {
295
+ const f = ensureFields(fields);
296
+ return {
297
+ ...f,
298
+ confidence: round2(Math.max(CONFIDENCE_FLOOR, f.confidence - CONTRADICTION_DAMP)),
299
+ evidence_count: f.evidence_count,
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Return a shallow clone of `metadata` with `confidence`, `evidence_count`, and `last_seen`
305
+ * defaulted if missing. Does not mutate the input.
306
+ * Used for AC-3 lazy migration — reading an old file without the fields
307
+ * yields entries that look fully-formed downstream.
308
+ *
309
+ * Also clamps `confidence` to [0, 1] and applies round2 so hand-edited or
310
+ * legacy out-of-range values (e.g. `confidence: 1.5` or `-0.3`) render cleanly.
311
+ * We clamp to [0, 1] rather than [FLOOR, CAP] because 1.00 is a valid
312
+ * hand-edited value; CAP is only the bump-on-reobservation ceiling.
313
+ *
314
+ * @cap-todo(ac:F-056/AC-3) last_seen lazy-migration: fall back to metadata.source, else epoch-0.
315
+ * @param {Object} metadata
316
+ * @param {Date} [_now] - accepted for signature parity; last_seen default does not depend on "now".
317
+ * @returns {Object}
318
+ */
319
+ function ensureFields(metadata, _now) {
320
+ const src = metadata || {};
321
+ const out = { ...src };
322
+ if (typeof out.confidence !== 'number' || Number.isNaN(out.confidence)) {
323
+ out.confidence = DEFAULT_CONFIDENCE;
324
+ } else {
325
+ out.confidence = round2(Math.min(1, Math.max(0, out.confidence)));
326
+ }
327
+ if (typeof out.evidence_count !== 'number' || !Number.isFinite(out.evidence_count) || out.evidence_count < 1) {
328
+ out.evidence_count = DEFAULT_EVIDENCE;
329
+ }
330
+ if (typeof out.last_seen !== 'string' || out.last_seen.length === 0) {
331
+ out.last_seen = typeof out.source === 'string' && out.source.length > 0 ? out.source : EPOCH_ZERO;
332
+ }
333
+ return out;
334
+ }
335
+
336
+ /**
337
+ * @param {Object} metadata
338
+ * @returns {boolean}
339
+ */
340
+ function isLowConfidence(metadata) {
341
+ const f = ensureFields(metadata);
342
+ return f.confidence < DIM_THRESHOLD;
343
+ }
344
+
345
+ // --- Orchestration ---
346
+
347
+ /**
348
+ * Compare a new entry against a list of existing entries and decide the learning signal.
349
+ *
350
+ * Priority when both could apply (rare in practice): re-observation wins. Rationale —
351
+ * if content is already >= 80 % similar, treating it as contradictory would be
352
+ * semantically wrong (it's the same observation, not a rebuttal).
353
+ *
354
+ * @param {{category:string, content:string, file?:string, metadata:Object}} newEntry
355
+ * @param {Array<{category:string, content:string, file?:string, metadata:Object}>} existingEntries
356
+ * @returns {{
357
+ * mergedEntry: Object,
358
+ * touchedExistingIndex: number|null,
359
+ * action: 'new'|'reobserved'|'contradicted'
360
+ * }}
361
+ */
362
+ function applyLearningSignals(newEntry, existingEntries) {
363
+ const list = Array.isArray(existingEntries) ? existingEntries : [];
364
+
365
+ // Pass 1: re-observation (same category + >= threshold similarity).
366
+ for (let i = 0; i < list.length; i++) {
367
+ const e = list[i];
368
+ if (!e || e.category !== newEntry.category) continue;
369
+ if (isReObservation(newEntry.content, e.content)) {
370
+ const bumped = bumpOnReObservation(e.metadata);
371
+ const mergedEntry = {
372
+ ...e,
373
+ metadata: { ...e.metadata, ...bumped },
374
+ };
375
+ return { mergedEntry, touchedExistingIndex: i, action: 'reobserved' };
376
+ }
377
+ }
378
+
379
+ // Pass 2: contradiction.
380
+ for (let i = 0; i < list.length; i++) {
381
+ const e = list[i];
382
+ if (!e) continue;
383
+ if (isContradiction(newEntry, e)) {
384
+ const damped = dampOnContradiction(e.metadata);
385
+ // The new entry is tracked separately (not merged) — only the existing entry's confidence drops.
386
+ // Callers receive the new entry with fresh init-fields AND the touched index so they can rewrite the existing entry.
387
+ // @cap-feature(feature:F-091) Spread order: initFields() first, then newEntry.metadata.
388
+ // Pre-F-091 the order was reversed — initFields() overrode the entry's existing
389
+ // confidence, which silently lost any source-aware confidence the engine had set.
390
+ // Post-F-091 source-aware values (e.g. 0.8 for @cap-decision) win; initFields() only
391
+ // fills in keys that are absent (typically last_seen).
392
+ const mergedEntry = {
393
+ ...newEntry,
394
+ metadata: { ...initFields(), ...(newEntry.metadata || {}) },
395
+ _contradictedExistingUpdate: {
396
+ index: i,
397
+ updatedMetadata: { ...e.metadata, ...damped },
398
+ },
399
+ };
400
+ return { mergedEntry, touchedExistingIndex: i, action: 'contradicted' };
401
+ }
402
+ }
403
+
404
+ // Fallback: brand-new observation.
405
+ return {
406
+ mergedEntry: {
407
+ ...newEntry,
408
+ // @cap-feature(feature:F-091) Same spread-order fix as the contradiction branch above.
409
+ metadata: { ...initFields(), ...(newEntry.metadata || {}) },
410
+ },
411
+ touchedExistingIndex: null,
412
+ action: 'new',
413
+ };
414
+ }
415
+
416
+ module.exports = {
417
+ // Constants
418
+ DEFAULT_CONFIDENCE,
419
+ DEFAULT_EVIDENCE,
420
+ CONFIDENCE_CAP,
421
+ CONFIDENCE_FLOOR,
422
+ SIMILARITY_THRESHOLD,
423
+ DIM_THRESHOLD,
424
+ REOBSERVATION_BUMP,
425
+ CONTRADICTION_DAMP,
426
+ NEGATION_MARKERS,
427
+ EPOCH_ZERO,
428
+
429
+ // Tokenization + similarity
430
+ tokenize,
431
+ jaccardSimilarity,
432
+ isReObservation,
433
+
434
+ // Contradiction
435
+ hasNegationMarker,
436
+ filesOverlap,
437
+ isContradiction,
438
+
439
+ // Fields
440
+ initFields,
441
+ bumpOnReObservation,
442
+ dampOnContradiction,
443
+ ensureFields,
444
+ isLowConfidence,
445
+
446
+ // F-091: source-aware initial confidence
447
+ SOURCE_INITIAL_CONFIDENCE,
448
+ initialConfidenceForSource,
449
+
450
+ // Orchestration
451
+ applyLearningSignals,
452
+ };