projscan 1.3.0 → 1.5.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 (76) hide show
  1. package/README.md +98 -9
  2. package/dist/cli/commands/doctor.js +18 -2
  3. package/dist/cli/commands/doctor.js.map +1 -1
  4. package/dist/cli/commands/explain.js +1 -0
  5. package/dist/cli/commands/explain.js.map +1 -1
  6. package/dist/cli/commands/memory.d.ts +11 -0
  7. package/dist/cli/commands/memory.js +175 -0
  8. package/dist/cli/commands/memory.js.map +1 -0
  9. package/dist/cli/commands/session.d.ts +12 -0
  10. package/dist/cli/commands/session.js +200 -0
  11. package/dist/cli/commands/session.js.map +1 -0
  12. package/dist/cli/index.js +4 -0
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/core/codeGraph.js +190 -241
  15. package/dist/core/codeGraph.js.map +1 -1
  16. package/dist/core/fileInspector.js +40 -44
  17. package/dist/core/fileInspector.js.map +1 -1
  18. package/dist/core/hotspotAnalyzer.js +65 -19
  19. package/dist/core/hotspotAnalyzer.js.map +1 -1
  20. package/dist/core/issueEngine.js +24 -0
  21. package/dist/core/issueEngine.js.map +1 -1
  22. package/dist/core/languages/csharpImports.js +6 -4
  23. package/dist/core/languages/csharpImports.js.map +1 -1
  24. package/dist/core/memory.d.ts +154 -0
  25. package/dist/core/memory.js +277 -0
  26. package/dist/core/memory.js.map +1 -0
  27. package/dist/core/review.d.ts +25 -1
  28. package/dist/core/review.js +84 -0
  29. package/dist/core/review.js.map +1 -1
  30. package/dist/core/session.d.ts +94 -0
  31. package/dist/core/session.js +187 -0
  32. package/dist/core/session.js.map +1 -0
  33. package/dist/mcp/prompts.js +272 -0
  34. package/dist/mcp/prompts.js.map +1 -1
  35. package/dist/mcp/server.js +198 -128
  36. package/dist/mcp/server.js.map +1 -1
  37. package/dist/mcp/sessionTouchScanner.d.ts +16 -0
  38. package/dist/mcp/sessionTouchScanner.js +112 -0
  39. package/dist/mcp/sessionTouchScanner.js.map +1 -0
  40. package/dist/mcp/tokenBudget.d.ts +22 -0
  41. package/dist/mcp/tokenBudget.js +26 -0
  42. package/dist/mcp/tokenBudget.js.map +1 -1
  43. package/dist/mcp/tools/doctor.js +65 -2
  44. package/dist/mcp/tools/doctor.js.map +1 -1
  45. package/dist/mcp/tools/explain.js +4 -3
  46. package/dist/mcp/tools/explain.js.map +1 -1
  47. package/dist/mcp/tools/explainIssue.js +3 -2
  48. package/dist/mcp/tools/explainIssue.js.map +1 -1
  49. package/dist/mcp/tools/file.js +3 -2
  50. package/dist/mcp/tools/file.js.map +1 -1
  51. package/dist/mcp/tools/graph.js +16 -11
  52. package/dist/mcp/tools/graph.js.map +1 -1
  53. package/dist/mcp/tools/impact.js +2 -2
  54. package/dist/mcp/tools/impact.js.map +1 -1
  55. package/dist/mcp/tools/memory.d.ts +19 -0
  56. package/dist/mcp/tools/memory.js +134 -0
  57. package/dist/mcp/tools/memory.js.map +1 -0
  58. package/dist/mcp/tools/review.js +25 -4
  59. package/dist/mcp/tools/review.js.map +1 -1
  60. package/dist/mcp/tools/session.d.ts +22 -0
  61. package/dist/mcp/tools/session.js +140 -0
  62. package/dist/mcp/tools/session.js.map +1 -0
  63. package/dist/mcp/tools/upgrade.js +3 -2
  64. package/dist/mcp/tools/upgrade.js.map +1 -1
  65. package/dist/mcp/tools.js +4 -0
  66. package/dist/mcp/tools.js.map +1 -1
  67. package/dist/reporters/consoleReporter.d.ts +12 -1
  68. package/dist/reporters/consoleReporter.js +289 -179
  69. package/dist/reporters/consoleReporter.js.map +1 -1
  70. package/dist/reporters/markdownReporter.js +185 -128
  71. package/dist/reporters/markdownReporter.js.map +1 -1
  72. package/dist/tool-manifest.json +79 -6
  73. package/dist/types.d.ts +21 -0
  74. package/dist/utils/config.js +76 -51
  75. package/dist/utils/config.js.map +1 -1
  76. package/package.json +8 -1
