codehere 1.0.1 → 1.0.2

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 (163) hide show
  1. package/README.md +36 -192
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,87 +0,0 @@
1
- /**
2
- * Hook mode detection — Pattern 20 backend.
3
- *
4
- * Reads ~/.claude/settings.json and reports which posture the safety
5
- * hook is in. Mode is encoded as flags on the configured command:
6
- *
7
- * off — no codehere scan-tool entry in PreToolUse
8
- * log-only — `codehere scan-tool --log-only`
9
- * advisory — DETECTION ONLY (LOW #20, atomic audit): getHookMode
10
- * recognizes a `--advisory` flag for forward-compat, but
11
- * scan-tool implements no advisory posture — a user writing
12
- * --advisory gets the DEFAULT block behavior while the nav
13
- * indicator would read 'advisory'. Don't advertise the flag.
14
- * block — `codehere scan-tool` (default critical/high block)
15
- * OR `codehere scan-tool --block-all` (all severity levels)
16
- *
17
- * Read-only by design — this module never WRITES the config. Mode
18
- * switching belongs in `codehere hook install/uninstall` so users go
19
- * through the same path that re-validates the hook surface.
20
- */
21
- import { existsSync, readFileSync } from 'fs';
22
- import { join } from 'path';
23
- import { homedir } from 'os';
24
- /**
25
- * Resolve at call time, not at import.
26
- *
27
- * This was a module-level const, which froze the path to whatever HOME happened to be when the
28
- * module first loaded. For a one-shot CLI that is harmless — HOME does not move mid-process —
29
- * but it made the safety-mode read untestable against an isolated home, and the test that
30
- * covered it compensated by writing to the developer's REAL ~/.claude/settings.json instead.
31
- * A safety feature whose only test edits the user's live safety config is not covered, it is
32
- * gambled with, and on CI (no ~/.claude at all) it failed outright.
33
- *
34
- * A function costs one stat call per invocation and makes the module honest under any HOME.
35
- */
36
- const settingsPath = () => join(homedir(), '.claude', 'settings.json');
37
- /**
38
- * Detect the current safety-hook mode from ~/.claude/settings.json.
39
- * Defensive against missing / malformed settings — anything we can't
40
- * parse cleanly returns 'off' (the most-conservative read).
41
- */
42
- export function getHookMode() {
43
- if (!existsSync(settingsPath()))
44
- return 'off';
45
- let parsed;
46
- try {
47
- parsed = JSON.parse(readFileSync(settingsPath(), 'utf-8'));
48
- }
49
- catch {
50
- return 'off';
51
- }
52
- const hooks = (parsed.hooks ?? {});
53
- const preToolUse = (hooks.PreToolUse ?? []);
54
- if (!Array.isArray(preToolUse) || preToolUse.length === 0)
55
- return 'off';
56
- let bestMatch = 'off';
57
- for (const entry of preToolUse) {
58
- if (!entry?.hooks)
59
- continue;
60
- for (const hk of entry.hooks) {
61
- const cmd = hk?.command;
62
- if (typeof cmd !== 'string')
63
- continue;
64
- if (!cmd.includes('codehere scan-tool'))
65
- continue;
66
- // Earliest match wins block > advisory > log-only — once a stricter
67
- // mode is found we keep it. (block is the operational default;
68
- // log-only is opt-in.)
69
- if (cmd.includes('--log-only')) {
70
- if (bestMatch === 'off')
71
- bestMatch = 'log-only';
72
- }
73
- else if (cmd.includes('--advisory')) {
74
- if (bestMatch === 'off' || bestMatch === 'log-only')
75
- bestMatch = 'advisory';
76
- }
77
- else {
78
- // No flag OR --block-all: both render as 'block' in the UI.
79
- // Block-all is stricter but the user-visible posture ("safety
80
- // hook actively blocks risky calls") is the same.
81
- return 'block';
82
- }
83
- }
84
- }
85
- return bestMatch;
86
- }
87
- //# sourceMappingURL=hook-mode.js.map
@@ -1,49 +0,0 @@
1
- /**
2
- * Cross-vendor tool-call audit mirror — #110 slice 2 (Codex first).
3
- *
4
- * When non-Claude adapters emit a tool-call event, we can't BLOCK
5
- * (no synchronous pre-tool intercept yet) but we CAN mirror the
6
- * call into the audit log post-hoc. Closes the audit-trail-parity
7
- * gap: every adapter's tool calls land in the same hook-events.jsonl
8
- * the per-tool Claude hook writes to.
9
- *
10
- * Future slices (per-adapter blocking) replace the post-hoc mirror
11
- * with pre-call intercepts. For now: visibility today, blocking next.
12
- */
13
- import { scanPayload, calculateRiskScore, appendAuditLog, AUDIT_FILE } from './claude-sdk-hook.js';
14
- import { signAuditTip } from './audit-tip-signature.js';
15
- export function mirrorToolCallToAudit(rec) {
16
- // Run the same pattern bank the per-tool hook uses, so post-hoc
17
- // findings show up in the audit feed with the same severity calibration.
18
- const findings = scanPayload(rec.toolInput);
19
- const riskScore = calculateRiskScore(findings);
20
- const truncated = rec.toolInput.length > 500 ? rec.toolInput.slice(0, 500) + '…' : rec.toolInput;
21
- const entry = {
22
- timestamp: new Date().toISOString(),
23
- sessionId: rec.sessionId,
24
- toolName: rec.toolName,
25
- toolInput: JSON.stringify({
26
- agent: rec.agent,
27
- input: truncated,
28
- cwd: rec.cwd ?? null,
29
- inputLength: rec.toolInput.length,
30
- mirrored: true, // marks post-hoc vs the synchronous Claude hook
31
- }),
32
- riskScore,
33
- blocked: false, // post-hoc — can't block, only log
34
- findings: findings.length,
35
- criticalFindings: findings.filter((f) => f.severity === 'critical').length,
36
- // R1.5 · the mirror runs IN the daemon — stamp the provenance it promises.
37
- writer: 'daemon',
38
- };
39
- try {
40
- appendAuditLog(entry);
41
- // R1.3 · sign-at-write — the daemon's mirror is a daemon append; advance
42
- // the signed tip per write (sub-ms, fail-open).
43
- signAuditTip(AUDIT_FILE);
44
- }
45
- catch {
46
- // Audit-write failures don't propagate — the agent's already running.
47
- }
48
- }
49
- //# sourceMappingURL=mirror-tool-call-to-audit.js.map
@@ -1,122 +0,0 @@
1
- /**
2
- * Pre-prompt safety scan — cross-vendor parity, task #110.
3
- *
4
- * The substrate the FAQ landing claims for v0.5.x → v0.6: "per-tool
5
- * blocking parity for Codex / Aider / OpenCode / Gemini." Today, only
6
- * Claude Code has a per-tool PreToolUse hook. The other 4 adapters
7
- * spawn raw — every Bash/Edit they make goes unscanned.
8
- *
9
- * Closing that gap fully needs per-adapter pre-tool intercepts (huge
10
- * scope per-adapter — different IPC + lifecycle for each CLI). This
11
- * module ships the FIRST cross-vendor slice: scan the user's PROMPT
12
- * before spawn. Catches the prompt-side leaks (API key in prompt,
13
- * injection-pattern poisoning, credential paste) for ALL 5 adapters
14
- * with one piece of code.
15
- *
16
- * Posture follows getHookMode():
17
- * block → high+ severity findings refuse spawn (throw)
18
- * advisory → findings logged + warning event, spawn proceeds
19
- * log-only → findings logged, spawn proceeds, no warning
20
- * off → no scan, spawn proceeds
21
- *
22
- * Every scan writes one audit-log entry with synthetic toolName
23
- * 'pre_prompt' so the audit feed + hash chain pick it up
24
- * without surface changes.
25
- */
26
- import { scanPayload, calculateRiskScore, appendAuditLog } from './claude-sdk-hook.js';
27
- import { getHookMode } from './hook-mode.js';
28
- import { filterAllowed } from '../security/permissions-config.js';
29
- /**
30
- * Scan a user prompt before spawn. Always writes one audit-log entry.
31
- * Returns allowed=false ONLY when mode === 'block' AND there's a
32
- * high-or-critical finding. Other modes always allow but still log.
33
- */
34
- export function scanPromptBeforeSpawn(input) {
35
- const rawFindings = scanPayload(input.prompt);
36
- // P18 slice 1 — filter against the cwd's permissions.yaml allow-list.
37
- // Allow-listed findings are dropped from the BLOCK calculation but
38
- // still recorded in the audit-log allowed-list field so a forensic
39
- // reviewer can see what was suppressed and why.
40
- const { surviving, allowed } = input.cwd
41
- ? filterAllowed(rawFindings, input.cwd)
42
- : { surviving: rawFindings, allowed: [] };
43
- const findings = surviving;
44
- const riskScore = calculateRiskScore(findings);
45
- const mode = (() => {
46
- try {
47
- return getHookMode();
48
- }
49
- catch {
50
- return 'off';
51
- }
52
- })();
53
- // Severity gate: only critical/high findings can refuse a spawn.
54
- // Medium/low findings get logged but never block — they're flagged
55
- // for visibility, not enforcement. Matches the per-tool hook's
56
- // existing severity-to-action policy.
57
- const hardFindings = findings.filter((f) => f.severity === 'critical' || f.severity === 'high');
58
- const wouldBlock = hardFindings.length > 0;
59
- const blocked = mode === 'block' && wouldBlock;
60
- // Audit entry — written for every scan, regardless of mode. Synthetic
61
- // toolName 'pre_prompt' lets audit-feed filters group these
62
- // separately from per-tool entries (existing chip filters on tool).
63
- const entry = {
64
- timestamp: new Date().toISOString(),
65
- sessionId: input.sessionId,
66
- toolName: 'pre_prompt',
67
- // Truncate the prompt for the audit record — full prompts can be
68
- // 10K+ chars and we don't want the audit log to balloon.
69
- toolInput: JSON.stringify({
70
- agent: input.agent,
71
- prompt: input.prompt.length > 500 ? input.prompt.slice(0, 500) + '…' : input.prompt,
72
- cwd: input.cwd ?? null,
73
- promptLength: input.prompt.length,
74
- // P18 slice 1 — record what got allow-listed for forensic audit.
75
- // Empty array when no permissions.yaml exists in the cwd.
76
- ...(allowed.length > 0
77
- ? {
78
- allowedByPermissions: allowed.map((a) => ({
79
- category: a.finding.category,
80
- matched: a.finding.matched,
81
- reason: a.entry.reason ?? '(no reason given)',
82
- })),
83
- }
84
- : {}),
85
- }),
86
- riskScore,
87
- blocked,
88
- findings: findings.length,
89
- criticalFindings: findings.filter((f) => f.severity === 'critical').length,
90
- // Mode-driven override marker so the audit reader knows WHY a
91
- // would-block finding wasn't enforced. Reuses the existing
92
- // OverrideReason 'log_only_mode' for log-only AND advisory + off
93
- // (all three skip the block; 'log_only_mode' is the closest
94
- // shared semantic — the entry's mode field can be added as a new
95
- // OverrideReason later without breaking the audit chain).
96
- ...(wouldBlock && !blocked ? { overrideReason: 'log_only_mode' } : {}),
97
- };
98
- try {
99
- appendAuditLog(entry);
100
- }
101
- catch {
102
- // Audit-write failure should NOT itself block a spawn — that would
103
- // be a denial-of-service vector (fill the log dir → no spawns).
104
- // The audit infra has its own failure-mode logging.
105
- }
106
- // Compose a block reason from the worst findings. Used by the
107
- // session-manager to throw an actionable error.
108
- let blockReason = '';
109
- if (blocked) {
110
- const top = hardFindings.slice(0, 3);
111
- const summary = top.map((f) => `[${f.severity}] ${f.description}`).join('; ');
112
- blockReason = `pre-prompt scan blocked: ${summary}`;
113
- }
114
- return {
115
- allowed: !blocked,
116
- blocked,
117
- riskScore,
118
- findings,
119
- blockReason,
120
- };
121
- }
122
- //# sourceMappingURL=pre-prompt-scan.js.map
@@ -1,177 +0,0 @@
1
- /**
2
- * Session Analyzer
3
- *
4
- * Detects multi-agent amplification patterns in the hook audit log.
5
- *
6
- * When Claude Code spawns sub-agents via the Task tool, each agent makes
7
- * additional tool calls. One compromised instruction can amplify into
8
- * many downstream tool calls — the "17.2x amplification" risk.
9
- *
10
- * This analyzer groups audit events by session and detects:
11
- * 1. High tool call volume per session (potential amplification)
12
- * 2. Sessions with blocked calls (injection attempts)
13
- * 3. Sessions with unusual tool mix (e.g., many Bash calls in a row)
14
- */
15
- /**
16
- * Group audit entries by session and compute per-session stats.
17
- */
18
- export function analyzeSessions(entries) {
19
- const bySession = new Map();
20
- for (const entry of entries) {
21
- const sid = entry.sessionId || 'unknown';
22
- if (!bySession.has(sid))
23
- bySession.set(sid, []);
24
- bySession.get(sid).push(entry);
25
- }
26
- const summaries = [];
27
- for (const [sessionId, sessionEntries] of bySession) {
28
- const sorted = [...sessionEntries].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
29
- const first = sorted[0];
30
- const last = sorted[sorted.length - 1];
31
- const durationMs = new Date(last.timestamp).getTime() - new Date(first.timestamp).getTime();
32
- const toolMix = {};
33
- let blocked = 0;
34
- let critical = 0;
35
- for (const e of sorted) {
36
- toolMix[e.toolName] = (toolMix[e.toolName] ?? 0) + 1;
37
- if (e.blocked)
38
- blocked++;
39
- if (e.criticalFindings > 0)
40
- critical++;
41
- }
42
- // Risk heuristics. The signal we care about is amplification — a runaway
43
- // or injected session doing things the user didn't watch. Volume ALONE is
44
- // weak signal: a normal attended dev session easily runs 40+ calls with
45
- // 20+ Bash (git, build, test, curl) and is perfectly clean. Crying "medium"
46
- // at that is the false alarm UX_OBSERVATIONS.md P1 warns against, and it
47
- // erodes trust in the real alerts. So:
48
- // - any blocked call or critical finding is genuine security signal
49
- // - pure volume only flags at a high bar (the code's own ">50" threshold)
50
- // - Bash-heavy only flags when ALSO high-volume (raised 10→20, +calls>40)
51
- const bashCount = toolMix['Bash'] ?? 0;
52
- const callCount = sorted.length;
53
- let risk = 'low';
54
- if ((blocked > 0 && callCount > 20) || critical > 0)
55
- risk = 'high';
56
- else if (blocked > 0)
57
- risk = 'medium';
58
- else if (callCount > 50 || (bashCount > 20 && callCount > 40))
59
- risk = 'medium';
60
- summaries.push({
61
- sessionId,
62
- firstSeen: first.timestamp,
63
- lastSeen: last.timestamp,
64
- totalCalls: callCount,
65
- blockedCalls: blocked,
66
- criticalCalls: critical,
67
- toolMix,
68
- amplificationRisk: risk,
69
- durationMs,
70
- });
71
- }
72
- return summaries.sort((a, b) => new Date(b.lastSeen).getTime() - new Date(a.lastSeen).getTime());
73
- }
74
- /**
75
- * Identify sessions with amplification risk worth flagging.
76
- */
77
- export function detectAmplificationAlerts(sessions) {
78
- const alerts = [];
79
- for (const session of sessions) {
80
- if (session.amplificationRisk === 'low')
81
- continue;
82
- let message;
83
- const risk = session.amplificationRisk;
84
- // rc.17 framing: don't accuse "injection" by default — that wording scares
85
- // users on perfectly normal long attended sessions. Reserve the strong
86
- // language for cases where the *blocked-call ratio* is high (real signal
87
- // of prompts trying things they shouldn't), not just call volume.
88
- // UX_OBSERVATIONS.md P1.
89
- const blockRatio = session.totalCalls > 0 ? session.blockedCalls / session.totalCalls : 0;
90
- if (session.blockedCalls > 0 && session.totalCalls > 20 && blockRatio > 0.05) {
91
- message = `${session.totalCalls} tool calls, ${session.blockedCalls} blocked (${(blockRatio * 100).toFixed(0)}%) — high block ratio, review for injection`;
92
- }
93
- else if (session.blockedCalls > 0 && session.totalCalls > 20) {
94
- message = `${session.totalCalls} tool calls, ${session.blockedCalls} blocked — long session, review if unattended`;
95
- }
96
- else if (session.totalCalls > 50) {
97
- message = `${session.totalCalls} tool calls in one session — high density, review if unattended`;
98
- }
99
- else if ((session.toolMix['Bash'] ?? 0) > 10) {
100
- message = `${session.toolMix['Bash']} Bash calls in one session — elevated tool-mix`;
101
- }
102
- else {
103
- message = `${session.totalCalls} calls, ${session.blockedCalls} blocked — worth a glance`;
104
- }
105
- alerts.push({
106
- sessionId: session.sessionId,
107
- message,
108
- callCount: session.totalCalls,
109
- blockedCount: session.blockedCalls,
110
- risk: risk === 'high' ? 'high' : 'medium',
111
- });
112
- }
113
- return alerts.slice(0, 5); // top 5 alerts
114
- }
115
- /**
116
- * Real coding tools — the ones a genuine host terminal session uses. Synthetic
117
- * audit-event types (WritePass.intent, pre_prompt, etc.) and seed fixtures only
118
- * carry these internal names, so requiring a real tool filters them out.
119
- */
120
- export const REAL_HOST_TOOLS = new Set([
121
- 'Bash',
122
- 'Edit',
123
- 'Write',
124
- 'MultiEdit',
125
- 'Read',
126
- 'Glob',
127
- 'Grep',
128
- 'Task',
129
- 'NotebookEdit',
130
- 'WebFetch',
131
- ]);
132
- /**
133
- * Is this a genuine host coding session worth surfacing — not a seed fixture or
134
- * a stale log entry? Requires at least one real coding tool and recent activity.
135
- * Surfacing stale/synthetic sessions as "running in your terminal" is the
136
- * screen-lie the no-screen-lies rule bans.
137
- */
138
- export function isRealHostSession(s, nowMs = Date.now(), recencyMs = 48 * 60 * 60 * 1000) {
139
- const hasRealTool = Object.keys(s.toolMix).some((t) => REAL_HOST_TOOLS.has(t));
140
- if (!hasRealTool)
141
- return false;
142
- const last = new Date(s.lastSeen).getTime();
143
- if (!Number.isFinite(last))
144
- return false;
145
- return last >= nowMs - recencyMs;
146
- }
147
- /**
148
- * Plain-English preview of a tool call for the host-session thread timeline.
149
- * The hook stores toolInput as a (sometimes truncated) JSON string. Extract the
150
- * meaningful field — the bash command, the file path — never dump raw JSON at
151
- * the user. Falls back to a regex when the JSON is cut off mid-string.
152
- */
153
- export function previewFromToolInput(toolInput, max = 140) {
154
- if (!toolInput)
155
- return '';
156
- let out;
157
- try {
158
- const inp = JSON.parse(toolInput);
159
- out = String(inp.command ?? inp.file_path ?? inp.path ?? inp.pattern ?? inp.url ?? '');
160
- }
161
- catch {
162
- const m = toolInput.match(/"(?:command|file_path|path|pattern|url)"\s*:\s*"((?:[^"\\]|\\.)*)/);
163
- out = m ? m[1].replace(/\\"/g, '"').replace(/\\n/g, ' ') : '';
164
- }
165
- return out.slice(0, max);
166
- }
167
- /**
168
- * Format session duration for display.
169
- */
170
- export function formatDuration(ms) {
171
- if (ms < 60_000)
172
- return `${Math.round(ms / 1000)}s`;
173
- if (ms < 3_600_000)
174
- return `${Math.round(ms / 60_000)}m`;
175
- return `${Math.round(ms / 3_600_000)}h`;
176
- }
177
- //# sourceMappingURL=session-analyzer.js.map
@@ -1,227 +0,0 @@
1
- /**
2
- * Infrastructure: Execution Tracer
3
- * Tracks every LLM call, tool invocation, and operation for reproducibility
4
- *
5
- * Clean Architecture: Infrastructure Layer
6
- * Strategic: Critical for production reliability and debugging
7
- */
8
- import { randomUUID } from 'crypto';
9
- import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
10
- import { join, dirname } from 'path';
11
- import { fileURLToPath } from 'url';
12
- import { getVersion } from '../../utils/version.js';
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
- /**
16
- * Execution Tracer
17
- * Tracks all operations for debugging and reproducibility
18
- */
19
- export class ExecutionTracer {
20
- currentRun = null;
21
- tracesDir;
22
- constructor() {
23
- // Store traces in agent/data/traces/
24
- const agentDir = join(__dirname, '../../..');
25
- this.tracesDir = join(agentDir, 'data', 'traces');
26
- if (!existsSync(this.tracesDir)) {
27
- mkdirSync(this.tracesDir, { recursive: true });
28
- }
29
- }
30
- /**
31
- * Start a new run context
32
- */
33
- startRun(operation, context) {
34
- const runId = randomUUID();
35
- this.currentRun = {
36
- runId,
37
- startTime: Date.now(),
38
- operation,
39
- query: context?.query,
40
- codebaseId: context?.codebaseId,
41
- userAgent: context?.userAgent || 'codehere-cli',
42
- version: getVersion(),
43
- traces: [],
44
- };
45
- return runId;
46
- }
47
- /**
48
- * End the current run and save to disk
49
- */
50
- endRun() {
51
- if (!this.currentRun) {
52
- return;
53
- }
54
- this.currentRun.endTime = Date.now();
55
- // Save run to disk
56
- const runFilePath = join(this.tracesDir, `${this.currentRun.runId}.json`);
57
- writeFileSync(runFilePath, JSON.stringify(this.currentRun, null, 2), 'utf-8');
58
- this.currentRun = null;
59
- }
60
- /**
61
- * Trace an operation
62
- */
63
- trace(operation) {
64
- if (!this.currentRun) {
65
- // If no run context, create a temporary one
66
- this.startRun('auto-trace');
67
- }
68
- const traceId = randomUUID();
69
- const startTime = Date.now();
70
- const trace = {
71
- traceId,
72
- runId: this.currentRun.runId,
73
- operationType: operation.operationType,
74
- operationName: operation.operationName,
75
- timestamp: startTime,
76
- duration: operation.duration,
77
- input: this.sanitizeInput(operation.input),
78
- output: this.sanitizeOutput(operation.output),
79
- error: operation.error
80
- ? {
81
- message: operation.error.message,
82
- stack: operation.error.stack,
83
- code: operation.error.code,
84
- }
85
- : undefined,
86
- metadata: operation.metadata,
87
- };
88
- this.currentRun.traces.push(trace);
89
- return traceId;
90
- }
91
- /**
92
- * Get run by ID (for reproducibility)
93
- */
94
- getRun(runId) {
95
- const runFilePath = join(this.tracesDir, `${runId}.json`);
96
- if (!existsSync(runFilePath)) {
97
- return null;
98
- }
99
- try {
100
- const content = readFileSync(runFilePath, 'utf-8');
101
- return JSON.parse(content);
102
- }
103
- catch (error) {
104
- console.error(`Failed to load run ${runId}:`, error);
105
- return null;
106
- }
107
- }
108
- /**
109
- * List recent runs
110
- */
111
- /**
112
- * Reproduce a run (replay operations in sequence)
113
- * Enhanced: Actually replays operations with state restoration
114
- */
115
- async reproduceRun(runId, options) {
116
- const run = this.getRun(runId);
117
- if (!run) {
118
- throw new Error(`Run ${runId} not found`);
119
- }
120
- // Create new run context for reproduction
121
- const reproductionRunId = this.startRun(`reproduce:${run.operation}`, {
122
- query: run.query,
123
- codebaseId: run.codebaseId,
124
- });
125
- // Log reproduction start
126
- console.log(`Reproducing run ${runId}...`);
127
- console.log(`Operation: ${run.operation}`);
128
- console.log(`Query: ${run.query || 'N/A'}`);
129
- console.log(`Traces: ${run.traces.length}`);
130
- console.log(`Step-by-step: ${options?.stepByStep || false}`);
131
- // Replay each trace in sequence
132
- for (let index = 0; index < run.traces.length; index++) {
133
- const trace = run.traces[index];
134
- console.log(`\n[${index + 1}/${run.traces.length}] ${trace.operationType}: ${trace.operationName}`);
135
- if (trace.error) {
136
- console.log(` ! Original error: ${trace.error.message}`);
137
- // Replay error trace
138
- this.trace({
139
- operationType: trace.operationType,
140
- operationName: trace.operationName,
141
- input: trace.input,
142
- output: trace.output,
143
- error: new Error(trace.error.message),
144
- metadata: trace.metadata,
145
- duration: trace.duration,
146
- });
147
- }
148
- else {
149
- // Replay successful trace
150
- this.trace({
151
- operationType: trace.operationType,
152
- operationName: trace.operationName,
153
- input: trace.input,
154
- output: trace.output,
155
- metadata: trace.metadata,
156
- duration: trace.duration,
157
- });
158
- // For tool invocations, attempt to replay if input/output available
159
- if (trace.operationType === 'tool_invocation' && trace.input && trace.output) {
160
- console.log(` ✓ Replayed: ${trace.operationName}`);
161
- if (trace.metadata?.filepath) {
162
- console.log(` File: ${trace.metadata.filepath}`);
163
- }
164
- }
165
- }
166
- // Step-by-step mode: wait for user confirmation
167
- if (options?.stepByStep && index < run.traces.length - 1) {
168
- // In a real CLI, we'd use readline to wait for user input
169
- // For now, just log that step-by-step is enabled
170
- console.log(` [Press Enter to continue to next step...]`);
171
- }
172
- }
173
- console.log(`\n✓ Reproduction complete. New run ID: ${reproductionRunId}`);
174
- this.endRun();
175
- }
176
- /**
177
- * Get current run ID
178
- */
179
- getCurrentRunId() {
180
- return this.currentRun?.runId || null;
181
- }
182
- /**
183
- * Sanitize input for storage (remove sensitive data, large objects)
184
- */
185
- sanitizeInput(input) {
186
- if (!input)
187
- return input;
188
- // Limit size and remove sensitive fields
189
- const sanitized = JSON.parse(JSON.stringify(input, (key, value) => {
190
- // Remove API keys and secrets
191
- if (typeof key === 'string' &&
192
- (key.toLowerCase().includes('key') ||
193
- key.toLowerCase().includes('secret') ||
194
- key.toLowerCase().includes('password'))) {
195
- return '[REDACTED]';
196
- }
197
- // Limit string length
198
- if (typeof value === 'string' && value.length > 10000) {
199
- return value.substring(0, 10000) + '...[truncated]';
200
- }
201
- // Limit array/object depth
202
- return value;
203
- }));
204
- // Limit total size
205
- const serialized = JSON.stringify(sanitized);
206
- if (serialized.length > 100000) {
207
- return { _truncated: true, _size: serialized.length };
208
- }
209
- return sanitized;
210
- }
211
- /**
212
- * Sanitize output for storage
213
- */
214
- sanitizeOutput(output) {
215
- if (!output)
216
- return output;
217
- // Limit size
218
- const serialized = JSON.stringify(output);
219
- if (serialized.length > 50000) {
220
- return { _truncated: true, _size: serialized.length, _preview: serialized.substring(0, 1000) };
221
- }
222
- return output;
223
- }
224
- }
225
- // Singleton instance
226
- export const executionTracer = new ExecutionTracer();
227
- //# sourceMappingURL=execution-tracer.js.map
@@ -1,13 +0,0 @@
1
- /**
2
- * AgentAdapter — F2 foundation.
3
- *
4
- * Uniform interface over every AI coding agent codehere can spawn and manage.
5
- * One adapter per backend: ClaudeCodeAdapter, CodexAdapter, AiderAdapter, …
6
- *
7
- * The interface intentionally carries **optional fields for later PM-UX layers**
8
- * so we don't force a breaking change when v0.4/v0.5 ship progress roll-up,
9
- * final report, intervention, and project scope. Unused today, ignored by the
10
- * current renderer. See memory/project_manager_ux_v1.md for the full shape.
11
- */
12
- export {};
13
- //# sourceMappingURL=agent-adapter.js.map