codehere 0.2.2 → 0.3.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -68
- package/dist/domain/entities/task.js +4 -4
- package/dist/error-handler.js +4 -9
- package/dist/index.js +261 -1224
- package/dist/infrastructure/config/product-context.js +7 -6
- package/dist/infrastructure/hooks/claude-sdk-hook.js +348 -72
- package/dist/infrastructure/observability/execution-tracer.js +11 -6
- package/dist/infrastructure/orchestration/agent-adapter.js +13 -0
- package/dist/infrastructure/orchestration/agent-installer.js +101 -0
- package/dist/infrastructure/orchestration/agents-config-seed.js +56 -0
- package/dist/infrastructure/orchestration/aider-adapter.js +319 -0
- package/dist/infrastructure/orchestration/artifacts-extractor.js +175 -0
- package/dist/infrastructure/orchestration/claude-code-adapter.js +352 -0
- package/dist/infrastructure/orchestration/codex-adapter.js +310 -0
- package/dist/infrastructure/orchestration/conductor-adapter.js +340 -0
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +553 -0
- package/dist/infrastructure/orchestration/opencode-adapter.js +275 -0
- package/dist/infrastructure/orchestration/progress-aggregator.js +189 -0
- package/dist/infrastructure/orchestration/readiness.js +106 -0
- package/dist/infrastructure/orchestration/session-manager.js +278 -0
- package/dist/infrastructure/orchestration/session-persistence.js +109 -0
- package/dist/infrastructure/protocols/mcp-helpers.js +10 -30
- package/dist/infrastructure/protocols/mcp-server-impl.js +359 -21
- package/dist/infrastructure/protocols/probe-port.js +36 -0
- package/dist/infrastructure/protocols/ui-page.js +1827 -0
- package/dist/infrastructure/security/file-collection.js +115 -0
- package/dist/infrastructure/security/trust-scorer.js +301 -56
- package/dist/infrastructure/storage/task-repository.js +8 -7
- package/dist/monitoring.js +9 -38
- package/dist/presentation/cli/commands/help-command.js +96 -139
- package/dist/presentation/cli/commands/hook-command.js +17 -24
- package/dist/presentation/cli/commands/report-command.js +59 -72
- package/dist/presentation/cli/commands/setup-command.js +4 -4
- package/dist/presentation/cli/commands/status-command.js +321 -30
- package/dist/presentation/cli/commands/trust-command.js +84 -72
- package/dist/presentation/cli/error-display.js +17 -21
- package/dist/presentation/cli/repl.js +474 -0
- package/dist/ui.js +11 -11
- package/package.json +24 -62
- package/dist/agents/base.js +0 -101
- package/dist/agents/orchestrator-model-driven.js +0 -141
- package/dist/agents/react-orchestrator-agent.js +0 -54
- package/dist/application/agents/execution-agent.js +0 -508
- package/dist/application/agents/multi-agent-orchestrator.js +0 -295
- package/dist/application/agents/planning-agent.js +0 -433
- package/dist/application/agents/validation-agent.js +0 -317
- package/dist/application/architectures/plan-and-act.js +0 -242
- package/dist/application/index.js +0 -12
- package/dist/application/services/agent-factory.js +0 -28
- package/dist/application/services/dependency-container.js +0 -596
- package/dist/application/services/git-context-service.js +0 -83
- package/dist/application/services/intelligent-router.js +0 -934
- package/dist/application/services/tool-executor-service.js +0 -94
- package/dist/application/use-cases/ask-question-use-case.js +0 -338
- package/dist/application/use-cases/edit-file-use-case.js +0 -830
- package/dist/application/use-cases/explain-file-use-case.js +0 -50
- package/dist/application/use-cases/index-codebase-use-case.js +0 -113
- package/dist/application/use-cases/planning-use-case.js +0 -276
- package/dist/application/use-cases/react-orchestration-use-case.js +0 -329
- package/dist/application/use-cases/reflexion-use-case.js +0 -30
- package/dist/application/workflows/deterministic-workflows.js +0 -189
- package/dist/ast-parser.js +0 -331
- package/dist/cache.js +0 -180
- package/dist/chat.js +0 -251
- package/dist/code-analysis.js +0 -327
- package/dist/context.js +0 -187
- package/dist/docs.js +0 -146
- package/dist/domain/entities/code-chunk.js +0 -6
- package/dist/domain/entities/decision.js +0 -9
- package/dist/domain/entities/edit.js +0 -6
- package/dist/domain/entities/knowledge-node.js +0 -9
- package/dist/domain/entities/knowledge-preservation.js +0 -10
- package/dist/domain/entities/memory.js +0 -6
- package/dist/domain/entities/pattern.js +0 -7
- package/dist/domain/entities/plan.js +0 -6
- package/dist/domain/entities/query.js +0 -6
- package/dist/domain/errors/embeddings-not-found-error.js +0 -13
- package/dist/domain/index.js +0 -19
- package/dist/domain/interfaces/ai-service.interface.js +0 -6
- package/dist/domain/interfaces/code-analyzer.interface.js +0 -6
- package/dist/domain/interfaces/decision-repository.interface.js +0 -9
- package/dist/domain/interfaces/embedding-repository.interface.js +0 -6
- package/dist/domain/interfaces/knowledge-graph-repository.interface.js +0 -9
- package/dist/domain/interfaces/knowledge-preservation-repository.interface.js +0 -8
- package/dist/domain/interfaces/memory-repository.interface.js +0 -6
- package/dist/domain/interfaces/pattern-repository.interface.js +0 -6
- package/dist/domain/interfaces/verification-service.interface.js +0 -6
- package/dist/domain/services/cross-team-pattern-sharing.js +0 -125
- package/dist/domain/services/decision-recorder.js +0 -223
- package/dist/domain/services/decision-retriever.js +0 -133
- package/dist/domain/services/environment-mode-manager.js +0 -22
- package/dist/domain/services/graded-memory-activation.js +0 -69
- package/dist/domain/services/knowledge-access.js +0 -132
- package/dist/domain/services/knowledge-extractor.js +0 -247
- package/dist/domain/services/knowledge-graph-builder.js +0 -268
- package/dist/domain/services/knowledge-graph-query.js +0 -187
- package/dist/domain/services/memory-retriever.js +0 -82
- package/dist/domain/services/memory-synthesizer.js +0 -91
- package/dist/domain/services/pattern-extractor.js +0 -442
- package/dist/domain/services/query-intent-classifier.js +0 -140
- package/dist/domain/services/react-loop.js +0 -186
- package/dist/domain/services/risk-tier-detector.js +0 -78
- package/dist/edit.js +0 -319
- package/dist/embed.js +0 -459
- package/dist/formatter.js +0 -168
- package/dist/git.js +0 -198
- package/dist/infrastructure/ai/cohere-ai-service.js +0 -607
- package/dist/infrastructure/ai/command-r-evaluation.js +0 -206
- package/dist/infrastructure/ai/model-config.js +0 -84
- package/dist/infrastructure/ai/provider-ai-service.js +0 -477
- package/dist/infrastructure/cache/embedding-cache.js +0 -175
- package/dist/infrastructure/cache/query-result-cache.js +0 -66
- package/dist/infrastructure/cache/response-cache.js +0 -48
- package/dist/infrastructure/cache/security-scan-cache.js +0 -47
- package/dist/infrastructure/chunking/ast-code-chunker.js +0 -19
- package/dist/infrastructure/chunking/code-aware-chunker.js +0 -397
- package/dist/infrastructure/context/context-compressor.js +0 -329
- package/dist/infrastructure/context/conversation-context.js +0 -128
- package/dist/infrastructure/context/session-manager.js +0 -94
- package/dist/infrastructure/cost/cost-tracker.js +0 -360
- package/dist/infrastructure/file-scanner/file-scanner.js +0 -106
- package/dist/infrastructure/governance/nist-ai-rmf.js +0 -292
- package/dist/infrastructure/governance/policy-as-code.js +0 -258
- package/dist/infrastructure/governance/prompt-to-code-lineage.js +0 -239
- package/dist/infrastructure/index.js +0 -7
- package/dist/infrastructure/observability/prompt-versioner.js +0 -145
- package/dist/infrastructure/protocols/a2a-metadata.js +0 -137
- package/dist/infrastructure/protocols/a2a-protocol.js +0 -314
- package/dist/infrastructure/protocols/mcp-oauth.js +0 -184
- package/dist/infrastructure/protocols/mcp-server.js +0 -333
- package/dist/infrastructure/protocols/streaming-handler.js +0 -103
- package/dist/infrastructure/providers/cohere-provider.js +0 -444
- package/dist/infrastructure/providers/fake-provider.js +0 -130
- package/dist/infrastructure/providers/local-llm-provider.js +0 -330
- package/dist/infrastructure/providers/model-config.types.js +0 -9
- package/dist/infrastructure/providers/model-provider.interface.js +0 -8
- package/dist/infrastructure/providers/openai-provider.js +0 -413
- package/dist/infrastructure/providers/openrouter-provider.js +0 -399
- package/dist/infrastructure/providers/operation-provider-resolver.js +0 -105
- package/dist/infrastructure/providers/provider-config-loader.js +0 -120
- package/dist/infrastructure/providers/provider-registry.js +0 -88
- package/dist/infrastructure/safety/pattern-detector.js +0 -133
- package/dist/infrastructure/security/ai-sast-scanner.js +0 -290
- package/dist/infrastructure/security/docker-sandbox.js +0 -178
- package/dist/infrastructure/security/enhanced-security-gate.js +0 -151
- package/dist/infrastructure/security/license-scanner.js +0 -167
- package/dist/infrastructure/security/provider-bias-detector.js +0 -164
- package/dist/infrastructure/security/sandbox-executor.js +0 -64
- package/dist/infrastructure/serialization/toon-serializer.js +0 -119
- package/dist/infrastructure/storage/memory-stream-service.js +0 -204
- package/dist/infrastructure/storage/plan-repository.js +0 -96
- package/dist/infrastructure/storage/sqlite-decision-repository.js +0 -232
- package/dist/infrastructure/storage/sqlite-embedding-repository.js +0 -311
- package/dist/infrastructure/storage/sqlite-knowledge-graph-repository.js +0 -417
- package/dist/infrastructure/storage/sqlite-knowledge-preservation-repository.js +0 -230
- package/dist/infrastructure/storage/sqlite-memory-repository.js +0 -229
- package/dist/infrastructure/storage/sqlite-pattern-repository.js +0 -249
- package/dist/infrastructure/storage/task-helpers.js +0 -197
- package/dist/infrastructure/telemetry/telemetry-service.js +0 -167
- package/dist/infrastructure/ux/appropriate-friction.js +0 -213
- package/dist/infrastructure/ux/bi-directional-learning.js +0 -337
- package/dist/infrastructure/ux/capability-boundaries.js +0 -42
- package/dist/infrastructure/ux/comprehensive-formatter.js +0 -117
- package/dist/infrastructure/ux/contextual-feature-discovery.js +0 -10
- package/dist/infrastructure/ux/expectation-management.js +0 -327
- package/dist/infrastructure/ux/feature-discovery.js +0 -14
- package/dist/infrastructure/ux/feedback-prompt.js +0 -45
- package/dist/infrastructure/ux/hitl-validation.js +0 -297
- package/dist/infrastructure/ux/progress-indicator.js +0 -116
- package/dist/infrastructure/ux/progressive-disclosure.js +0 -236
- package/dist/infrastructure/ux/review-checkpoint.js +0 -72
- package/dist/infrastructure/ux/staged-feature-intro.js +0 -24
- package/dist/infrastructure/ux/stream-markdown.js +0 -151
- package/dist/infrastructure/ux/syntax-highlighter.js +0 -91
- package/dist/infrastructure/ux/team-standards.js +0 -196
- package/dist/infrastructure/ux/transparency.js +0 -71
- package/dist/infrastructure/validation/agent-feedback.js +0 -55
- package/dist/infrastructure/validation/agent-validation-helper.js +0 -137
- package/dist/infrastructure/validation/review-handler.js +0 -40
- package/dist/infrastructure/validation/summary-aggregator.js +0 -70
- package/dist/infrastructure/validation/summary-extractor.js +0 -45
- package/dist/infrastructure/validation/trace-summary.js +0 -52
- package/dist/infrastructure/verification/verification-service.js +0 -47
- package/dist/infrastructure/xai/attention-visualizer.js +0 -172
- package/dist/infrastructure/xai/cot-visualizer.js +0 -283
- package/dist/infrastructure/xai/decision-tree-log.js +0 -212
- package/dist/infrastructure/xai/interactive-xai.js +0 -260
- package/dist/infrastructure/xai/uncertainty-quantifier.js +0 -198
- package/dist/intelligent-retrieval.js +0 -272
- package/dist/intent/classifier.js +0 -2
- package/dist/intent/context.js +0 -2
- package/dist/iterative-refinement.js +0 -189
- package/dist/learning.js +0 -232
- package/dist/log.js +0 -31
- package/dist/migrate.js +0 -133
- package/dist/parallel-processor.js +0 -534
- package/dist/planner.js +0 -198
- package/dist/policy.js +0 -26
- package/dist/presentation/cli/commands/ask-command.js +0 -314
- package/dist/presentation/cli/commands/cost-command.js +0 -191
- package/dist/presentation/cli/commands/decision-command.js +0 -207
- package/dist/presentation/cli/commands/docs-command.js +0 -25
- package/dist/presentation/cli/commands/fix-command.js +0 -53
- package/dist/presentation/cli/commands/knowledge-command.js +0 -129
- package/dist/presentation/cli/commands/learning-dashboard-command.js +0 -183
- package/dist/presentation/cli/commands/models-command.js +0 -194
- package/dist/presentation/cli/commands/orchestrate-command.js +0 -207
- package/dist/presentation/cli/commands/plan-command.js +0 -246
- package/dist/presentation/cli/commands/product-command.js +0 -199
- package/dist/presentation/cli/commands/react-command.js +0 -181
- package/dist/presentation/cli/commands/review-command.js +0 -154
- package/dist/presentation/cli/commands/run-command.js +0 -188
- package/dist/presentation/cli/commands/smart-command.js +0 -395
- package/dist/presentation/cli/commands/suggest-command.js +0 -138
- package/dist/presentation/cli/commands/trace-command.js +0 -129
- package/dist/presentation/cli/commands/undo-command.js +0 -150
- package/dist/query-optimizer.js +0 -205
- package/dist/refactor.js +0 -116
- package/dist/review.js +0 -206
- package/dist/search.js +0 -163
- package/dist/session.js +0 -704
- package/dist/task-verification.js +0 -336
- package/dist/testgen.js +0 -166
- package/dist/token-optimizer.js +0 -277
- package/dist/tools/bash.js +0 -213
- package/dist/tools/edit.js +0 -235
- package/dist/tools/executor.js +0 -153
- package/dist/tools/git-tool.js +0 -292
- package/dist/tools/parser.js +0 -158
- package/dist/tools/registry.js +0 -302
- package/dist/tools/search.js +0 -100
- package/dist/types.js +0 -2
- package/dist/utils/check-api-status.js +0 -100
- package/dist/utils/diff-parser.js +0 -162
- package/dist/verification/backup.js +0 -259
- package/dist/verification/compiler.js +0 -264
- package/dist/verification/index.js +0 -273
- package/dist/verification/test-runner.js +0 -379
|
@@ -16,21 +16,23 @@ import { colors, newline, sectionHeader } from '../../../ui.js';
|
|
|
16
16
|
import { getVersion } from '../../../utils/version.js';
|
|
17
17
|
import { loadProductContext } from '../../../infrastructure/config/product-context.js';
|
|
18
18
|
import { AUDIT_FILE } from '../../../infrastructure/hooks/claude-sdk-hook.js';
|
|
19
|
-
import { analyzeSessions, detectAmplificationAlerts, formatDuration } from '../../../infrastructure/hooks/session-analyzer.js';
|
|
19
|
+
import { analyzeSessions, detectAmplificationAlerts, formatDuration, } from '../../../infrastructure/hooks/session-analyzer.js';
|
|
20
20
|
function readAuditLog(maxLines = 5000) {
|
|
21
21
|
if (!existsSync(AUDIT_FILE))
|
|
22
22
|
return [];
|
|
23
23
|
try {
|
|
24
24
|
const lines = readFileSync(AUDIT_FILE, 'utf-8').split('\n').filter(Boolean);
|
|
25
25
|
const recent = lines.slice(-maxLines);
|
|
26
|
-
return recent
|
|
26
|
+
return recent
|
|
27
|
+
.map((l) => {
|
|
27
28
|
try {
|
|
28
29
|
return JSON.parse(l);
|
|
29
30
|
}
|
|
30
31
|
catch {
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
33
|
-
})
|
|
34
|
+
})
|
|
35
|
+
.filter(Boolean);
|
|
34
36
|
}
|
|
35
37
|
catch {
|
|
36
38
|
return [];
|
|
@@ -69,8 +71,12 @@ function extractBlockReason(entry) {
|
|
|
69
71
|
return parts.slice(-2).join('/');
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
|
-
catch {
|
|
74
|
+
catch {
|
|
75
|
+
/* parse failed — truncated JSON, try regex */
|
|
76
|
+
}
|
|
73
77
|
// Fallback: extract file_path or command from truncated JSON via regex
|
|
78
|
+
if (!entry.toolInput)
|
|
79
|
+
return 'tool call';
|
|
74
80
|
const fpMatch = entry.toolInput.match(/"file_path"\s*:\s*"([^"]+)"/);
|
|
75
81
|
if (fpMatch) {
|
|
76
82
|
const parts = fpMatch[1].split('/');
|
|
@@ -84,15 +90,15 @@ function extractBlockReason(entry) {
|
|
|
84
90
|
// ── main command ──────────────────────────────────────────────────────────────
|
|
85
91
|
// ── session drill-down ────────────────────────────────────────────────────────
|
|
86
92
|
function renderSessionDrillDown(sessionId, entries) {
|
|
87
|
-
const sessionEntries = entries.filter(e => e.sessionId === sessionId || e.sessionId?.startsWith(sessionId));
|
|
93
|
+
const sessionEntries = entries.filter((e) => e.sessionId === sessionId || e.sessionId?.startsWith(sessionId));
|
|
88
94
|
if (sessionEntries.length === 0) {
|
|
89
95
|
console.log(colors.yellow(`No entries found for session: ${sessionId}`));
|
|
90
96
|
console.log(colors.dim(' Tip: use the first 8+ characters of a session ID from `codehere status --sessions`'));
|
|
91
97
|
return;
|
|
92
98
|
}
|
|
93
99
|
const sorted = [...sessionEntries].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
94
|
-
const blocked = sorted.filter(e => e.blocked);
|
|
95
|
-
const critical = sorted.filter(e => e.criticalFindings > 0);
|
|
100
|
+
const blocked = sorted.filter((e) => e.blocked);
|
|
101
|
+
const critical = sorted.filter((e) => e.criticalFindings > 0);
|
|
96
102
|
const durationMs = new Date(sorted[sorted.length - 1].timestamp).getTime() - new Date(sorted[0].timestamp).getTime();
|
|
97
103
|
const toolCounts = {};
|
|
98
104
|
for (const e of sorted) {
|
|
@@ -157,6 +163,252 @@ function renderSessionDrillDown(sessionId, entries) {
|
|
|
157
163
|
}
|
|
158
164
|
newline();
|
|
159
165
|
}
|
|
166
|
+
function extractFilePath(entry) {
|
|
167
|
+
if (!entry.toolInput)
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
const input = JSON.parse(entry.toolInput);
|
|
171
|
+
if (input.file_path)
|
|
172
|
+
return String(input.file_path);
|
|
173
|
+
if (Array.isArray(input.edits) && input.edits[0]?.file_path)
|
|
174
|
+
return String(input.edits[0].file_path);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
const m = entry.toolInput.match(/"file_path"\s*:\s*"([^"]+)"/);
|
|
178
|
+
if (m)
|
|
179
|
+
return m[1];
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
function shortReason(entry, maxLen = 60) {
|
|
184
|
+
const r = extractBlockReason(entry);
|
|
185
|
+
return r.length <= maxLen ? r : r.substring(0, maxLen - 1) + '…';
|
|
186
|
+
}
|
|
187
|
+
const BLOCK_CATEGORY_LABEL = {
|
|
188
|
+
'self-test': "codehere's own canary tests",
|
|
189
|
+
'cache-cleanup': 'routine cache/tmp deletion',
|
|
190
|
+
'content-match': 'editing files that mention dangerous patterns (docs/tests)',
|
|
191
|
+
secret: 'potential credential in content',
|
|
192
|
+
destructive: 'destructive command (rm -rf, force push)',
|
|
193
|
+
injection: 'code/command injection pattern',
|
|
194
|
+
other: 'uncategorized',
|
|
195
|
+
};
|
|
196
|
+
function classifyBlock(entry) {
|
|
197
|
+
const raw = entry.toolInput.toLowerCase();
|
|
198
|
+
// Codehere's own test canaries (seeing these means someone is running our tests)
|
|
199
|
+
if (raw.includes('rm -rf /home/user') || raw.includes('evil.com')) {
|
|
200
|
+
return 'self-test';
|
|
201
|
+
}
|
|
202
|
+
// rm -rf against caches/tmp/build artifacts — real cleanup, not a threat
|
|
203
|
+
if (raw.includes('rm -rf') &&
|
|
204
|
+
(raw.includes('/.cache/') ||
|
|
205
|
+
raw.includes('library/caches/') ||
|
|
206
|
+
raw.includes('/tmp/') ||
|
|
207
|
+
raw.includes('node_modules') ||
|
|
208
|
+
raw.includes('/dist/') ||
|
|
209
|
+
raw.includes('/.next/') ||
|
|
210
|
+
raw.includes('/build/'))) {
|
|
211
|
+
return 'cache-cleanup';
|
|
212
|
+
}
|
|
213
|
+
// Edit/Write into docs/source that merely mentions dangerous patterns — content match FP
|
|
214
|
+
if (entry.toolName === 'Edit' || entry.toolName === 'MultiEdit' || entry.toolName === 'Write') {
|
|
215
|
+
const fp = extractFilePath(entry) || '';
|
|
216
|
+
if (/\.(md|tsx?|jsx?|html|rst|txt|ya?ml|json)$/i.test(fp)) {
|
|
217
|
+
return 'content-match';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Destructive (not caught as cleanup above — so targeting user data)
|
|
221
|
+
if (raw.includes('rm -rf') || raw.includes('--force') || raw.includes('force push')) {
|
|
222
|
+
return 'destructive';
|
|
223
|
+
}
|
|
224
|
+
// Secret-in-content
|
|
225
|
+
if (/api_key|apikey|auth_token|bearer\s|password\s*[:=]|secret\s*[:=]|sk-[a-z0-9]{20,}/i.test(raw)) {
|
|
226
|
+
return 'secret';
|
|
227
|
+
}
|
|
228
|
+
// Code/command injection
|
|
229
|
+
if (raw.includes('eval(') || raw.includes('child_process.exec') || raw.includes('innerhtml')) {
|
|
230
|
+
return 'injection';
|
|
231
|
+
}
|
|
232
|
+
return 'other';
|
|
233
|
+
}
|
|
234
|
+
function formatDateRange(fromMs, toMs) {
|
|
235
|
+
const f = new Date(fromMs);
|
|
236
|
+
const t = new Date(toMs);
|
|
237
|
+
const opt = { month: 'short', day: 'numeric' };
|
|
238
|
+
return `${f.toLocaleDateString('en-US', opt)} – ${t.toLocaleDateString('en-US', opt)}`;
|
|
239
|
+
}
|
|
240
|
+
function renderWeeklyRecap(entries, jsonOut) {
|
|
241
|
+
const weekMs = 7 * 24 * 3600 * 1000;
|
|
242
|
+
const now = Date.now();
|
|
243
|
+
const windowStart = now - weekMs;
|
|
244
|
+
const week = entries.filter((e) => new Date(e.timestamp).getTime() >= windowStart);
|
|
245
|
+
// Aggregates
|
|
246
|
+
const sessions = new Set(week.map((e) => e.sessionId).filter(Boolean));
|
|
247
|
+
const writes = week.filter((e) => e.toolName === 'Write');
|
|
248
|
+
const edits = week.filter((e) => e.toolName === 'Edit' || e.toolName === 'MultiEdit');
|
|
249
|
+
const bash = week.filter((e) => e.toolName === 'Bash');
|
|
250
|
+
const blocks = week.filter((e) => e.blocked);
|
|
251
|
+
const fileStats = {};
|
|
252
|
+
for (const e of [...writes, ...edits]) {
|
|
253
|
+
const fp = extractFilePath(e);
|
|
254
|
+
if (!fp)
|
|
255
|
+
continue;
|
|
256
|
+
if (!fileStats[fp])
|
|
257
|
+
fileStats[fp] = { path: fp, edits: 0, writes: 0 };
|
|
258
|
+
if (e.toolName === 'Write')
|
|
259
|
+
fileStats[fp].writes++;
|
|
260
|
+
else
|
|
261
|
+
fileStats[fp].edits++;
|
|
262
|
+
}
|
|
263
|
+
const topFiles = Object.values(fileStats)
|
|
264
|
+
.sort((a, b) => b.edits + b.writes - (a.edits + a.writes))
|
|
265
|
+
.slice(0, 5);
|
|
266
|
+
const uniqueFiles = Object.keys(fileStats).length;
|
|
267
|
+
// Classify blocks — most of the 18-blocks headline is noise, users deserve to see which kind
|
|
268
|
+
const blockCategories = {
|
|
269
|
+
'self-test': 0,
|
|
270
|
+
'cache-cleanup': 0,
|
|
271
|
+
'content-match': 0,
|
|
272
|
+
secret: 0,
|
|
273
|
+
destructive: 0,
|
|
274
|
+
injection: 0,
|
|
275
|
+
other: 0,
|
|
276
|
+
};
|
|
277
|
+
for (const b of blocks) {
|
|
278
|
+
blockCategories[classifyBlock(b)]++;
|
|
279
|
+
}
|
|
280
|
+
const blockBreakdown = Object.entries(blockCategories)
|
|
281
|
+
.filter(([, n]) => n > 0)
|
|
282
|
+
.sort((a, b) => b[1] - a[1]);
|
|
283
|
+
const shortPath = (p) => {
|
|
284
|
+
const parts = p.split('/').filter(Boolean);
|
|
285
|
+
return parts.slice(-2).join('/');
|
|
286
|
+
};
|
|
287
|
+
if (jsonOut) {
|
|
288
|
+
console.log(JSON.stringify({
|
|
289
|
+
range: { from: new Date(windowStart).toISOString(), to: new Date(now).toISOString() },
|
|
290
|
+
sessions: sessions.size,
|
|
291
|
+
toolCalls: week.length,
|
|
292
|
+
writes: writes.length,
|
|
293
|
+
edits: edits.length,
|
|
294
|
+
bashCommands: bash.length,
|
|
295
|
+
uniqueFiles,
|
|
296
|
+
blocks: blocks.length,
|
|
297
|
+
topFiles: topFiles.map((f) => ({ file: shortPath(f.path), edits: f.edits, writes: f.writes })),
|
|
298
|
+
blockBreakdown: blockBreakdown.map(([category, count]) => ({
|
|
299
|
+
category,
|
|
300
|
+
count,
|
|
301
|
+
label: BLOCK_CATEGORY_LABEL[category],
|
|
302
|
+
})),
|
|
303
|
+
}, null, 2));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const pad = (s, n) => (s.length >= n ? s : s + ' '.repeat(n - s.length));
|
|
307
|
+
const num = (n) => colors.bold(String(n).padStart(6));
|
|
308
|
+
sectionHeader(`codehere weekly · ${formatDateRange(windowStart, now)}`);
|
|
309
|
+
newline();
|
|
310
|
+
if (week.length === 0) {
|
|
311
|
+
console.log(colors.dim(' No activity in the last 7 days.'));
|
|
312
|
+
console.log(colors.dim(' Run Claude Code with the codehere hook installed to start tracking.'));
|
|
313
|
+
newline();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// Headline numbers
|
|
317
|
+
console.log(` ${colors.bold(String(sessions.size))} session${sessions.size === 1 ? '' : 's'} · ${colors.bold(week.length.toLocaleString())} tool call${week.length === 1 ? '' : 's'} scanned · ${blocks.length > 0 ? colors.yellow(String(blocks.length)) : colors.dim('0')} block${blocks.length === 1 ? '' : 's'}`);
|
|
318
|
+
newline();
|
|
319
|
+
// Activity breakdown
|
|
320
|
+
console.log(colors.dim(' ACTIVITY'));
|
|
321
|
+
console.log(` ${num(writes.length)} new files written`);
|
|
322
|
+
console.log(` ${num(edits.length)} edits to existing files`);
|
|
323
|
+
console.log(` ${num(bash.length)} shell commands`);
|
|
324
|
+
console.log(` ${num(uniqueFiles)} unique files touched`);
|
|
325
|
+
newline();
|
|
326
|
+
// Top files
|
|
327
|
+
if (topFiles.length > 0) {
|
|
328
|
+
console.log(colors.dim(' TOP FILES'));
|
|
329
|
+
for (const f of topFiles) {
|
|
330
|
+
const count = f.edits + f.writes;
|
|
331
|
+
const kind = f.writes > 0 && f.edits === 0 ? 'write' : f.edits > 0 && f.writes === 0 ? 'edit' : 'touch';
|
|
332
|
+
const plural = count === 1 ? '' : kind === 'touch' ? 'es' : 's';
|
|
333
|
+
const countStr = String(count).padStart(3);
|
|
334
|
+
const kindStr = `${kind}${plural}`.padEnd(7);
|
|
335
|
+
console.log(` ${colors.dim(countStr)} ${colors.dim(kindStr)} ${shortPath(f.path)}`);
|
|
336
|
+
}
|
|
337
|
+
newline();
|
|
338
|
+
}
|
|
339
|
+
// Blocks — categorized so users see signal vs noise at a glance
|
|
340
|
+
if (blocks.length > 0) {
|
|
341
|
+
console.log(colors.dim(` BLOCKS ${blocks.length} total`));
|
|
342
|
+
for (const [category, count] of blockBreakdown) {
|
|
343
|
+
const categoryColor = category === 'other' || category === 'destructive' || category === 'secret' || category === 'injection'
|
|
344
|
+
? colors.yellow
|
|
345
|
+
: colors.dim;
|
|
346
|
+
const countStr = String(count).padStart(3);
|
|
347
|
+
console.log(` ${colors.dim(countStr)} ${categoryColor(pad(category, 15))} ${colors.dim(BLOCK_CATEGORY_LABEL[category])}`);
|
|
348
|
+
}
|
|
349
|
+
newline();
|
|
350
|
+
console.log(colors.dim(' Drill in: codehere status --sessions'));
|
|
351
|
+
newline();
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
console.log(` ${colors.green('✓')} Nothing blocked this week.`);
|
|
355
|
+
newline();
|
|
356
|
+
}
|
|
357
|
+
console.log(colors.dim(' ─────'));
|
|
358
|
+
console.log(colors.dim(' codehere status --weekly --json Export for sharing'));
|
|
359
|
+
newline();
|
|
360
|
+
}
|
|
361
|
+
// ── audit export ─────────────────────────────────────────────────────────────
|
|
362
|
+
function parseDurationToHours(s) {
|
|
363
|
+
const m = /^(\d+)([hdw])$/.exec(s.trim());
|
|
364
|
+
if (!m)
|
|
365
|
+
return null;
|
|
366
|
+
const n = parseInt(m[1], 10);
|
|
367
|
+
switch (m[2]) {
|
|
368
|
+
case 'h':
|
|
369
|
+
return n;
|
|
370
|
+
case 'd':
|
|
371
|
+
return n * 24;
|
|
372
|
+
case 'w':
|
|
373
|
+
return n * 24 * 7;
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
function exportAudit(entries, format, sinceHours) {
|
|
378
|
+
const filtered = sinceHours != null ? entries.filter((e) => withinHours(e.timestamp, sinceHours)) : entries;
|
|
379
|
+
if (format === 'jsonl') {
|
|
380
|
+
for (const e of filtered)
|
|
381
|
+
console.log(JSON.stringify(e));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (format === 'csv') {
|
|
385
|
+
console.log('timestamp,sessionId,toolName,riskScore,blocked,findings,criticalFindings');
|
|
386
|
+
for (const e of filtered) {
|
|
387
|
+
const row = [e.timestamp, e.sessionId, e.toolName, e.riskScore, e.blocked, e.findings, e.criticalFindings].join(',');
|
|
388
|
+
console.log(row);
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (format === 'json') {
|
|
393
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (format === 'md') {
|
|
397
|
+
console.log(`# codehere audit log · ${filtered.length} events`);
|
|
398
|
+
console.log('');
|
|
399
|
+
if (sinceHours != null)
|
|
400
|
+
console.log(`*Window: last ${sinceHours} hour${sinceHours === 1 ? '' : 's'}*\n`);
|
|
401
|
+
console.log('| Timestamp | Session | Tool | Risk | Blocked | Findings |');
|
|
402
|
+
console.log('|---|---|---|---:|:---:|---:|');
|
|
403
|
+
for (const e of filtered) {
|
|
404
|
+
const sid = (e.sessionId || '').substring(0, 8);
|
|
405
|
+
const blocked = e.blocked ? '✗' : '✓';
|
|
406
|
+
console.log(`| \`${e.timestamp}\` | \`${sid}\` | ${e.toolName} | ${e.riskScore} | ${blocked} | ${e.findings} |`);
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
throw new Error(`Unsupported export format: ${format}. Use jsonl, json, csv, or md.`);
|
|
411
|
+
}
|
|
160
412
|
export async function executeStatusCommand(options = {}) {
|
|
161
413
|
const cwd = process.cwd();
|
|
162
414
|
const version = getVersion();
|
|
@@ -166,12 +418,33 @@ export async function executeStatusCommand(options = {}) {
|
|
|
166
418
|
renderSessionDrillDown(options.session, entries);
|
|
167
419
|
return;
|
|
168
420
|
}
|
|
169
|
-
|
|
170
|
-
|
|
421
|
+
// Audit export mode — dump raw events for archival/compliance/sharing
|
|
422
|
+
if (options.export) {
|
|
423
|
+
const sinceHours = options.since ? parseDurationToHours(options.since) : null;
|
|
424
|
+
if (options.since && sinceHours == null) {
|
|
425
|
+
console.error(`Invalid --since value: "${options.since}". Use e.g. 24h, 7d, 4w.`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
exportAudit(entries, options.export, sinceHours);
|
|
430
|
+
}
|
|
431
|
+
catch (e) {
|
|
432
|
+
console.error(e.message);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// Weekly recap mode — shareable agent-activity summary
|
|
438
|
+
if (options.weekly) {
|
|
439
|
+
renderWeeklyRecap(entries, options.json);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const last24h = entries.filter((e) => withinHours(e.timestamp, 24));
|
|
443
|
+
const last7d = entries.filter((e) => withinHours(e.timestamp, 24 * 7));
|
|
171
444
|
const stats = (subset) => ({
|
|
172
445
|
total: subset.length,
|
|
173
|
-
blocked: subset.filter(e => e.blocked).length,
|
|
174
|
-
critical: subset.filter(e => e.criticalFindings > 0).length,
|
|
446
|
+
blocked: subset.filter((e) => e.blocked).length,
|
|
447
|
+
critical: subset.filter((e) => e.criticalFindings > 0).length,
|
|
175
448
|
});
|
|
176
449
|
const s24 = stats(last24h);
|
|
177
450
|
const s7d = stats(last7d);
|
|
@@ -196,9 +469,14 @@ export async function executeStatusCommand(options = {}) {
|
|
|
196
469
|
const { getTaskRepository } = await import('../../../infrastructure/storage/task-repository.js');
|
|
197
470
|
const taskRepo = getTaskRepository();
|
|
198
471
|
const tasks = await taskRepo.getAll();
|
|
199
|
-
result.tasks = tasks.slice(0, 20).map(t => ({
|
|
200
|
-
id: t.id,
|
|
201
|
-
|
|
472
|
+
result.tasks = tasks.slice(0, 20).map((t) => ({
|
|
473
|
+
id: t.id,
|
|
474
|
+
mode: t.mode,
|
|
475
|
+
status: t.status,
|
|
476
|
+
riskTier: t.riskTier,
|
|
477
|
+
environment: t.environment,
|
|
478
|
+
goal: t.goal,
|
|
479
|
+
createdAt: t.createdAt,
|
|
202
480
|
}));
|
|
203
481
|
result.taskCount = tasks.length;
|
|
204
482
|
}
|
|
@@ -214,16 +492,17 @@ export async function executeStatusCommand(options = {}) {
|
|
|
214
492
|
sectionHeader('Codehere Status');
|
|
215
493
|
newline();
|
|
216
494
|
// Version + hooks
|
|
217
|
-
const hookStatus = hookRunning
|
|
218
|
-
? colors.green('✓') + ' Hooks running'
|
|
219
|
-
: colors.yellow('⚠') + ' Hooks not installed';
|
|
495
|
+
const hookStatus = hookRunning ? colors.green('✓') + ' Hooks running' : colors.yellow('⚠') + ' Hooks not installed';
|
|
220
496
|
console.log(` Version: ${version} · Status: Running · ${hookStatus}`);
|
|
221
497
|
newline();
|
|
222
498
|
// Product brain
|
|
223
499
|
if (productCtx) {
|
|
224
|
-
const riskColor = productCtx.riskLevel === 'critical'
|
|
225
|
-
|
|
226
|
-
|
|
500
|
+
const riskColor = productCtx.riskLevel === 'critical'
|
|
501
|
+
? colors.red
|
|
502
|
+
: productCtx.riskLevel === 'high'
|
|
503
|
+
? colors.yellow
|
|
504
|
+
: productCtx.riskLevel === 'medium'
|
|
505
|
+
? colors.cyan
|
|
227
506
|
: colors.green;
|
|
228
507
|
const parts = [
|
|
229
508
|
colors.bold(productCtx.name),
|
|
@@ -256,7 +535,10 @@ export async function executeStatusCommand(options = {}) {
|
|
|
256
535
|
}
|
|
257
536
|
newline();
|
|
258
537
|
// Recent blocks
|
|
259
|
-
const recentBlocked = [...entries]
|
|
538
|
+
const recentBlocked = [...entries]
|
|
539
|
+
.reverse()
|
|
540
|
+
.filter((e) => e.blocked)
|
|
541
|
+
.slice(0, 5);
|
|
260
542
|
if (recentBlocked.length > 0) {
|
|
261
543
|
console.log(colors.dim(' RECENT BLOCKS'));
|
|
262
544
|
for (const entry of recentBlocked) {
|
|
@@ -285,13 +567,15 @@ export async function executeStatusCommand(options = {}) {
|
|
|
285
567
|
// Multi-agent session analysis
|
|
286
568
|
if (options.sessions && entries.length > 0) {
|
|
287
569
|
const allSessions = analyzeSessions(entries);
|
|
288
|
-
const sessions = allSessions.filter(s => s.sessionId !== 'unknown');
|
|
570
|
+
const sessions = allSessions.filter((s) => s.sessionId !== 'unknown');
|
|
289
571
|
const alerts = detectAmplificationAlerts(sessions);
|
|
290
572
|
console.log(colors.bold('Sessions (last 10):'));
|
|
291
573
|
for (const s of sessions.slice(0, 10)) {
|
|
292
574
|
const sid = s.sessionId.substring(0, 8);
|
|
293
|
-
const risk = s.amplificationRisk === 'high'
|
|
294
|
-
|
|
575
|
+
const risk = s.amplificationRisk === 'high'
|
|
576
|
+
? colors.red('high')
|
|
577
|
+
: s.amplificationRisk === 'medium'
|
|
578
|
+
? colors.yellow('med')
|
|
295
579
|
: colors.green('low');
|
|
296
580
|
const blocked = s.blockedCalls > 0 ? colors.red(` ${s.blockedCalls} blocked`) : '';
|
|
297
581
|
const dur = formatDuration(s.durationMs);
|
|
@@ -323,15 +607,22 @@ export async function executeStatusCommand(options = {}) {
|
|
|
323
607
|
const taskRepo = getTaskRepository();
|
|
324
608
|
const tasks = await taskRepo.getAll();
|
|
325
609
|
if (tasks.length === 0) {
|
|
326
|
-
console.log(colors.dim('No tasks found. Run "codehere
|
|
610
|
+
console.log(colors.dim('No tasks found. Run "codehere start" to open the task intake, or "codehere task <prompt>" from the CLI.'));
|
|
327
611
|
}
|
|
328
612
|
else {
|
|
329
|
-
const pad = (s, n) => s.length >= n ? s.substring(0, n) : s + ' '.repeat(n - s.length);
|
|
330
|
-
const trunc = (s, n) => s.length <= n ? s : s.substring(0, n - 1) + '…';
|
|
613
|
+
const pad = (s, n) => (s.length >= n ? s.substring(0, n) : s + ' '.repeat(n - s.length));
|
|
614
|
+
const trunc = (s, n) => (s.length <= n ? s : s.substring(0, n - 1) + '…');
|
|
331
615
|
console.log(colors.dim([pad('ID', 10), pad('Mode', 8), pad('Status', 12), pad('Risk', 4), pad('Env', 6), 'Goal'].join(' ')));
|
|
332
616
|
console.log(colors.dim(['-'.repeat(10), '-'.repeat(8), '-'.repeat(12), '-'.repeat(4), '-'.repeat(6), '-'.repeat(30)].join(' ')));
|
|
333
|
-
tasks.slice(0, 10).forEach(task => {
|
|
334
|
-
console.log([
|
|
617
|
+
tasks.slice(0, 10).forEach((task) => {
|
|
618
|
+
console.log([
|
|
619
|
+
pad(task.id.substring(0, 8) + '..', 10),
|
|
620
|
+
pad(task.mode, 8),
|
|
621
|
+
pad(task.status, 12),
|
|
622
|
+
pad(task.riskTier.toUpperCase(), 4),
|
|
623
|
+
pad(task.environment.substring(0, 6), 6),
|
|
624
|
+
trunc(task.goal, 30),
|
|
625
|
+
].join(' '));
|
|
335
626
|
});
|
|
336
627
|
if (tasks.length > 10) {
|
|
337
628
|
newline();
|
|
@@ -340,7 +631,7 @@ export async function executeStatusCommand(options = {}) {
|
|
|
340
631
|
}
|
|
341
632
|
}
|
|
342
633
|
catch {
|
|
343
|
-
console.log(colors.dim('No tasks found. Run "codehere
|
|
634
|
+
console.log(colors.dim('No tasks found. Run "codehere start" to open the task intake, or "codehere task <prompt>" from the CLI.'));
|
|
344
635
|
}
|
|
345
636
|
}
|
|
346
637
|
// Quick trust scan (opt-in)
|