package/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  AI coding agents are becoming the primary interface to code. Today, when you ask your agent *"which files implement auth?"* or *"what breaks if I bump React from 18 to 19?"* - it either guesses from names, or it shells out to grep and reads raw output not built for it.
22
22
 
23
- **projscan is the first code-intelligence tool built for agents, not for humans.** Your agent gets a fast, AST-accurate, context-budget-aware view of your codebase through 20 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots, diff structural changes between refs, surface coupling/cycle hotspots, get a one-call PR review, request structured fix-action prompts for any open issue, and ask "what breaks if I change this?" via transitive blast-radius analysis - without loading the file tree into its context.
23
+ **projscan is the first code-intelligence tool built for agents, not for humans.** Your agent gets a fast, AST-accurate, context-budget-aware view of your codebase through 22 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots, diff structural changes between refs, surface coupling/cycle hotspots, get a one-call PR review, request structured fix-action prompts for any open issue, ask "what breaks if I change this?" via transitive blast-radius analysis, share a durable session across multiple agent invocations, and learn from how you use it — quieting accumulated noise on this specific repo over time without ever phoning home.
24
24
 
25
25
  Humans get the same thing through the CLI.
26
26
 
@@ -231,12 +231,12 @@ Cache version bumped 2 → 3 in 0.11 (CC stored per file). Existing v2 caches ar
231
231
 
232
232
  ## Performance
233
233
 
234
- Reference numbers from `npm run bench` on an Apple M3 Pro running Node 25 (cold / warm cache, milliseconds). These are the 1.0 reference numbers:
234
+ Reference numbers from `npm run bench` on an Apple M3 Pro running Node 25 (cold / warm cache, milliseconds), refreshed for 1.5.0:
235
235
 
236
236
  | Repo | Files | analyze | doctor | hotspots | coupling | search |
237
237
  |------|-------|---------|--------|----------|----------|--------|
238
- | projscan itself | ~135 | 612 / 463 | 557 / 509 | 673 / 507 | 364 / 173 | 439 / 258 |
239
- | Synthetic medium | 500 | 278 / 268 | 274 / 260 | 300 / 297 | 217 / 174 | 238 / 193 |
238
+ | projscan itself | ~120 | 650 / 576 | 659 / 574 | 794 / 622 | 405 / 186 | 485 / 277 |
239
+ | Synthetic medium | 500 | 284 / 257 | 277 / 255 | 300 / 278 | 224 / 177 | 239 / 196 |
240
240
 
241
241
  For real-world numbers against larger codebases, `npm run bench:references` shallow-clones TypeScript, Django, and kubernetes/client-go into `.bench-cache/` (gitignored) and runs the same suite. First run is network-bound; later runs reuse the cache. Restrict to one target with `-- --only ts|django|k8s-client-go`.
242
242
 
@@ -515,25 +515,104 @@ Coverage is also automatically joined into `projscan hotspots` when one of those
515
515
 
516
516
  **This is the primary way to use projscan.** `projscan mcp` starts an [MCP](https://modelcontextprotocol.io) server over stdio so AI coding agents can query your codebase with real structural accuracy - not regex, not grep.
517
517
 
518
+ <img src="docs/projscan-agent-demo.gif" alt="projscan answering two agent questions: what breaks if I rename buildCodeGraph (impact analysis with definitions, direct callers, transitive reach), and where should I fix first (ranked hotspots with cyclomatic complexity)" width="700">
519
+
520
+ Two questions an agent asks; structural answers in milliseconds. *"What breaks if I rename `buildCodeGraph`?"* → 31 direct callers, 97 files reachable. *"Where should I fix first?"* → ranked hotspots with AST cyclomatic complexity, churn, and ownership signals.
521
+
518
522
  ### Claude Code
519
523
 
524
+ One-liner — adds projscan as an MCP server in the current project:
525
+
520
526
  ```bash
521
- claude mcp add projscan -- npx projscan mcp
527
+ claude mcp add projscan -- npx -y projscan mcp
528
+ ```
529
+
530
+ ### Cursor
531
+
532
+ Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project):
533
+
534
+ ```json
535
+ {
536
+ "mcpServers": {
537
+ "projscan": {
538
+ "command": "npx",
539
+ "args": ["-y", "projscan", "mcp"]
540
+ }
541
+ }
542
+ }
522
543
  ```
