pi-vcc 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/demo.gif +0 -0
  4. package/flow/plans/20260515-1300/plan.md +206 -0
  5. package/index.ts +14 -0
  6. package/package.json +36 -0
  7. package/pi-vcc-config.schema.json +131 -0
  8. package/scripts/audit-sessions.ts +88 -0
  9. package/scripts/benchmark-real-sessions.ts +25 -0
  10. package/scripts/compare-before-after.ts +36 -0
  11. package/scripts/dump-branch-output.ts +20 -0
  12. package/src/commands/pi-vcc.ts +33 -0
  13. package/src/commands/vcc-recall.ts +65 -0
  14. package/src/core/brief.ts +381 -0
  15. package/src/core/build-sections.ts +87 -0
  16. package/src/core/content.ts +60 -0
  17. package/src/core/filter-noise.ts +42 -0
  18. package/src/core/format-recall.ts +27 -0
  19. package/src/core/format.ts +56 -0
  20. package/src/core/lineage.ts +26 -0
  21. package/src/core/load-messages.ts +63 -0
  22. package/src/core/normalize.ts +66 -0
  23. package/src/core/recall-scope.ts +14 -0
  24. package/src/core/render-entries.ts +68 -0
  25. package/src/core/report.ts +237 -0
  26. package/src/core/sanitize.ts +5 -0
  27. package/src/core/search-entries.ts +230 -0
  28. package/src/core/settings.ts +215 -0
  29. package/src/core/skill-collapse.ts +35 -0
  30. package/src/core/summarize.ts +159 -0
  31. package/src/core/tool-args.ts +14 -0
  32. package/src/details.ts +7 -0
  33. package/src/extract/commits.ts +69 -0
  34. package/src/extract/files.ts +80 -0
  35. package/src/extract/goals.ts +79 -0
  36. package/src/extract/preferences.ts +55 -0
  37. package/src/extract/references.ts +214 -0
  38. package/src/extract/signals.ts +145 -0
  39. package/src/hooks/before-compact.ts +405 -0
  40. package/src/sections.ts +14 -0
  41. package/src/tools/recall.ts +109 -0
  42. package/src/types.ts +14 -0
  43. package/tests/before-compact-hook.test.ts +181 -0
  44. package/tests/before-compact.test.ts +140 -0
  45. package/tests/brief.test.ts +206 -0
  46. package/tests/build-sections.test.ts +90 -0
  47. package/tests/compile.test.ts +110 -0
  48. package/tests/config-integration.test.ts +107 -0
  49. package/tests/content.test.ts +31 -0
  50. package/tests/edge-cases.test.ts +368 -0
  51. package/tests/extract-goals.test.ts +86 -0
  52. package/tests/extract-preferences.test.ts +30 -0
  53. package/tests/extract-references.test.ts +475 -0
  54. package/tests/extract-signals.test.ts +561 -0
  55. package/tests/filter-noise.test.ts +61 -0
  56. package/tests/fixtures.ts +61 -0
  57. package/tests/format-recall.test.ts +30 -0
  58. package/tests/format.test.ts +91 -0
  59. package/tests/lineage.test.ts +33 -0
  60. package/tests/load-messages.test.ts +51 -0
  61. package/tests/normalize.test.ts +97 -0
  62. package/tests/real-sessions.test.ts +38 -0
  63. package/tests/recall-expand.test.ts +15 -0
  64. package/tests/recall-scope.test.ts +32 -0
  65. package/tests/recall-tool-scope.test.ts +67 -0
  66. package/tests/render-entries.test.ts +62 -0
  67. package/tests/report.test.ts +44 -0
  68. package/tests/sanitize.test.ts +24 -0
  69. package/tests/search-entries.test.ts +144 -0
  70. package/tests/settings-scaffold.test.ts +120 -0
  71. package/tests/settings.test.ts +32 -0
  72. package/tests/support/load-session.ts +23 -0
  73. package/tests/support/real-sessions.ts +51 -0
  74. package/tsconfig.json +14 -0
  75. package/vitest.config.ts +7 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 buihongduc132
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # pi-vcc
2
+
3
+ [![npm](https://img.shields.io/npm/v/@sting8k/pi-vcc)](https://www.npmjs.com/package/@sting8k/pi-vcc)
4
+ [![license](https://img.shields.io/npm/l/@sting8k/pi-vcc)](https://spdx.org/licenses/MIT)
5
+
6
+ Algorithmic conversation compactor for [Pi](https://github.com/badlogic/pi-mono). No LLM calls — produces a brief transcript via extraction and formatting.
7
+
8
+ Inspired by [VCC](https://github.com/lllyasviel/VCC) **(View-oriented Conversation Compiler)**.
9
+
10
+ ## Features
11
+
12
+ - **Zero LLM calls** — deterministic, same input always produces same output
13
+ - **35–99% token reduction** on real sessions (higher on longer sessions)
14
+ - **30–470ms latency** — no API calls, pure algorithmic extraction
15
+ - **4 semantic sections** — Session Goal, Outstanding Context, References, Key Signals
16
+ - **Brief transcript** with tool-call rollup and noise filtering
17
+ - **History search** via `vcc_recall` tool (lineage-scoped, expand, pagination)
18
+ - **Section merging** across repeated compactions with deduplication
19
+ - **Configurable extraction** — additive patterns for URLs, GitHub refs, versions, constraints, decisions, statuses
20
+
21
+ ## Installation
22
+
23
+ ### For humans (npm)
24
+
25
+ ```bash
26
+ npm install @sting8k/pi-vcc
27
+ ```
28
+
29
+ ### For AI agents (pi settings.json)
30
+
31
+ Add to your pi `settings.json` packages array:
32
+
33
+ ```json
34
+ {
35
+ "packages": ["@sting8k/pi-vcc"]
36
+ }
37
+ ```
38
+
39
+ ### Git-sourced
40
+
41
+ In `settings.json`, reference the repo directly:
42
+
43
+ ```json
44
+ {
45
+ "packages": ["github:buihongduc132/pi-vcc"]
46
+ }
47
+ ```
48
+
49
+ Or clone into `profile/git/github.com/buihongduc132/`:
50
+
51
+ ```bash
52
+ git clone https://github.com/buihongduc132/pi-vcc.git profile/git/github.com/buihongduc132/pi-vcc
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ Once installed, pi-vcc registers two hooks and one tool automatically:
58
+
59
+ ### Commands
60
+
61
+ | Command | Description |
62
+ |---------|-------------|
63
+ | `/pi-vcc` | Trigger pi-vcc compaction explicitly |
64
+ | `/compact` | Handled by pi-vcc when `overrideDefaultCompaction: true` |
65
+
66
+ ### Tool: `vcc_recall`
67
+
68
+ Search session history after compaction:
69
+
70
+ ```
71
+ vcc_recall({ query: "auth bug", scope: "all", page: 1 })
72
+ vcc_recall({ expand: [0, 3], scope: "all" })
73
+ ```
74
+
75
+ - **Default scope**: active lineage only; use `scope: "all"` for off-lineage branches
76
+ - Supports regex queries, pagination, and expand indices
77
+
78
+ ## Configuration
79
+
80
+ Config file: `~/.pi/agent/pi-vcc-config.json` (auto-scaffolded with defaults).
81
+
82
+ ```json
83
+ {
84
+ "overrideDefaultCompaction": false,
85
+ "debug": false,
86
+ "extraction": {
87
+ "references": {
88
+ "enabled": true,
89
+ "extraUrlPatterns": [],
90
+ "extraGithubRefPatterns": [],
91
+ "extraVersionPatterns": [],
92
+ "extraBranchPatterns": []
93
+ },
94
+ "keySignals": {
95
+ "enabled": true,
96
+ "extraConstraintPatterns": [],
97
+ "extraDecisionPatterns": [],
98
+ "extraStatusPatterns": []
99
+ },
100
+ "goals": {
101
+ "enabled": true,
102
+ "extraTaskVerbs": [],
103
+ "extraScopeChangeWords": []
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ | Field | Default | Description |
110
+ |-------|---------|-------------|
111
+ | `overrideDefaultCompaction` | `false` | When `true`, pi-vcc handles all compaction triggers |
112
+ | `debug` | `false` | Write debug snapshot to `/tmp/pi-vcc-debug.json` |
113
+ | `extraction.*.enabled` | `true` | Enable/disable individual extraction categories |
114
+ | `extraction.*.extra*` | `[]` | Additive patterns (built-ins never removed) |
115
+
116
+ ## License
117
+
118
+ MIT © [buihongduc132](https://github.com/buihongduc132)
119
+
120
+ Repository: [buihongduc132/pi-vcc](https://github.com/buihongduc132/pi-vcc)
package/demo.gif ADDED
Binary file
@@ -0,0 +1,206 @@
1
+ # Plan: Enhance pi-vcc Extractors + Configurable Rules
2
+
3
+ ## User Context (verbatim)
4
+
5
+ > "Do it be able to capture: url like ? ALSO what are others important mark / anchor that it should capture but currently it is not? Also what are NLP keywords that it can capture for better information as well?"
6
+ > "make it to be able to configured as a config file as well (JUST adding, not removing the rules)"
7
+ > "TDD approach; must have verifier loop; LOTS of test cases"
8
+
9
+ ## Problem
10
+
11
+ pi-vcc compaction loses critical information:
12
+ 1. **URLs** — explicitly filtered OUT by `NON_GOAL_RE` in `goals.ts`
13
+ 2. **GitHub refs** — `#42`, `PR #7`, `@user`, `owner/repo` not captured
14
+ 3. **Version/context anchors** — semver, branch names, commit refs, error codes
15
+ 4. **NLP signals** — constraints, decisions, status changes not extracted
16
+ 5. **No configurability** — all regexes are hardcoded, users can't tune them
17
+
18
+ ## Declarative Target State
19
+
20
+ ### 1. New Section: `[References]` in output
21
+
22
+ ```
23
+ [References]
24
+ - URL: https://docs.example.com/api/v2
25
+ - URL: http://100.114.135.99:4747
26
+ - GitHub: #42, PR #7, buihongduc132/pi-plugins
27
+ - Version: v2.3.1, @1.0.0
28
+ - Branch: feat/new-auth
29
+ - CommitRef: abc1234
30
+ ```
31
+
32
+ ### 2. New Section: `[Key Signals]` in output
33
+
34
+ ```
35
+ [Key Signals]
36
+ - Constraint: must not push to main directly
37
+ - Decision: use Redis for caching instead of in-process LRU
38
+ - Status: DONE — auth module migrated
39
+ - Blocker: CI fails on Node 18
40
+ ```
41
+
42
+ ### 3. Configurable extraction rules via `pi-vcc-config.json`
43
+
44
+ ```jsonc
45
+ {
46
+ // Existing settings preserved
47
+ "overrideDefaultCompaction": false,
48
+ "debug": false,
49
+
50
+ // NEW: extraction config (additive only — defaults match current behavior)
51
+ "extraction": {
52
+ "references": {
53
+ "enabled": true,
54
+ "urlPatterns": ["https?://\\S+"], // additive to built-in
55
+ "githubRefPatterns": ["#\\d+", "PR\\s*#\\d+"], // additive
56
+ "versionPatterns": ["v?\\d+\\.\\d+\\.\\d+"],
57
+ "branchPatterns": ["\\b(?:feat|fix|main|develop)/[\\w-]+"]
58
+ },
59
+ "keySignals": {
60
+ "enabled": true,
61
+ "constraintPatterns": ["\\b(must not|don'?t|cannot|forbidden|never)\\b"],
62
+ "decisionPatterns": ["\\b(decided|let'?s use|going with|chose)\\b"],
63
+ "statusPatterns": ["\\b(DONE|TODO|WIP|blocked|resolved)\\b"]
64
+ },
65
+ "goals": {
66
+ // Can ADD patterns to existing extractors, never remove
67
+ "extraTaskVerbs": [],
68
+ "extraScopeChangeWords": []
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### 4. No breaking changes
75
+
76
+ - All existing extractors unchanged
77
+ - Existing tests must continue to pass
78
+ - New sections appear AFTER existing sections in output
79
+ - Config defaults = current behavior + new extractors enabled
80
+
81
+ ## Implementation Tasks (TDD)
82
+
83
+ ### Phase 1: New Extractor — References
84
+
85
+ | # | Task | Files | Test First |
86
+ |---|------|-------|------------|
87
+ | 1.1 | Create `src/extract/references.ts` — extract URLs, GitHub refs, versions, branches, commit refs | `extract/references.ts` | `tests/extract-references.test.ts` |
88
+ | 1.2 | Wire into `buildSections` — add `references` field to `SectionData` | `sections.ts`, `build-sections.ts` | `tests/build-sections.test.ts` |
89
+ | 1.3 | Wire into `formatSummary` — render `[References]` section | `format.ts` | `tests/format.test.ts` |
90
+ | 1.4 | Wire into `mergePrevious` — merge references across compactions | `summarize.ts` | `tests/compile.test.ts` |
91
+
92
+ ### Phase 2: New Extractor — Key Signals
93
+
94
+ | # | Task | Files | Test First |
95
+ |---|------|-------|------------|
96
+ | 2.1 | Create `src/extract/signals.ts` — extract constraints, decisions, status markers | `extract/signals.ts` | `tests/extract-signals.test.ts` |
97
+ | 2.2 | Wire into `buildSections` + `formatSummary` + `mergePrevious` | `sections.ts`, `build-sections.ts`, `format.ts`, `summarize.ts` | extend existing tests |
98
+
99
+ ### Phase 3: Configurable Rules
100
+
101
+ | # | Task | Files | Test First |
102
+ |---|------|-------|------------|
103
+ | 3.1 | Extend `PiVccSettings` with `extraction` config schema | `settings.ts` | `tests/settings.test.ts` |
104
+ | 3.2 | Pass config to extractors — patterns become configurable (defaults = current) | all extractors | extend all extractor tests |
105
+ | 3.3 | Fix `goals.ts` `NON_GOAL_RE` — URLs no longer filtered from goals (moved to dedicated extractor) | `extract/goals.ts` | `tests/extract-goals.test.ts` |
106
+
107
+ ### Phase 4: Comprehensive Test Coverage
108
+
109
+ | # | Task |
110
+ |---|------|
111
+ | 4.1 | Edge cases: multi-line URLs, malformed refs, duplicate dedup |
112
+ | 4.2 | Config override: custom patterns replace defaults |
113
+ | 4.3 | Merge across compactions: references persist correctly |
114
+ | 4.4 | Backwards compat: output format identical when new sections empty |
115
+
116
+ ## Test Cases (Detailed)
117
+
118
+ ### extract-references.test.ts
119
+
120
+ ```
121
+ URLs:
122
+ - http://example.com
123
+ - https://docs.site.com/api/v2#section
124
+ - http://100.114.135.99:4747
125
+ - file:///path/to/file
126
+ - URL inside markdown [text](url)
127
+ - Multiple URLs in one message
128
+ - URL at line boundary
129
+ - Dedup same URL across blocks
130
+ - Skip URLs that are only tool_result noise (large JSON dumps)
131
+
132
+ GitHub refs:
133
+ - #42, #1234
134
+ - PR #7, pr#12
135
+ - Issue #5
136
+ - @username mentions
137
+ - owner/repo paths (e.g., buihongduc132/pi-plugins)
138
+ - Full GitHub URLs (https://github.com/owner/repo/issues/42)
139
+
140
+ Versions:
141
+ - v1.2.3, v0.3.12
142
+ - @1.0.0, @^2.0.0
143
+ - semver ranges: >=1.0.0 <2.0.0
144
+
145
+ Branches:
146
+ - feat/new-auth, fix/login-bug
147
+ - main, develop, master (only if context suggests branch ref)
148
+
149
+ Commit refs:
150
+ - abc1234, abc1234567 (7-12 hex chars, standalone)
151
+ - NOT inside git commit output (already handled by commits extractor)
152
+ ```
153
+
154
+ ### extract-signals.test.ts
155
+
156
+ ```
157
+ Constraints:
158
+ - "must not push to main directly"
159
+ - "don't use any external deps"
160
+ - "cannot modify the public API"
161
+ - "forbidden to write to /etc"
162
+ - CONSTRAINT_RE from summary: /\b(don'?t|must not|cannot|forbidden|disallowed|off[- ]limits|out of scope|excluded|do not)\b/i
163
+
164
+ Decisions:
165
+ - "decided to use Redis"
166
+ - "let's go with approach B"
167
+ - "going with the microservice pattern"
168
+ - "chose SQLite for simplicity"
169
+
170
+ Status markers:
171
+ - "DONE — auth migrated"
172
+ - "WIP: still debugging"
173
+ - "TODO: add tests"
174
+ - "blocked on upstream fix"
175
+ - "resolved: was a typo"
176
+
177
+ Negatives (should NOT match):
178
+ - Questions ("should we use X?")
179
+ - Hypotheticals ("what if we can't?")
180
+ - Code comments in tool results
181
+ ```
182
+
183
+ ### Config tests
184
+
185
+ ```
186
+ - Default config = all extractors enabled with built-in patterns
187
+ - Custom patterns additive (append to defaults)
188
+ - Disable specific extractor via enabled: false
189
+ - Invalid config gracefully ignored (fallback to defaults)
190
+ - Config hot-reload (re-read on each compaction)
191
+ ```
192
+
193
+ ## Risk Assessment
194
+
195
+ | Risk | Level | Mitigation |
196
+ |------|-------|------------|
197
+ | Breaking existing output format | LOW | New sections only; existing untouched |
198
+ | Performance (regex on every block) | LOW | Patterns pre-compiled; only user/assistant blocks scanned |
199
+ | False positives (refs in code dumps) | MEDIUM | Filter tool_result blocks; only scan user + assistant |
200
+ | Config migration | LOW | scaffoldSettings fills missing keys |
201
+
202
+ ## PR Target
203
+
204
+ `buihongduc132/pi-vcc` (fork) → `sting8k/pi-vcc` (upstream)
205
+
206
+ Branch: `feat/enhance-extractors-configurable`
package/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { scaffoldSettings } from "./src/core/settings";
3
+ import { registerBeforeCompactHook } from "./src/hooks/before-compact";
4
+ import { registerPiVccCommand } from "./src/commands/pi-vcc";
5
+ import { registerVccRecallCommand } from "./src/commands/vcc-recall";
6
+ import { registerRecallTool } from "./src/tools/recall";
7
+
8
+ export default (pi: ExtensionAPI) => {
9
+ scaffoldSettings();
10
+ registerBeforeCompactHook(pi);
11
+ registerPiVccCommand(pi);
12
+ registerVccRecallCommand(pi);
13
+ registerRecallTool(pi);
14
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "pi-vcc",
3
+ "version": "0.4.0",
4
+ "description": "Algorithmic conversation compactor for pi - transcript-preserving structured summaries, no LLM calls",
5
+ "main": "index.ts",
6
+ "keywords": [
7
+ "pi-package",
8
+ "pi-extension",
9
+ "pi",
10
+ "pi-coding-agent",
11
+ "vcc",
12
+ "version-control",
13
+ "compaction"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/buihongduc132/pi-vcc.git"
18
+ },
19
+ "scripts": {
20
+ "test": "vitest run",
21
+ "typecheck": "tsc --noEmit",
22
+ "prepublishOnly": "npm run typecheck && npm test"
23
+ },
24
+ "peerDependencies": {
25
+ "@mariozechner/pi-coding-agent": "*",
26
+ "@sinclair/typebox": "*"
27
+ },
28
+ "pi": {
29
+ "extensions": [
30
+ "./index.ts"
31
+ ]
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^6.0.3"
35
+ }
36
+ }
@@ -0,0 +1,131 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/buihongduc132/pi-vcc/pi-vcc-config.schema.json",
4
+ "title": "pi-vcc Configuration",
5
+ "description": "Configuration for pi-vcc algorithmic conversation compactor",
6
+ "type": "object",
7
+ "properties": {
8
+ "overrideDefaultCompaction": {
9
+ "type": "boolean",
10
+ "default": false,
11
+ "description": "When true, pi-vcc handles ALL compactions (/compact, auto-threshold, /pi-vcc). When false, only /pi-vcc triggers pi-vcc; everything else falls back to pi core's LLM-based compaction."
12
+ },
13
+ "debug": {
14
+ "type": "boolean",
15
+ "default": false,
16
+ "description": "Write debug snapshot to /tmp/pi-vcc-debug.json on each compaction."
17
+ },
18
+ "extraction": {
19
+ "$ref": "#/$defs/extraction"
20
+ }
21
+ },
22
+ "$defs": {
23
+ "extraction": {
24
+ "type": "object",
25
+ "description": "Fine-grained extraction configuration. All extra* patterns are ADDITIVE — built-in patterns are never removed.",
26
+ "properties": {
27
+ "references": {
28
+ "$ref": "#/$defs/references"
29
+ },
30
+ "keySignals": {
31
+ "$ref": "#/$defs/keySignals"
32
+ },
33
+ "goals": {
34
+ "$ref": "#/$defs/goals"
35
+ }
36
+ },
37
+ "additionalProperties": false
38
+ },
39
+ "references": {
40
+ "type": "object",
41
+ "description": "References extractor: URLs, GitHub refs, versions, branches, commit refs. Output appears as [References] section.",
42
+ "properties": {
43
+ "enabled": {
44
+ "type": "boolean",
45
+ "default": true,
46
+ "description": "When false, skip this extraction entirely."
47
+ },
48
+ "extraUrlPatterns": {
49
+ "type": "array",
50
+ "items": { "type": "string", "format": "regex" },
51
+ "default": [],
52
+ "description": "Additional URL regex patterns (appended to built-in https?://\\S+). Each pattern is compiled with global flag. Capture group 1 or full match is used."
53
+ },
54
+ "extraGithubRefPatterns": {
55
+ "type": "array",
56
+ "items": { "type": "string", "format": "regex" },
57
+ "default": [],
58
+ "description": "Additional GitHub ref patterns (appended to built-in #\\d+, PR\\s*#\\d+, owner/repo). Full match is used."
59
+ },
60
+ "extraVersionPatterns": {
61
+ "type": "array",
62
+ "items": { "type": "string", "format": "regex" },
63
+ "default": [],
64
+ "description": "Additional version patterns (appended to built-in v?\\d+\\.\\d+\\.\\d+). Capture group 1 or full match is used."
65
+ },
66
+ "extraBranchPatterns": {
67
+ "type": "array",
68
+ "items": { "type": "string", "format": "regex" },
69
+ "default": [],
70
+ "description": "Additional branch patterns (appended to built-in feat|fix|hotfix|release|.../xxx). Full match is used."
71
+ }
72
+ },
73
+ "additionalProperties": false
74
+ },
75
+ "keySignals": {
76
+ "type": "object",
77
+ "description": "Key Signals extractor: constraints, decisions, status markers. Output appears as [Key Signals] section.",
78
+ "properties": {
79
+ "enabled": {
80
+ "type": "boolean",
81
+ "default": true,
82
+ "description": "When false, skip this extraction entirely."
83
+ },
84
+ "extraConstraintPatterns": {
85
+ "type": "array",
86
+ "items": { "type": "string", "format": "regex" },
87
+ "default": [],
88
+ "description": "Additional constraint patterns (appended to built-in don't|must not|cannot|forbidden|disallowed|off-limits|out of scope|excluded|do not). Applied to user messages only."
89
+ },
90
+ "extraDecisionPatterns": {
91
+ "type": "array",
92
+ "items": { "type": "string", "format": "regex" },
93
+ "default": [],
94
+ "description": "Additional decision patterns (appended to built-in decided|let's use|going with|chose|we'll use). Applied to user messages only."
95
+ },
96
+ "extraStatusPatterns": {
97
+ "type": "array",
98
+ "items": { "type": "string" },
99
+ "default": [],
100
+ "description": "Additional status keywords (appended to built-in DONE|TODO|WIP|blocked|resolved). Keywords are matched at sentence start or after punctuation. Applied to user + assistant messages."
101
+ }
102
+ },
103
+ "additionalProperties": false
104
+ },
105
+ "goals": {
106
+ "type": "object",
107
+ "description": "Goals extractor tweaks. Controls session goal and scope-change detection.",
108
+ "properties": {
109
+ "enabled": {
110
+ "type": "boolean",
111
+ "default": true,
112
+ "description": "When false, skip goals extraction entirely (only affects [Session Goal] section)."
113
+ },
114
+ "extraTaskVerbs": {
115
+ "type": "array",
116
+ "items": { "type": "string" },
117
+ "default": [],
118
+ "description": "Additional task verbs (appended to built-in fix|implement|add|create|build|refactor|...). Used to detect new task statements in user messages."
119
+ },
120
+ "extraScopeChangeWords": {
121
+ "type": "array",
122
+ "items": { "type": "string" },
123
+ "default": [],
124
+ "description": "Additional scope-change keywords (appended to built-in instead|actually|change of plan|...). Used to detect when user pivots to a new goal."
125
+ }
126
+ },
127
+ "additionalProperties": false
128
+ }
129
+ },
130
+ "additionalProperties": false
131
+ }
@@ -0,0 +1,88 @@
1
+ import { basename, dirname } from "node:path";
2
+ import { compile } from "../src/core/summarize";
3
+ import { normalize } from "../src/core/normalize";
4
+ import { filterNoise } from "../src/core/filter-noise";
5
+ import { renderMessage } from "../src/core/render-entries";
6
+ import { prepareSessionSamples } from "../tests/support/real-sessions";
7
+ import { loadSessionMessages } from "../tests/support/load-session";
8
+
9
+ const SEP = "=".repeat(80);
10
+ const samples = await prepareSessionSamples(10);
11
+
12
+ for (const sample of samples) {
13
+ const loaded = loadSessionMessages(sample.copy);
14
+ const { messages } = loaded;
15
+
16
+ const rawBlocks = normalize(messages);
17
+ const filteredBlocks = filterNoise(rawBlocks);
18
+ const afterText = compile({ messages });
19
+
20
+ const rendered = messages.map((m, i) => renderMessage(m, i));
21
+ const beforeChars = rendered.reduce((s, e) => s + e.summary.length, 0);
22
+
23
+ const project = dirname(sample.source).split("--").filter(Boolean).pop() ?? "unknown";
24
+
25
+ const goalSection = afterText.match(/\[Session Goal\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
26
+ const stateSection = afterText.match(/\[Current State\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
27
+ const doneSection = afterText.match(/\[What Was Done\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
28
+ const problemsSection = afterText.match(/\[Open Problems\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
29
+ const nextSection = afterText.match(/\[Next Best Steps\]\n([\s\S]*?)(?=\n\n\[|$)/)?.[1] ?? "(empty)";
30
+
31
+ const doneLines = doneSection.split("\n").filter(l => l.trim());
32
+ const problemLines = problemsSection.split("\n").filter(l => l.trim());
33
+
34
+ // Detect issues
35
+ const issues: string[] = [];
36
+
37
+ // 1. Goal quality
38
+ const goalLines = goalSection.split("\n").map(l => l.replace(/^- /, "").trim()).filter(Boolean);
39
+ if (goalLines[0] && goalLines[0].length < 5) issues.push(`GOAL_TOO_SHORT: "${goalLines[0]}"`);
40
+ if (goalLines.length === 0) issues.push("GOAL_EMPTY");
41
+
42
+ // 2. Sensitive data in What Was Done
43
+ if (/sshpass|password|secret|token=|api[_-]?key/i.test(doneSection)) {
44
+ issues.push("SENSITIVE_DATA_IN_DONE");
45
+ }
46
+
47
+ // 3. Raw code/minified JS in summary
48
+ if (/\{[a-zA-Z$_]+:[a-zA-Z$_]+,[a-zA-Z$_]+:/.test(afterText) || /var [a-zA-Z]+=/.test(afterText)) {
49
+ issues.push("RAW_CODE_LEAK");
50
+ }
51
+
52
+ // 4. Open problems count
53
+ if (problemLines.length > 10) issues.push(`PROBLEMS_OVERCOUNT: ${problemLines.length}`);
54
+
55
+ // 5. Next steps empty
56
+ if (nextSection === "(empty)") issues.push("NEXT_STEPS_EMPTY");
57
+
58
+ // 6. What Was Done too verbose
59
+ if (doneLines.length > 15) issues.push(`DONE_TOO_VERBOSE: ${doneLines.length} lines`);
60
+
61
+ // 7. Summary too large (>10K chars)
62
+ if (afterText.length > 10000) issues.push(`SUMMARY_TOO_LARGE: ${afterText.length} chars`);
63
+
64
+ console.log(SEP);
65
+ console.log(`PROJECT: ${project}`);
66
+ console.log(`FILE: ${basename(sample.source)}`);
67
+ console.log(`Size: ${(sample.size / 1024).toFixed(0)}KB | Msgs: ${messages.length} | Blocks raw: ${rawBlocks.length} -> filtered: ${filteredBlocks.length}`);
68
+ console.log(`Before: ${beforeChars} chars | After: ${afterText.length} chars | Ratio: ${(beforeChars / afterText.length).toFixed(1)}x`);
69
+ console.log(`Issues: ${issues.length === 0 ? "NONE" : issues.join(", ")}`);
70
+ console.log("");
71
+ console.log("--- GOAL ---");
72
+ console.log(goalSection.slice(0, 300));
73
+ console.log("");
74
+ console.log("--- CURRENT STATE (first 300c) ---");
75
+ console.log(stateSection.slice(0, 300));
76
+ console.log("");
77
+ console.log("--- WHAT WAS DONE (first 5 lines) ---");
78
+ console.log(doneLines.slice(0, 5).join("\n"));
79
+ console.log(`... (${doneLines.length} total lines)`);
80
+ console.log("");
81
+ console.log("--- OPEN PROBLEMS (first 5 lines) ---");
82
+ console.log(problemLines.slice(0, 5).join("\n"));
83
+ console.log(`... (${problemLines.length} total lines)`);
84
+ console.log("");
85
+ console.log("--- NEXT STEPS ---");
86
+ console.log(nextSection.slice(0, 300));
87
+ console.log("");
88
+ }
@@ -0,0 +1,25 @@
1
+ import { performance } from "node:perf_hooks";
2
+ import { basename } from "node:path";
3
+ import { buildCompactReport } from "../src/core/report";
4
+ import { prepareSessionSamples } from "../tests/support/real-sessions";
5
+ import { loadSessionMessages } from "../tests/support/load-session";
6
+
7
+ const samples = await prepareSessionSamples(2);
8
+ for (const sample of samples) {
9
+ const loaded = loadSessionMessages(sample.copy);
10
+ const start = performance.now();
11
+ const report = buildCompactReport({ messages: loaded.messages });
12
+ const elapsedMs = performance.now() - start;
13
+ console.log(JSON.stringify({
14
+ sourceFile: basename(sample.source),
15
+ sourceSizeBytes: sample.size,
16
+ copiedToTemp: true,
17
+ loadedMessages: loaded.messageCount,
18
+ skippedMessages: loaded.skippedCount,
19
+ compileMs: Number(elapsedMs.toFixed(2)),
20
+ before: report.before,
21
+ after: report.after,
22
+ compression: report.compression,
23
+ recall: report.recall,
24
+ }, null, 2));
25
+ }