codehere 0.8.0 → 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.
- package/README.md +37 -184
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +329 -0
- package/dist/index.js +74 -1004
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
- package/dist/presentation/cli/commands/check-command.js +251 -0
- package/dist/presentation/cli/commands/help-command.js +22 -286
- package/dist/ui.js +19 -438
- package/dist/utils/version.js +0 -12
- package/package.json +18 -27
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/generate-final-report.js +0 -134
- package/dist/application/use-cases/generate-narration.js +0 -128
- package/dist/application/use-cases/generate-thread-title.js +0 -178
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/thread-export.js +0 -155
- package/dist/application/use-cases/verify-claim.js +0 -274
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/entities/task.js +0 -61
- package/dist/domain/services/codebase-detector.js +0 -91
- package/dist/error-handler.js +0 -251
- package/dist/infrastructure/completion/bash-completion.js +0 -25
- package/dist/infrastructure/completion/fish-completion.js +0 -22
- package/dist/infrastructure/completion/zsh-completion.js +0 -30
- package/dist/infrastructure/config/config-manager.js +0 -173
- package/dist/infrastructure/config/product-context.js +0 -67
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -163
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -119
- package/dist/infrastructure/context/registry-bootstrap.js +0 -99
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -113
- package/dist/infrastructure/cost/cost-router.js +0 -104
- package/dist/infrastructure/cost/spend-aggregator.js +0 -147
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
- package/dist/infrastructure/hooks/hook-mode.js +0 -71
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
- package/dist/infrastructure/orchestration/context-handoff.js +0 -144
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -77
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
- package/dist/infrastructure/orchestration/graphify-context.js +0 -254
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1091
- package/dist/infrastructure/orchestration/session-persistence.js +0 -258
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/protocols/verify-page.js +0 -983
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/security/permissions-config.js +0 -234
- package/dist/infrastructure/security/trust-scorer.js +0 -657
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
- package/dist/infrastructure/storage/project-thread-reader.js +0 -172
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/infrastructure/storage/task-repository.js +0 -206
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -126
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -198
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -578
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -116
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -387
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
- package/dist/presentation/cli/commands/setup-command.js +0 -307
- package/dist/presentation/cli/commands/status-command.js +0 -661
- package/dist/presentation/cli/commands/thread-command.js +0 -288
- package/dist/presentation/cli/commands/trust-command.js +0 -306
- package/dist/presentation/cli/commands/verify-command.js +0 -233
- package/dist/presentation/cli/error-display.js +0 -336
- package/dist/presentation/cli/repl.js +0 -797
- package/dist/presentation/cli/task-deliverable.js +0 -127
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -166
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Title backfill — v0.6.0 follow-up to M9.
|
|
3
|
-
*
|
|
4
|
-
* The M9 hidden Title agent (generate-thread-title.ts) only fires on the FIRST
|
|
5
|
-
* task entry written to a project's thread.jsonl. Projects seeded by the v0.6
|
|
6
|
-
* migration (project-thread-migration.ts) bypass that path — they get a
|
|
7
|
-
* project-meta header but no Title agent invocation. Result: 5/7 projects on
|
|
8
|
-
* a typical dogfood machine show "(untitled)" in the /threads switcher even
|
|
9
|
-
* though their first task carries enough signal to title from.
|
|
10
|
-
*
|
|
11
|
-
* This backfill closes that gap. For every project with title=null and at
|
|
12
|
-
* least one task entry, it fires the Title agent on the first task's prompt +
|
|
13
|
-
* cwd and appends a `{_meta: 'project-update', title: ...}` line. Reader
|
|
14
|
-
* (project-thread-reader.ts) takes the latest project-update title.
|
|
15
|
-
*
|
|
16
|
-
* Idempotent: skips projects that already have a title (or that have NO tasks
|
|
17
|
-
* to title from). Safe to run on every daemon boot. Best-effort: any LLM
|
|
18
|
-
* failure (provider missing, timeout, rate limit) skips that project silently.
|
|
19
|
-
*
|
|
20
|
-
* Cost budget: ~1 LLM call per untitled project on first run after this lands.
|
|
21
|
-
* Subsequent boots are no-ops since titles persist.
|
|
22
|
-
*/
|
|
23
|
-
import { existsSync, readdirSync, readFileSync, appendFileSync, statSync } from 'fs';
|
|
24
|
-
import { join } from 'path';
|
|
25
|
-
import { homedir } from 'os';
|
|
26
|
-
import { generateThreadTitle } from '../../application/use-cases/generate-thread-title.js';
|
|
27
|
-
const DEFAULT_PROJECTS_DIR = join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'projects');
|
|
28
|
-
function scanThread(slug, file) {
|
|
29
|
-
let title = null;
|
|
30
|
-
let firstPrompt = null;
|
|
31
|
-
let firstCwd = null;
|
|
32
|
-
let firstStartedAt = Number.MAX_SAFE_INTEGER;
|
|
33
|
-
let content;
|
|
34
|
-
try {
|
|
35
|
-
content = readFileSync(file, 'utf-8');
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return { slug, title, firstPrompt, firstCwd };
|
|
39
|
-
}
|
|
40
|
-
for (const line of content.split('\n')) {
|
|
41
|
-
const trimmed = line.trim();
|
|
42
|
-
if (!trimmed)
|
|
43
|
-
continue;
|
|
44
|
-
let rec;
|
|
45
|
-
try {
|
|
46
|
-
rec = JSON.parse(trimmed);
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (rec._meta === 'project') {
|
|
52
|
-
if (typeof rec.title === 'string')
|
|
53
|
-
title = rec.title;
|
|
54
|
-
}
|
|
55
|
-
else if (rec._meta === 'project-update') {
|
|
56
|
-
if (typeof rec.title === 'string')
|
|
57
|
-
title = rec.title;
|
|
58
|
-
}
|
|
59
|
-
else if (rec.type === 'task') {
|
|
60
|
-
const startedAt = typeof rec.startedAt === 'number' ? rec.startedAt : Number.MAX_SAFE_INTEGER;
|
|
61
|
-
if (startedAt < firstStartedAt) {
|
|
62
|
-
firstStartedAt = startedAt;
|
|
63
|
-
if (typeof rec.prompt === 'string')
|
|
64
|
-
firstPrompt = rec.prompt;
|
|
65
|
-
if (typeof rec.cwd === 'string')
|
|
66
|
-
firstCwd = rec.cwd;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return { slug, title, firstPrompt, firstCwd };
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Backfill missing titles. Returns counts; never throws (best-effort per project).
|
|
74
|
-
*
|
|
75
|
-
* Disable via CODEHERE_DISABLE_TITLE_AGENT=1 (same env var that gates M9 itself —
|
|
76
|
-
* keeps the toggle surface minimal).
|
|
77
|
-
*/
|
|
78
|
-
export async function backfillThreadTitles(options) {
|
|
79
|
-
const start = Date.now();
|
|
80
|
-
const result = {
|
|
81
|
-
scanned: 0,
|
|
82
|
-
alreadyTitled: 0,
|
|
83
|
-
noTasks: 0,
|
|
84
|
-
titlesGenerated: 0,
|
|
85
|
-
titleAgentFailed: 0,
|
|
86
|
-
durationMs: 0,
|
|
87
|
-
};
|
|
88
|
-
if (process.env.CODEHERE_DISABLE_TITLE_AGENT === '1') {
|
|
89
|
-
result.durationMs = Date.now() - start;
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
const root = options?.projectsDir ?? DEFAULT_PROJECTS_DIR;
|
|
93
|
-
if (!existsSync(root)) {
|
|
94
|
-
result.durationMs = Date.now() - start;
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
let stat;
|
|
98
|
-
try {
|
|
99
|
-
stat = statSync(root);
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
result.durationMs = Date.now() - start;
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
if (!stat.isDirectory()) {
|
|
106
|
-
result.durationMs = Date.now() - start;
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
let slugs;
|
|
110
|
-
try {
|
|
111
|
-
slugs = readdirSync(root);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
result.durationMs = Date.now() - start;
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
const cap = options?.maxProjects ?? Number.MAX_SAFE_INTEGER;
|
|
118
|
-
let backfilledCount = 0;
|
|
119
|
-
for (const slug of slugs) {
|
|
120
|
-
if (backfilledCount >= cap)
|
|
121
|
-
break;
|
|
122
|
-
const projectDir = join(root, slug);
|
|
123
|
-
let s;
|
|
124
|
-
try {
|
|
125
|
-
s = statSync(projectDir);
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
if (!s.isDirectory())
|
|
131
|
-
continue;
|
|
132
|
-
const file = join(projectDir, 'thread.jsonl');
|
|
133
|
-
if (!existsSync(file))
|
|
134
|
-
continue;
|
|
135
|
-
result.scanned++;
|
|
136
|
-
const scanned = scanThread(slug, file);
|
|
137
|
-
if (scanned.title) {
|
|
138
|
-
result.alreadyTitled++;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (!scanned.firstPrompt || !scanned.firstCwd) {
|
|
142
|
-
// Nothing to title from — skip. (Migration-seeded headers without task
|
|
143
|
-
// entries fall here, as do projects where the first task lost its prompt
|
|
144
|
-
// somehow.)
|
|
145
|
-
result.noTasks++;
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
let title;
|
|
149
|
-
try {
|
|
150
|
-
title = await generateThreadTitle(scanned.firstPrompt, scanned.firstCwd);
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
// Title agent should never throw (it returns null on failure), but guard
|
|
154
|
-
// anyway so one bad call doesn't sink the whole backfill.
|
|
155
|
-
title = null;
|
|
156
|
-
}
|
|
157
|
-
if (!title) {
|
|
158
|
-
result.titleAgentFailed++;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
// Race guard: backfill scanned BEFORE the LLM call, but the LLM blocks
|
|
162
|
-
// for seconds. A user rename in that window leaves a userOverride:true
|
|
163
|
-
// marker the pre-scan never saw. Re-read the same file just before the
|
|
164
|
-
// write and skip if the user got there first. (Inlined rather than calling
|
|
165
|
-
// hasManualOverride() because that helper hardcodes the production
|
|
166
|
-
// projects dir; backfill supports a custom projectsDir for tests.)
|
|
167
|
-
let userBeatUs = false;
|
|
168
|
-
try {
|
|
169
|
-
const recheck = readFileSync(file, 'utf-8');
|
|
170
|
-
for (const line of recheck.split('\n')) {
|
|
171
|
-
const t = line.trim();
|
|
172
|
-
if (!t)
|
|
173
|
-
continue;
|
|
174
|
-
try {
|
|
175
|
-
const rec = JSON.parse(t);
|
|
176
|
-
if (rec._meta === 'project-update' && rec.userOverride === true) {
|
|
177
|
-
userBeatUs = true;
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
/* skip malformed */
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
/* if we can't read, fall through and let the append fail */
|
|
188
|
-
}
|
|
189
|
-
if (userBeatUs) {
|
|
190
|
-
result.alreadyTitled++;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
const updateLine = JSON.stringify({
|
|
195
|
-
_meta: 'project-update',
|
|
196
|
-
slug,
|
|
197
|
-
title,
|
|
198
|
-
cwd: scanned.firstCwd,
|
|
199
|
-
backfilledAt: Date.now(),
|
|
200
|
-
updatedAt: Date.now(),
|
|
201
|
-
});
|
|
202
|
-
appendFileSync(file, updateLine + '\n', 'utf-8');
|
|
203
|
-
result.titlesGenerated++;
|
|
204
|
-
backfilledCount++;
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
// Append failure is logged via counter but doesn't sink the loop.
|
|
208
|
-
result.titleAgentFailed++;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
result.durationMs = Date.now() - start;
|
|
212
|
-
return result;
|
|
213
|
-
}
|
|
214
|
-
//# sourceMappingURL=project-thread-title-backfill.js.map
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Infrastructure: Task Repository
|
|
3
|
-
* Task storage and retrieval using JSONL format
|
|
4
|
-
*
|
|
5
|
-
* Phase 1: v0.5.0-alpha
|
|
6
|
-
* See: .internal/ARCHITECTURE.md (internal docs)
|
|
7
|
-
*/
|
|
8
|
-
import { appendFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
-
import { join, dirname } from 'path';
|
|
10
|
-
import { findCodebaseRoot } from '../../domain/services/codebase-detector.js';
|
|
11
|
-
/**
|
|
12
|
-
* JSONL-based task repository
|
|
13
|
-
* Stores tasks in .codehere/tasks.jsonl at codebase root
|
|
14
|
-
*/
|
|
15
|
-
export class JsonlTaskRepository {
|
|
16
|
-
tasksPath;
|
|
17
|
-
tasksCache = new Map();
|
|
18
|
-
cacheLoaded = false;
|
|
19
|
-
constructor(customPath) {
|
|
20
|
-
if (customPath) {
|
|
21
|
-
this.tasksPath = customPath;
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
// Use environment variable or detect from codebase root
|
|
25
|
-
const envPath = process.env.CODEHERE_TASKS_PATH;
|
|
26
|
-
if (envPath) {
|
|
27
|
-
this.tasksPath = envPath;
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
// Detect codebase root and use .codehere/tasks.jsonl
|
|
31
|
-
const codebaseInfo = findCodebaseRoot(process.cwd());
|
|
32
|
-
const codebaseRoot = codebaseInfo?.rootPath || process.cwd();
|
|
33
|
-
const codehereDir = join(codebaseRoot, '.codehere');
|
|
34
|
-
// Ensure .codehere directory exists
|
|
35
|
-
if (!existsSync(codehereDir)) {
|
|
36
|
-
mkdirSync(codehereDir, { recursive: true });
|
|
37
|
-
}
|
|
38
|
-
this.tasksPath = join(codehereDir, 'tasks.jsonl');
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Ensure directory exists
|
|
42
|
-
const tasksDir = dirname(this.tasksPath);
|
|
43
|
-
if (!existsSync(tasksDir)) {
|
|
44
|
-
mkdirSync(tasksDir, { recursive: true });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Load tasks from JSONL file into cache
|
|
49
|
-
* For duplicate IDs, keeps the latest version (by updatedAt)
|
|
50
|
-
*/
|
|
51
|
-
loadTasks() {
|
|
52
|
-
if (this.cacheLoaded) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (!existsSync(this.tasksPath)) {
|
|
56
|
-
this.cacheLoaded = true;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const content = readFileSync(this.tasksPath, 'utf-8');
|
|
61
|
-
const lines = content
|
|
62
|
-
.trim()
|
|
63
|
-
.split('\n')
|
|
64
|
-
.filter((line) => line.trim());
|
|
65
|
-
// Load all tasks, then keep only the latest version of each ID
|
|
66
|
-
const allTasks = [];
|
|
67
|
-
for (const line of lines) {
|
|
68
|
-
try {
|
|
69
|
-
const task = JSON.parse(line);
|
|
70
|
-
allTasks.push(task);
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
// Skip invalid lines
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// Group by ID and keep latest version
|
|
77
|
-
const taskMap = new Map();
|
|
78
|
-
for (const task of allTasks) {
|
|
79
|
-
const existing = taskMap.get(task.id);
|
|
80
|
-
if (!existing || task.updatedAt > existing.updatedAt) {
|
|
81
|
-
taskMap.set(task.id, task);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
this.tasksCache = taskMap;
|
|
85
|
-
this.cacheLoaded = true;
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
// Silent failure - don't break user workflow
|
|
89
|
-
console.error('[TaskRepository] Failed to load tasks:', error);
|
|
90
|
-
this.cacheLoaded = true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Create a new task
|
|
95
|
-
*/
|
|
96
|
-
async create(task) {
|
|
97
|
-
// Ensure cache is loaded
|
|
98
|
-
this.loadTasks();
|
|
99
|
-
// Set timestamps if not provided
|
|
100
|
-
const now = Date.now();
|
|
101
|
-
const taskToCreate = {
|
|
102
|
-
...task,
|
|
103
|
-
createdAt: task.createdAt || now,
|
|
104
|
-
updatedAt: task.updatedAt || now,
|
|
105
|
-
};
|
|
106
|
-
// Add to cache
|
|
107
|
-
this.tasksCache.set(taskToCreate.id, taskToCreate);
|
|
108
|
-
// Append to JSONL file (append-only, immutable history)
|
|
109
|
-
try {
|
|
110
|
-
appendFileSync(this.tasksPath, JSON.stringify(taskToCreate) + '\n', 'utf-8');
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
// Silent failure - remove from cache if write failed
|
|
114
|
-
this.tasksCache.delete(taskToCreate.id);
|
|
115
|
-
console.error('[TaskRepository] Failed to create task:', error);
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Get task by ID (returns latest version if multiple exist)
|
|
121
|
-
*/
|
|
122
|
-
async getById(id) {
|
|
123
|
-
this.loadTasks();
|
|
124
|
-
// Load all tasks and find the latest version of this ID
|
|
125
|
-
const allTasks = Array.from(this.tasksCache.values());
|
|
126
|
-
const tasksWithId = allTasks.filter((task) => task.id === id);
|
|
127
|
-
if (tasksWithId.length === 0) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
// Return the task with the latest updatedAt timestamp
|
|
131
|
-
return tasksWithId.reduce((latest, current) => {
|
|
132
|
-
return current.updatedAt > latest.updatedAt ? current : latest;
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Get tasks by status
|
|
137
|
-
*/
|
|
138
|
-
async getByStatus(status) {
|
|
139
|
-
this.loadTasks();
|
|
140
|
-
const tasks = Array.from(this.tasksCache.values());
|
|
141
|
-
return tasks.filter((task) => task.status === status);
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Get tasks by mode
|
|
145
|
-
*/
|
|
146
|
-
async getByMode(mode) {
|
|
147
|
-
this.loadTasks();
|
|
148
|
-
const tasks = Array.from(this.tasksCache.values());
|
|
149
|
-
return tasks.filter((task) => task.mode === mode);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Get recent tasks (latest updated first)
|
|
153
|
-
*/
|
|
154
|
-
async getRecent(limit = 10) {
|
|
155
|
-
this.loadTasks();
|
|
156
|
-
const tasks = Array.from(this.tasksCache.values());
|
|
157
|
-
return tasks.sort((a, b) => b.updatedAt - a.updatedAt).slice(0, limit);
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Get all tasks (latest version of each)
|
|
161
|
-
*/
|
|
162
|
-
async getAll() {
|
|
163
|
-
this.loadTasks();
|
|
164
|
-
return Array.from(this.tasksCache.values());
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Update existing task
|
|
168
|
-
* Creates a new entry in JSONL (append-only)
|
|
169
|
-
*/
|
|
170
|
-
async update(task) {
|
|
171
|
-
// Ensure cache is loaded
|
|
172
|
-
this.loadTasks();
|
|
173
|
-
// Update timestamp
|
|
174
|
-
const updatedTask = {
|
|
175
|
-
...task,
|
|
176
|
-
updatedAt: Date.now(),
|
|
177
|
-
};
|
|
178
|
-
// Update cache
|
|
179
|
-
this.tasksCache.set(updatedTask.id, updatedTask);
|
|
180
|
-
// Append updated version to JSONL (append-only, immutable history)
|
|
181
|
-
try {
|
|
182
|
-
appendFileSync(this.tasksPath, JSON.stringify(updatedTask) + '\n', 'utf-8');
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
// Revert cache if write failed
|
|
186
|
-
const original = await this.getById(task.id);
|
|
187
|
-
if (original) {
|
|
188
|
-
this.tasksCache.set(task.id, original);
|
|
189
|
-
}
|
|
190
|
-
console.error('[TaskRepository] Failed to update task:', error);
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// Singleton instance (lazy initialization with codebase detection)
|
|
196
|
-
let taskRepositoryInstance = null;
|
|
197
|
-
/**
|
|
198
|
-
* Get task repository instance
|
|
199
|
-
*/
|
|
200
|
-
export function getTaskRepository(customPath) {
|
|
201
|
-
if (!taskRepositoryInstance || customPath) {
|
|
202
|
-
taskRepositoryInstance = new JsonlTaskRepository(customPath);
|
|
203
|
-
}
|
|
204
|
-
return taskRepositoryInstance;
|
|
205
|
-
}
|
|
206
|
-
//# sourceMappingURL=task-repository.js.map
|
package/dist/monitoring.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Enterprise Monitoring & Health Check System
|
|
3
|
-
* Ensures 24/7 operation visibility
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync, mkdirSync } from 'fs';
|
|
6
|
-
import { join, dirname } from 'path';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
import { cohereCircuitBreaker } from './error-handler.js';
|
|
9
|
-
class MetricsCollector {
|
|
10
|
-
operations = {
|
|
11
|
-
total: 0,
|
|
12
|
-
successful: 0,
|
|
13
|
-
failed: 0,
|
|
14
|
-
retries: 0,
|
|
15
|
-
};
|
|
16
|
-
performance = {
|
|
17
|
-
responseTimes: [],
|
|
18
|
-
retryTimes: [],
|
|
19
|
-
apiCalls: 0,
|
|
20
|
-
cacheHits: 0,
|
|
21
|
-
};
|
|
22
|
-
errors = {
|
|
23
|
-
byType: {},
|
|
24
|
-
recent: [],
|
|
25
|
-
};
|
|
26
|
-
recordOperation(success, responseTime, retries) {
|
|
27
|
-
this.operations.total++;
|
|
28
|
-
if (success) {
|
|
29
|
-
this.operations.successful++;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
this.operations.failed++;
|
|
33
|
-
}
|
|
34
|
-
if (retries) {
|
|
35
|
-
this.operations.retries += retries;
|
|
36
|
-
}
|
|
37
|
-
if (responseTime) {
|
|
38
|
-
this.performance.responseTimes.push(responseTime);
|
|
39
|
-
// Keep only last 1000 measurements
|
|
40
|
-
if (this.performance.responseTimes.length > 1000) {
|
|
41
|
-
this.performance.responseTimes.shift();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
recordApiCall() {
|
|
46
|
-
this.performance.apiCalls++;
|
|
47
|
-
}
|
|
48
|
-
recordCacheHit() {
|
|
49
|
-
this.performance.cacheHits++;
|
|
50
|
-
}
|
|
51
|
-
recordError(type, message) {
|
|
52
|
-
this.errors.byType[type] = (this.errors.byType[type] || 0) + 1;
|
|
53
|
-
this.errors.recent.push({
|
|
54
|
-
timestamp: new Date().toISOString(),
|
|
55
|
-
type,
|
|
56
|
-
message,
|
|
57
|
-
});
|
|
58
|
-
// Keep only last 100 errors
|
|
59
|
-
if (this.errors.recent.length > 100) {
|
|
60
|
-
this.errors.recent.shift();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
getMetrics() {
|
|
64
|
-
const avgResponseTime = this.performance.responseTimes.length > 0
|
|
65
|
-
? this.performance.responseTimes.reduce((a, b) => a + b, 0) / this.performance.responseTimes.length
|
|
66
|
-
: 0;
|
|
67
|
-
const avgRetryTime = this.performance.retryTimes.length > 0
|
|
68
|
-
? this.performance.retryTimes.reduce((a, b) => a + b, 0) / this.performance.retryTimes.length
|
|
69
|
-
: 0;
|
|
70
|
-
return {
|
|
71
|
-
operations: { ...this.operations },
|
|
72
|
-
performance: {
|
|
73
|
-
avgResponseTime,
|
|
74
|
-
avgRetryTime,
|
|
75
|
-
apiCalls: this.performance.apiCalls,
|
|
76
|
-
cacheHits: this.performance.cacheHits,
|
|
77
|
-
},
|
|
78
|
-
errors: {
|
|
79
|
-
byType: { ...this.errors.byType },
|
|
80
|
-
recent: [...this.errors.recent],
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
reset() {
|
|
85
|
-
this.operations = { total: 0, successful: 0, failed: 0, retries: 0 };
|
|
86
|
-
this.performance = { responseTimes: [], retryTimes: [], apiCalls: 0, cacheHits: 0 };
|
|
87
|
-
this.errors = { byType: {}, recent: [] };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
export const metricsCollector = new MetricsCollector();
|
|
91
|
-
/**
|
|
92
|
-
* Check API health
|
|
93
|
-
*/
|
|
94
|
-
function checkApi() {
|
|
95
|
-
const circuitState = cohereCircuitBreaker.getState();
|
|
96
|
-
if (circuitState === 'OPEN') {
|
|
97
|
-
return { status: 'circuit_open', message: 'API circuit breaker is OPEN - too many failures' };
|
|
98
|
-
}
|
|
99
|
-
if (circuitState === 'HALF_OPEN') {
|
|
100
|
-
return { status: 'error', message: 'API circuit breaker is HALF_OPEN - recovering' };
|
|
101
|
-
}
|
|
102
|
-
// Check if any API key is configured
|
|
103
|
-
const hasKey = !!(process.env.ANTHROPIC_API_KEY ||
|
|
104
|
-
process.env.OPENAI_API_KEY ||
|
|
105
|
-
process.env.COHERE_API_KEY ||
|
|
106
|
-
process.env.OPENROUTER_API_KEY);
|
|
107
|
-
if (!hasKey) {
|
|
108
|
-
return { status: 'error', message: 'No API key configured' };
|
|
109
|
-
}
|
|
110
|
-
return { status: 'ok', message: 'API accessible' };
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Check filesystem health
|
|
114
|
-
*/
|
|
115
|
-
function checkFilesystem() {
|
|
116
|
-
try {
|
|
117
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
118
|
-
const __dirname = dirname(__filename);
|
|
119
|
-
const agentDir = join(__dirname, '..');
|
|
120
|
-
const dataDir = join(agentDir, 'data');
|
|
121
|
-
// Check if data directory exists or can be created
|
|
122
|
-
if (!existsSync(dataDir)) {
|
|
123
|
-
// Try to create it
|
|
124
|
-
try {
|
|
125
|
-
mkdirSync(dataDir, { recursive: true });
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
return { status: 'error', message: 'Cannot create data directory' };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return { status: 'ok', message: 'Filesystem accessible' };
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
return {
|
|
135
|
-
status: 'error',
|
|
136
|
-
message: `Filesystem check failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Check memory usage
|
|
142
|
-
*/
|
|
143
|
-
function checkMemory() {
|
|
144
|
-
try {
|
|
145
|
-
const usage = process.memoryUsage();
|
|
146
|
-
const heapUsedMB = usage.heapUsed / 1024 / 1024;
|
|
147
|
-
const heapTotalMB = usage.heapTotal / 1024 / 1024;
|
|
148
|
-
const usagePercent = (heapUsedMB / heapTotalMB) * 100;
|
|
149
|
-
if (usagePercent > 90) {
|
|
150
|
-
return {
|
|
151
|
-
status: 'warning',
|
|
152
|
-
message: `High memory usage: ${usagePercent.toFixed(1)}%`,
|
|
153
|
-
usage: usagePercent,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
status: 'ok',
|
|
158
|
-
message: `Memory usage: ${usagePercent.toFixed(1)}%`,
|
|
159
|
-
usage: usagePercent,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
return {
|
|
164
|
-
status: 'ok',
|
|
165
|
-
message: 'Memory check unavailable',
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Perform health check
|
|
171
|
-
*/
|
|
172
|
-
export function performHealthCheck() {
|
|
173
|
-
const apiCheck = checkApi();
|
|
174
|
-
const fsCheck = checkFilesystem();
|
|
175
|
-
const memCheck = checkMemory();
|
|
176
|
-
const checks = {
|
|
177
|
-
api: apiCheck,
|
|
178
|
-
filesystem: fsCheck,
|
|
179
|
-
memory: memCheck,
|
|
180
|
-
};
|
|
181
|
-
let status = 'healthy';
|
|
182
|
-
if (apiCheck.status === 'circuit_open' || fsCheck.status === 'error') {
|
|
183
|
-
status = 'unhealthy';
|
|
184
|
-
}
|
|
185
|
-
else if (apiCheck.status === 'error' || memCheck.status === 'warning') {
|
|
186
|
-
status = 'degraded';
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
status,
|
|
190
|
-
timestamp: new Date().toISOString(),
|
|
191
|
-
checks,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Get current metrics
|
|
196
|
-
*/
|
|
197
|
-
export function getMetrics() {
|
|
198
|
-
return metricsCollector.getMetrics();
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Format health status for display
|
|
202
|
-
*/
|
|
203
|
-
export function formatHealthStatus(health) {
|
|
204
|
-
const statusIcon = health.status === 'healthy' ? '✓' : health.status === 'degraded' ? '⚠' : '✗';
|
|
205
|
-
let output = ` ${statusIcon} ${health.status.toUpperCase()}\n\n`;
|
|
206
|
-
for (const [component, check] of Object.entries(health.checks)) {
|
|
207
|
-
const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '⚠' : '✗';
|
|
208
|
-
output += ` ${icon} ${component.padEnd(12)} ${check.message}\n`;
|
|
209
|
-
}
|
|
210
|
-
return output;
|
|
211
|
-
}
|
|
212
|
-
//# sourceMappingURL=monitoring.js.map
|