523
544
 
524
- ### Cursor / Windsurf / any MCP client
545
+ ### Windsurf
546
+
547
+ Add to `~/.codeium/windsurf/mcp_config.json`:
525
548
 
526
549
  ```json
527
550
  {
528
551
  "mcpServers": {
529
552
  "projscan": {
530
553
  "command": "npx",
531
- "args": ["projscan", "mcp"]
554
+ "args": ["-y", "projscan", "mcp"]
532
555
  }
533
556
  }
534
557
  }
535
558
  ```
536
559
 
560
+ ### Cline (VS Code extension)
561
+
562
+ In Cline's MCP settings panel (or the underlying `cline_mcp_settings.json`):
563
+
564
+ ```json
565
+ {
566
+ "mcpServers": {
567
+ "projscan": {
568
+ "command": "npx",
569
+ "args": ["-y", "projscan", "mcp"]
570
+ }
571
+ }
572
+ }
573
+ ```
574
+
575
+ ### Continue.dev
576
+
577
+ Add to `~/.continue/config.yaml`:
578
+
579
+ ```yaml
580
+ mcpServers:
581
+ - name: projscan
582
+ command: npx
583
+ args:
584
+ - -y
585
+ - projscan
586
+ - mcp
587
+ ```
588
+
589
+ ### Zed
590
+
591
+ Add to `~/.config/zed/settings.json` under `context_servers`:
592
+
593
+ ```json
594
+ {
595
+ "context_servers": {
596
+ "projscan": {
597
+ "command": {
598
+ "path": "npx",
599
+ "args": ["-y", "projscan", "mcp"]
600
+ }
601
+ }
602
+ }
603
+ }
604
+ ```
605
+
606
+ ### Any other MCP-aware client
607
+
608
+ The transport is **stdio**. Wire your client to invoke `npx -y projscan mcp` as a subprocess; the server speaks JSON-RPC 2.0 over stdin/stdout. If your client wants `notifications/file_changed` push notifications when the repo changes, append `--watch`:
609
+
610
+ ```bash
611
+ npx -y projscan mcp --watch
612
+ ```
613
+
614
+ Capability is advertised under `experimental.fileChanged` on `initialize` so clients can detect support.
615
+
537
616
  ### What agents can ask
538
617
 
539
618
  - *"Who imports `src/auth/jwt.ts`?"* → `projscan_graph { file, direction: "importers" }`
@@ -543,7 +622,7 @@ claude mcp add projscan -- npx projscan mcp
543
622
  - *"What breaks if I bump chalk to 6?"* → `projscan_upgrade { package: "chalk" }`
544
623
  - *"Where should I refactor first?"* → `projscan_hotspots`
545
624
 
546
- ### The 20 MCP tools
625
+ ### The 22 MCP tools
547
626
 
548
627
  **Structural (0.6.0 / 0.11 / 0.13 / 0.14 / 0.15 - agent-native):**
549
628
  - **`projscan_graph`** - query the AST-based code graph. Directions: `imports`, `exports`, `importers`, `symbol_defs`, `package_importers`. Millisecond responses on a warm cache.
@@ -573,6 +652,12 @@ claude mcp add projscan -- npx projscan mcp
573
652
  **Workspace (0.11):**
574
653
  - `projscan_workspaces` - list monorepo packages (npm/yarn/pnpm/Nx/Turbo/Lerna). Use the `name` as the `package` arg on `projscan_hotspots` / `projscan_coupling` to scope.
575
654
 
655
+ **Session (1.4):**
656
+ - **`projscan_session`** *(1.4)* - durable cross-invocation session. Subactions: `current` (id + counts), `touched` (files touched this session, sorted by recency, filterable by source: `tool-result` / `fs-watch` / `explicit`), `events` (chronological log), `reset` (start a fresh session). Auto-populated from every tool result and from `notifications/file_changed` push events when `--watch` is on. Lets multiple agents in the same project see "what's been touched here" without re-running git.
657
+
658
+ **Memory (1.5):**
659
+ - **`projscan_memory`** *(1.5)* - durable, local-only feedback loop. Records, per analyzer rule id, how many runs surfaced it and how many fixed it. Subactions: `current` (aggregate counts), `stable` (rules surfaced across ≥ 3 runs over ≥ 7 days without ever being fixed — paired with a ready-to-paste `.projscanrc.json disableRules` snippet), `runs` (every tracked rule with full history), `forget` (drop a single rule). Stored at `.projscan-memory/memory.json`; never leaves the machine. Lets an agent ask "what is this project tolerating?" and propose quieting it.
660
+
576
661
  ### Context-window budgeting
577
662
 
578
663
  **Every MCP tool accepts an optional `max_tokens` argument.** Set it and projscan serializes the result, and - if over budget - truncates the largest array field record-by-record until it fits. Responses include a `_budget` sidecar when truncated so your agent knows it got a partial view.
@@ -616,9 +701,13 @@ All opt-in - default behavior is unchanged.
616
701
 
617
702
  projscan caches parsed ASTs at `.projscan-cache/graph.json` (auto-gitignored). First run populates it; subsequent runs re-parse only files whose `mtime` changed. Agent queries on a warm cache are milliseconds, not seconds.
618
703
 
619
- ### Prompts (2, parameterized with live project data)
704
+ ### Prompts (6, parameterized with live project data)
620
705
  - `prioritize_refactoring` - ranked plan grounded in current hotspots
621
706
  - `investigate_file` - senior-engineer brief for a specific file
707
+ - **`refactor_hotspot`** *(1.5)* - step-by-step refactor plan for one hotspot file
708
+ - **`triage_doctor_issues`** *(1.5)* - critical / important / backlog ordering of open issues
709
+ - **`review_this_pr`** *(1.5)* - PR-comment-ready review primed with the structural diff and verdict
710
+ - **`safely_rename_symbol`** *(1.5)* - ordered rename + verification checklist via `projscan_impact` blast radius
622
711
 
623
712
  ### Resources (3, readable on demand)
624
713
  - `projscan://health` · `projscan://hotspots` · `projscan://structure`
@@ -10,6 +10,7 @@ import { reportHealthJson } from '../../reporters/jsonReporter.js';
10
10
  import { reportHealthMarkdown } from '../../reporters/markdownReporter.js';
11
11
  import { reportHealthSarif } from '../../reporters/sarifReporter.js';
12
12
  import { reportHealthHtml } from '../../reporters/htmlReporter.js';
13
+ import { findStableRules, loadMemory } from '../../core/memory.js';
13
14
  export function registerDoctor() {
14
15
  program
15
16
  .command('doctor')
@@ -51,8 +52,23 @@ export function registerDoctor() {
51
52
  case 'html':
52
53
  reportHealthHtml(issues);
53
54
  break;
54
- default:
55
- reportHealth(issues, scan.scanDurationMs);
55
+ default: {
56
+ // 1.5+ — surface a Project Memory tip when stable rules
57
+ // have accumulated. Best-effort: a memory load failure
58
+ // never blocks the doctor output.
59
+ let stableRuleCount = 0;
60
+ try {
61
+ const memory = await loadMemory(rootPath);
62
+ stableRuleCount = findStableRules(memory).length;
63
+ }
64
+ catch {
65
+ // best-effort
66
+ }
67
+ reportHealth(issues, {
68
+ scanTimeMs: scan.scanDurationMs,
69
+ stableRuleCount,
70
+ });
71
+ }
56
72
  }
57
73
  }
58
74
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,7 @@ export function registerExplain() {
31
31
  catch (error) {
32
32
  if (error.code === 'ENOENT') {
33
33
  console.error(chalk.red(`File not found: ${filePath}`));
34
+ console.error(chalk.dim(` Tip: paths are repo-relative. Run \`projscan structure\` to see the file tree.`));
34
35
  }
35
36
  else {
36
37
  console.error(chalk.red(error instanceof Error ? error.message : String(error)));
@@ -1 +1 @@
1
- {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAEhF,MAAM,UAAU,eAAe;IAC7B,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACzD,MAAM,WAAW,GAAG,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAEvD,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,qBAAqB,CAAC,WAAW,CAAC,CAAC;oBACnC,MAAM;gBACR,KAAK,UAAU;oBACb,yBAAyB,CAAC,WAAW,CAAC,CAAC;oBACvC,MAAM;gBACR;oBACE,iBAAiB,CAAC,WAAW,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAEhF,MAAM,UAAU,eAAe;IAC7B,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACzD,MAAM,WAAW,GAAG,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAEvD,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,qBAAqB,CAAC,WAAW,CAAC,CAAC;oBACnC,MAAM;gBACR,KAAK,UAAU;oBACb,yBAAyB,CAAC,WAAW,CAAC,CAAC;oBACvC,MAAM;gBACR;oBACE,iBAAiB,CAAC,WAAW,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC,CAAC;gBACxD,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,kFAAkF,CAAC,CAC9F,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `projscan memory` — inspect or prune the local Project Memory store
3
+ * that learns which analyzer rules this repo has been carrying across
4
+ * many runs.
5
+ *
6
+ * projscan memory — aggregate summary (default)
7
+ * projscan memory stable — long-running rules + .projscanrc snippet
8
+ * projscan memory runs — every tracked rule with full history
9
+ * projscan memory forget <rule> — drop a single rule's history
10
+ */
11
+ export declare function registerMemory(): void;
@@ -0,0 +1,175 @@
1
+ import chalk from 'chalk';
2
+ import { program, getRootPath, getFormat, setupLogLevel, maybeCompactBanner } from '../_shared.js';
3
+ import { findStableRules, forgetRule, loadMemory, saveMemory, } from '../../core/memory.js';
4
+ /**
5
+ * `projscan memory` — inspect or prune the local Project Memory store
6
+ * that learns which analyzer rules this repo has been carrying across
7
+ * many runs.
8
+ *
9
+ * projscan memory — aggregate summary (default)
10
+ * projscan memory stable — long-running rules + .projscanrc snippet
11
+ * projscan memory runs — every tracked rule with full history
12
+ * projscan memory forget <rule> — drop a single rule's history
13
+ */
14
+ export function registerMemory() {
15
+ const memory = program
16
+ .command('memory')
17
+ .description('Inspect or prune the local Project Memory (1.5+)')
18
+ .action(async () => {
19
+ await runSummary();
20
+ });
21
+ memory
22
+ .command('stable')
23
+ .description('Show rules that have been surfacing across enough runs to count as accepted')
24
+ .action(async () => {
25
+ await runStable();
26
+ });
27
+ memory
28
+ .command('runs')
29
+ .description('Show every tracked rule with its observation history')
30
+ .action(async () => {
31
+ await runRuns();
32
+ });
33
+ memory
34
+ .command('forget <rule>')
35
+ .description("Drop a single rule's history from memory")
36
+ .action(async (rule) => {
37
+ await runForget(rule);
38
+ });
39
+ }
40
+ async function runSummary() {
41
+ setupLogLevel();
42
+ maybeCompactBanner();
43
+ const rootPath = getRootPath();
44
+ const format = getFormat();
45
+ try {
46
+ const m = await loadMemory(rootPath);
47
+ if (format === 'json') {
48
+ console.log(JSON.stringify(summarize(m), null, 2));
49
+ return;
50
+ }
51
+ printSummary(m);
52
+ }
53
+ catch (error) {
54
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
55
+ process.exit(1);
56
+ }
57
+ }
58
+ async function runStable() {
59
+ setupLogLevel();
60
+ maybeCompactBanner();
61
+ const rootPath = getRootPath();
62
+ const format = getFormat();
63
+ try {
64
+ const m = await loadMemory(rootPath);
65
+ const stable = findStableRules(m);
66
+ if (format === 'json') {
67
+ console.log(JSON.stringify({ totalRuns: m.totalRuns, stable }, null, 2));
68
+ return;
69
+ }
70
+ printStable(m, stable);
71
+ }
72
+ catch (error) {
73
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
74
+ process.exit(1);
75
+ }
76
+ }
77
+ async function runRuns() {
78
+ setupLogLevel();
79
+ maybeCompactBanner();
80
+ const rootPath = getRootPath();
81
+ const format = getFormat();
82
+ try {
83
+ const m = await loadMemory(rootPath);
84
+ const all = Object.values(m.rules).sort((a, b) => b.runCount - a.runCount);
85
+ if (format === 'json') {
86
+ console.log(JSON.stringify({ totalRuns: m.totalRuns, rules: all }, null, 2));
87
+ return;
88
+ }
89
+ printRuns(m, all);
90
+ }
91
+ catch (error) {
92
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
93
+ process.exit(1);
94
+ }
95
+ }
96
+ async function runForget(rule) {
97
+ setupLogLevel();
98
+ maybeCompactBanner();
99
+ const rootPath = getRootPath();
100
+ try {
101
+ const m = await loadMemory(rootPath);
102
+ const existed = forgetRule(m, rule);
103
+ if (existed) {
104
+ await saveMemory(rootPath, m);
105
+ console.log(chalk.green(`✓ Dropped "${rule}" from memory.`));
106
+ }
107
+ else {
108
+ console.log(chalk.dim(`No memory entry for "${rule}".`));
109
+ }
110
+ }
111
+ catch (error) {
112
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
113
+ process.exit(1);
114
+ }
115
+ }
116
+ function summarize(m) {
117
+ return {
118
+ totalRuns: m.totalRuns,
119
+ rulesTracked: Object.keys(m.rules).length,
120
+ stableRuleCount: findStableRules(m).length,
121
+ lastUpdatedAt: m.lastUpdatedAt,
122
+ };
123
+ }
124
+ function printSummary(m) {
125
+ const stableCount = findStableRules(m).length;
126
+ console.log('');
127
+ console.log(chalk.bold('Project Memory'));
128
+ console.log(chalk.dim('────────────────────────────────────────'));
129
+ console.log(` total runs: ${m.totalRuns}`);
130
+ console.log(` rules tracked: ${Object.keys(m.rules).length}`);
131
+ console.log(` stable rules: ${stableCount > 0 ? chalk.yellow(stableCount) : '0'} ${chalk.dim('(unfixed across many runs)')}`);
132
+ console.log(` last updated: ${m.lastUpdatedAt}`);
133
+ if (stableCount > 0) {
134
+ console.log('');
135
+ console.log(chalk.dim(' Tip: run `projscan memory stable` to see which rules to consider disabling in .projscanrc.'));
136
+ }
137
+ }
138
+ function printStable(m, stable) {
139
+ console.log('');
140
+ console.log(chalk.bold('Stable rules (effectively accepted)'));
141
+ console.log(chalk.dim('────────────────────────────────────────'));
142
+ if (stable.length === 0) {
143
+ console.log(chalk.dim(' No stable rules yet — they appear after a rule has surfaced across'));
144
+ console.log(chalk.dim(' ≥ 3 runs over ≥ 7 days without ever being fixed.'));
145
+ return;
146
+ }
147
+ for (const r of stable) {
148
+ console.log(` ${chalk.yellow('▲')} ${chalk.bold(r.ruleId)} ${chalk.dim(`(seen in ${r.runCount} runs since ${r.firstSeenAt.slice(0, 10)})`)}`);
149
+ }
150
+ console.log('');
151
+ console.log(chalk.bold(' Suggested .projscanrc.json:'));
152
+ console.log('');
153
+ console.log(' ' + JSON.stringify({ disableRules: stable.map((r) => r.ruleId) }, null, 2).split('\n').join('\n '));
154
+ }
155
+ function printRuns(m, all) {
156
+ console.log('');
157
+ console.log(chalk.bold(`Tracked rules (${all.length})`));
158
+ console.log(chalk.dim('────────────────────────────────────────'));
159
+ if (all.length === 0) {
160
+ console.log(chalk.dim(' No rules tracked yet. Memory begins recording on `projscan doctor` runs.'));
161
+ return;
162
+ }
163
+ for (const r of all.slice(0, 30)) {
164
+ const status = r.suppressedInConfig
165
+ ? chalk.dim('[suppressed]')
166
+ : r.fixedCount > 0
167
+ ? chalk.green(`[fixed ×${r.fixedCount}]`)
168
+ : chalk.dim('[active]');
169
+ console.log(` runs ${String(r.runCount).padStart(3)} ${status} ${r.ruleId}`);
170
+ }
171
+ if (all.length > 30) {
172
+ console.log(chalk.dim(` ... and ${all.length - 30} more`));
173
+ }
174
+ }
175
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/cli/commands/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `projscan session` — inspect the durable cross-invocation session that
3
+ * the MCP server populates as agents work. Mirrors the `projscan_session`
4
+ * MCP tool but for terminal use.
5
+ *
6
+ * Subcommands:
7
+ * projscan session — current session summary (default)
8
+ * projscan session touched — list files touched this session
9
+ * projscan session events — chronological event log
10
+ * projscan session reset — discard the current session
11
+ */
12
+ export declare function registerSession(): void;
@@ -0,0 +1,200 @@
1
+ import chalk from 'chalk';
2
+ import { program, getRootPath, getFormat, setupLogLevel, maybeCompactBanner } from '../_shared.js';
3
+ import { loadSession, resetSession, } from '../../core/session.js';
4
+ /**
5
+ * `projscan session` — inspect the durable cross-invocation session that
6
+ * the MCP server populates as agents work. Mirrors the `projscan_session`
7
+ * MCP tool but for terminal use.
8
+ *
9
+ * Subcommands:
10
+ * projscan session — current session summary (default)
11
+ * projscan session touched — list files touched this session
12
+ * projscan session events — chronological event log
13
+ * projscan session reset — discard the current session
14
+ */
15
+ export function registerSession() {
16
+ const session = program
17
+ .command('session')
18
+ .description('Inspect or reset the durable cross-invocation session (1.4+)')
19
+ .action(async () => {
20
+ await runSummary();
21
+ });
22
+ session
23
+ .command('touched')
24
+ .description('List files touched this session (sorted by last-touched desc)')
25
+ .option('--source <source>', 'restrict to tool-result | fs-watch | explicit')
26
+ .option('--limit <n>', 'show at most N entries', (v) => parseInt(v, 10))
27
+ .action(async (opts) => {
28
+ await runTouched(opts);
29
+ });
30
+ session
31
+ .command('events')
32
+ .description('Show the session event log, newest first')
33
+ .option('--limit <n>', 'show at most N entries', (v) => parseInt(v, 10))
34
+ .action(async (opts) => {
35
+ await runEvents(opts);
36
+ });
37
+ session
38
+ .command('reset')
39
+ .description('Discard the current session and start a fresh one')
40
+ .action(async () => {
41
+ await runReset();
42
+ });
43
+ }
44
+ async function runSummary() {
45
+ setupLogLevel();
46
+ maybeCompactBanner();
47
+ const rootPath = getRootPath();
48
+ const format = getFormat();
49
+ try {
50
+ const { session: sess, created } = await loadSession(rootPath);
51
+ if (format === 'json') {
52
+ console.log(JSON.stringify({ session: sess, created }, null, 2));
53
+ return;
54
+ }
55
+ printSummary(sess, created);
56
+ }
57
+ catch (error) {
58
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
59
+ process.exit(1);
60
+ }
61
+ }
62
+ async function runTouched(opts) {
63
+ setupLogLevel();
64
+ maybeCompactBanner();
65
+ const rootPath = getRootPath();
66
+ const format = getFormat();
67
+ try {
68
+ const { session: sess } = await loadSession(rootPath);
69
+ const all = Object.values(sess.touchedFiles);
70
+ const filtered = opts.source ? all.filter((t) => t.source === opts.source) : all;
71
+ filtered.sort((a, b) => b.lastTouchedAt.localeCompare(a.lastTouchedAt));
72
+ const limited = typeof opts.limit === 'number' && opts.limit > 0 ? filtered.slice(0, opts.limit) : filtered;
73
+ if (format === 'json') {
74
+ console.log(JSON.stringify({ sessionId: sess.id, total: filtered.length, touched: limited }, null, 2));
75
+ return;
76
+ }
77
+ printTouched(sess, limited, filtered.length);
78
+ }
79
+ catch (error) {
80
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
81
+ process.exit(1);
82
+ }
83
+ }
84
+ async function runEvents(opts) {
85
+ setupLogLevel();
86
+ maybeCompactBanner();
87
+ const rootPath = getRootPath();
88
+ const format = getFormat();
89
+ try {
90
+ const { session: sess } = await loadSession(rootPath);
91
+ const reversed = [...sess.events].reverse();
92
+ const limited = typeof opts.limit === 'number' && opts.limit > 0 ? reversed.slice(0, opts.limit) : reversed;
93
+ if (format === 'json') {
94
+ console.log(JSON.stringify({ sessionId: sess.id, total: reversed.length, events: limited }, null, 2));
95
+ return;
96
+ }
97
+ printEvents(sess, limited, reversed.length);
98
+ }
99
+ catch (error) {
100
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
101
+ process.exit(1);
102
+ }
103
+ }
104
+ async function runReset() {
105
+ setupLogLevel();
106
+ maybeCompactBanner();
107
+ const rootPath = getRootPath();
108
+ const format = getFormat();
109
+ try {
110
+ const fresh = await resetSession(rootPath);
111
+ if (format === 'json') {
112
+ console.log(JSON.stringify({ reset: true, session: fresh }, null, 2));
113
+ return;
114
+ }
115
+ console.log(chalk.green('✓ Session reset.'));
116
+ console.log(` New session id: ${fresh.id}`);
117
+ console.log(` Started at: ${fresh.startedAt}`);
118
+ }
119
+ catch (error) {
120
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
121
+ process.exit(1);
122
+ }
123
+ }
124
+ function printSummary(session, created) {
125
+ const startedAtMs = Date.parse(session.startedAt);
126
+ const ageMs = Number.isFinite(startedAtMs) ? Date.now() - startedAtMs : null;
127
+ console.log('');
128
+ console.log(chalk.bold('Session'));
129
+ console.log(chalk.dim('────────────────────────────────────────'));
130
+ console.log(` id: ${session.id}`);
131
+ console.log(` status: ${created ? chalk.cyan('fresh (just created)') : chalk.green('active')}`);
132
+ console.log(` started: ${session.startedAt}`);
133
+ console.log(` last activity: ${session.lastActivityAt}`);
134
+ if (ageMs !== null) {
135
+ console.log(` age: ${formatDuration(ageMs)}`);
136
+ }
137
+ console.log('');
138
+ console.log(` touched files: ${Object.keys(session.touchedFiles).length}`);
139
+ console.log(` events: ${session.events.length}`);
140
+ console.log('');
141
+ console.log(chalk.dim(' Tip: run `projscan session touched` for the file list.'));
142
+ }
143
+ function printTouched(session, items, total) {
144
+ console.log('');
145
+ console.log(chalk.bold(`Touched files (${session.id.slice(0, 8)})`));
146
+ console.log(chalk.dim('────────────────────────────────────────'));
147
+ if (items.length === 0) {
148
+ console.log(chalk.dim(' No files touched in this session yet.'));
149
+ return;
150
+ }
151
+ for (const t of items) {
152
+ const sourceTag = formatSourceTag(t.source);
153
+ console.log(` ${sourceTag} ${t.file} ${chalk.dim(`(×${t.count}, ${t.lastTouchedAt})`)}`);
154
+ }
155
+ if (items.length < total) {
156
+ console.log('');
157
+ console.log(chalk.dim(` ${total - items.length} more — pass --limit ${total} to see all.`));
158
+ }
159
+ }
160
+ function printEvents(session, items, total) {
161
+ console.log('');
162
+ console.log(chalk.bold(`Events (${session.id.slice(0, 8)})`));
163
+ console.log(chalk.dim('────────────────────────────────────────'));
164
+ if (items.length === 0) {
165
+ console.log(chalk.dim(' No events recorded in this session yet.'));
166
+ return;
167
+ }
168
+ for (const e of items) {
169
+ console.log(` ${chalk.dim(e.at)} ${e.kind}`);
170
+ }
171
+ if (items.length < total) {
172
+ console.log('');
173
+ console.log(chalk.dim(` ${total - items.length} more — pass --limit ${total} to see all.`));
174
+ }
175
+ }
176
+ function formatSourceTag(source) {
177
+ switch (source) {
178
+ case 'tool-result':
179
+ return chalk.cyan('[tool] ');
180
+ case 'fs-watch':
181
+ return chalk.yellow('[fs-watch]');
182
+ case 'explicit':
183
+ return chalk.magenta('[explicit]');
184
+ default:
185
+ return chalk.dim('[unknown] ');
186
+ }
187
+ }
188
+ function formatDuration(ms) {
189
+ if (ms < 1000)
190
+ return `${ms}ms`;
191
+ const seconds = Math.floor(ms / 1000);
192
+ if (seconds < 60)
193
+ return `${seconds}s`;
194
+ const minutes = Math.floor(seconds / 60);
195
+ if (minutes < 60)
196
+ return `${minutes}m ${seconds % 60}s`;
197
+ const hours = Math.floor(minutes / 60);
198
+ return `${hours}h ${minutes % 60}m`;
199
+ }
200
+ //# sourceMappingURL=session.js.map