codehere 0.7.1 → 1.0.1
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 +22 -13
- package/dist/application/services/verdict-service.js +154 -0
- package/dist/application/use-cases/assess-cross-vendor.js +126 -0
- package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
- package/dist/application/use-cases/export-aat.js +107 -0
- package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
- package/dist/application/use-cases/generate-final-report.js +140 -15
- package/dist/application/use-cases/generate-narration.js +5 -3
- package/dist/application/use-cases/generate-thread-title.js +5 -2
- package/dist/application/use-cases/narrator-prompt-template.js +1 -1
- package/dist/application/use-cases/plan-auto-review.js +57 -0
- package/dist/application/use-cases/reconcile-audit.js +100 -0
- package/dist/application/use-cases/rerun-tests.js +226 -0
- package/dist/application/use-cases/search-sessions.js +70 -0
- package/dist/application/use-cases/should-auto-review.js +71 -0
- package/dist/application/use-cases/thread-export.js +90 -0
- package/dist/application/use-cases/verify-claim.js +187 -5
- package/dist/domain/audit.js +11 -0
- package/dist/domain/services/codebase-detector.js +0 -6
- package/dist/domain/task.js +91 -0
- package/dist/error-handler.js +3 -115
- package/dist/index.js +279 -31
- package/dist/infrastructure/completion/bash-completion.js +7 -3
- package/dist/infrastructure/completion/fish-completion.js +7 -12
- package/dist/infrastructure/completion/zsh-completion.js +7 -12
- package/dist/infrastructure/config/config-manager.js +4 -3
- package/dist/infrastructure/config/product-context.js +1 -19
- package/dist/infrastructure/context/adapters/figma-source.js +37 -2
- package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
- package/dist/infrastructure/context/github-api.js +110 -0
- package/dist/infrastructure/context/github-device-flow.js +65 -0
- package/dist/infrastructure/context/oauth-store.js +5 -2
- package/dist/infrastructure/context/registry-bootstrap.js +0 -4
- package/dist/infrastructure/cost/budget-config.js +4 -3
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +91 -16
- package/dist/infrastructure/cost/team-ledger.js +355 -0
- package/dist/infrastructure/discovery/discovery-log.js +85 -0
- package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +139 -3
- package/dist/infrastructure/hooks/hook-mode.js +20 -4
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
- package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
- package/dist/infrastructure/observability/execution-tracer.js +1 -1
- package/dist/infrastructure/orchestration/agents-config-seed.js +23 -11
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- package/dist/infrastructure/orchestration/cassette-recorder.js +1 -1
- package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
- package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
- package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
- package/dist/infrastructure/orchestration/context-handoff.js +32 -14
- package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
- package/dist/infrastructure/orchestration/failover-config.js +1 -11
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +134 -3
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/memory-injector.js +4 -1
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +167 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +275 -23
- package/dist/infrastructure/orchestration/session-manager.js +593 -94
- package/dist/infrastructure/orchestration/session-persistence.js +177 -41
- package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
- package/dist/infrastructure/protocols/cloud-sync.js +2 -2
- package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1498 -354
- package/dist/infrastructure/protocols/mcp-tools.js +401 -0
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
- package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
- package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
- package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
- package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
- package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
- package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
- package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
- package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
- package/dist/infrastructure/protocols/ui/styles.js +2147 -0
- package/dist/infrastructure/protocols/ui/tokens.js +96 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +116 -4126
- package/dist/infrastructure/protocols/verify-page.js +884 -148
- package/dist/infrastructure/security/fix-code.js +144 -0
- package/dist/infrastructure/security/fix-secret.js +89 -0
- package/dist/infrastructure/security/permissions-config.js +3 -2
- package/dist/infrastructure/security/scan-types.js +10 -0
- package/dist/infrastructure/security/ship-scan.js +195 -0
- package/dist/infrastructure/security/trust-scorer.js +70 -3
- package/dist/infrastructure/security/vibe-checks.js +99 -0
- package/dist/infrastructure/storage/atomic-write.js +86 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +151 -4
- package/dist/infrastructure/storage/project-thread-reader.js +262 -25
- package/dist/infrastructure/storage/project-thread-title-backfill.js +1 -1
- package/dist/monitoring.js +2 -2
- package/dist/presentation/cli/commands/audit-command.js +79 -4
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/check-command.js +96 -0
- package/dist/presentation/cli/commands/context-source-command.js +20 -1
- package/dist/presentation/cli/commands/help-command.js +5 -5
- package/dist/presentation/cli/commands/hook-command.js +5 -5
- package/dist/presentation/cli/commands/init-command.js +89 -66
- package/dist/presentation/cli/commands/memory-command.js +28 -28
- package/dist/presentation/cli/commands/permission-command.js +1 -12
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/protocols-command.js +4 -4
- package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
- package/dist/presentation/cli/commands/report-command.js +48 -32
- package/dist/presentation/cli/commands/scan-command.js +52 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
- package/dist/presentation/cli/commands/search-command.js +51 -0
- package/dist/presentation/cli/commands/setup-command.js +31 -8
- package/dist/presentation/cli/commands/status-command.js +39 -86
- package/dist/presentation/cli/commands/team-command.js +256 -0
- package/dist/presentation/cli/commands/thread-command.js +87 -5
- package/dist/presentation/cli/commands/trust-command.js +53 -31
- package/dist/presentation/cli/commands/verify-command.js +74 -9
- package/dist/presentation/cli/error-display.js +0 -8
- package/dist/presentation/cli/repl.js +15 -7
- package/dist/presentation/cli/task-deliverable.js +36 -10
- package/dist/testing/replay-adapter.js +0 -19
- package/dist/ui.js +51 -239
- package/dist/utils/version.js +0 -6
- package/package.json +30 -10
- package/dist/domain/entities/task.js +0 -61
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/task-repository.js +0 -206
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Infrastructure: Input Validator
|
|
3
|
-
* Enterprise-grade input validation and sanitization
|
|
4
|
-
*
|
|
5
|
-
* Clean Architecture: Infrastructure Layer
|
|
6
|
-
* Security: Prevents injection attacks, validates user input
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Validate file path to prevent directory traversal
|
|
10
|
-
*/
|
|
11
|
-
export function validateFilePath(filepath) {
|
|
12
|
-
const errors = [];
|
|
13
|
-
// Check for directory traversal attempts
|
|
14
|
-
if (filepath.includes('..') || filepath.includes('~')) {
|
|
15
|
-
errors.push('Directory traversal detected in file path');
|
|
16
|
-
}
|
|
17
|
-
// Check for absolute paths (should be relative)
|
|
18
|
-
if (filepath.startsWith('/') || /^[A-Z]:\\/.test(filepath)) {
|
|
19
|
-
errors.push('Absolute paths not allowed');
|
|
20
|
-
}
|
|
21
|
-
// Check for null bytes
|
|
22
|
-
if (filepath.includes('\0')) {
|
|
23
|
-
errors.push('Null bytes not allowed in file path');
|
|
24
|
-
}
|
|
25
|
-
// Check for dangerous characters
|
|
26
|
-
const dangerousChars = /[<>:"|?*\x00-\x1f]/;
|
|
27
|
-
if (dangerousChars.test(filepath)) {
|
|
28
|
-
errors.push('Invalid characters in file path');
|
|
29
|
-
}
|
|
30
|
-
// Sanitize path
|
|
31
|
-
let sanitized = filepath
|
|
32
|
-
.replace(/\.\./g, '') // Remove ..
|
|
33
|
-
.replace(/~/g, '') // Remove ~
|
|
34
|
-
.replace(/\0/g, '') // Remove null bytes
|
|
35
|
-
.replace(/[<>:"|?*\x00-\x1f]/g, '_'); // Replace dangerous chars
|
|
36
|
-
// Normalize path separators
|
|
37
|
-
sanitized = sanitized.replace(/\\/g, '/');
|
|
38
|
-
return {
|
|
39
|
-
valid: errors.length === 0,
|
|
40
|
-
errors,
|
|
41
|
-
sanitized: errors.length > 0 ? sanitized : undefined,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Validate query string to prevent injection
|
|
46
|
-
*/
|
|
47
|
-
export function validateQuery(query) {
|
|
48
|
-
const errors = [];
|
|
49
|
-
// Check for null bytes
|
|
50
|
-
if (query.includes('\0')) {
|
|
51
|
-
errors.push('Null bytes not allowed in query');
|
|
52
|
-
}
|
|
53
|
-
// Check for command injection patterns
|
|
54
|
-
const commandInjectionPatterns = [
|
|
55
|
-
/[;&|`$(){}[\]]/, // Shell metacharacters
|
|
56
|
-
/<script/i, // XSS attempts
|
|
57
|
-
/javascript:/i, // JavaScript protocol
|
|
58
|
-
/on\w+\s*=/i, // Event handlers
|
|
59
|
-
];
|
|
60
|
-
for (const pattern of commandInjectionPatterns) {
|
|
61
|
-
if (pattern.test(query)) {
|
|
62
|
-
errors.push('Potentially dangerous characters detected in query');
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
// Sanitize query
|
|
67
|
-
let sanitized = query
|
|
68
|
-
.replace(/\0/g, '') // Remove null bytes
|
|
69
|
-
.replace(/[;&|`$(){}[\]]/g, '') // Remove shell metacharacters
|
|
70
|
-
.replace(/<script/gi, '<script') // Escape script tags
|
|
71
|
-
.replace(/javascript:/gi, '') // Remove javascript protocol
|
|
72
|
-
.replace(/on\w+\s*=/gi, ''); // Remove event handlers
|
|
73
|
-
// Trim and limit length
|
|
74
|
-
sanitized = sanitized.trim();
|
|
75
|
-
const MAX_QUERY_LENGTH = 10000; // Reasonable limit
|
|
76
|
-
if (sanitized.length > MAX_QUERY_LENGTH) {
|
|
77
|
-
errors.push(`Query exceeds maximum length of ${MAX_QUERY_LENGTH} characters`);
|
|
78
|
-
sanitized = sanitized.substring(0, MAX_QUERY_LENGTH);
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
valid: errors.length === 0,
|
|
82
|
-
errors,
|
|
83
|
-
sanitized: errors.length > 0 ? sanitized : undefined,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Validate API key format
|
|
88
|
-
*/
|
|
89
|
-
export function validateAPIKey(apiKey) {
|
|
90
|
-
const errors = [];
|
|
91
|
-
if (!apiKey || apiKey.trim().length === 0) {
|
|
92
|
-
errors.push('API key is required');
|
|
93
|
-
}
|
|
94
|
-
// Check minimum length
|
|
95
|
-
if (apiKey.length < 10) {
|
|
96
|
-
errors.push('API key is too short');
|
|
97
|
-
}
|
|
98
|
-
// Check for null bytes
|
|
99
|
-
if (apiKey.includes('\0')) {
|
|
100
|
-
errors.push('Null bytes not allowed in API key');
|
|
101
|
-
}
|
|
102
|
-
// Check for whitespace (API keys shouldn't have spaces)
|
|
103
|
-
if (apiKey !== apiKey.trim()) {
|
|
104
|
-
errors.push('API key should not have leading or trailing whitespace');
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
valid: errors.length === 0,
|
|
108
|
-
errors,
|
|
109
|
-
sanitized: errors.length > 0 ? apiKey.trim() : undefined,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Validate file content before processing
|
|
114
|
-
*/
|
|
115
|
-
export function validateFileContent(content, maxSize = 10 * 1024 * 1024) {
|
|
116
|
-
const errors = [];
|
|
117
|
-
// Check size
|
|
118
|
-
const sizeInBytes = new Blob([content]).size;
|
|
119
|
-
if (sizeInBytes > maxSize) {
|
|
120
|
-
errors.push(`File content exceeds maximum size of ${maxSize} bytes`);
|
|
121
|
-
}
|
|
122
|
-
// Check for null bytes (shouldn't be in text files)
|
|
123
|
-
if (content.includes('\0')) {
|
|
124
|
-
errors.push('Null bytes detected in file content');
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
valid: errors.length === 0,
|
|
128
|
-
errors,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Sanitize user input for logging (prevent sensitive data leakage)
|
|
133
|
-
*/
|
|
134
|
-
export function sanitizeForLogging(input) {
|
|
135
|
-
// Remove potential secrets (API keys, tokens, etc.)
|
|
136
|
-
let sanitized = input
|
|
137
|
-
// Remove API keys (Cohere format: alphanumeric, 40+ chars)
|
|
138
|
-
.replace(/[A-Za-z0-9]{40,}/g, '[REDACTED]')
|
|
139
|
-
// Remove email addresses
|
|
140
|
-
.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[EMAIL_REDACTED]')
|
|
141
|
-
// Remove potential tokens
|
|
142
|
-
.replace(/token[=:]\s*[A-Za-z0-9_-]+/gi, 'token=[REDACTED]')
|
|
143
|
-
.replace(/password[=:]\s*[^\s]+/gi, 'password=[REDACTED]')
|
|
144
|
-
.replace(/secret[=:]\s*[^\s]+/gi, 'secret=[REDACTED]');
|
|
145
|
-
// Limit length for logging
|
|
146
|
-
const MAX_LOG_LENGTH = 1000;
|
|
147
|
-
if (sanitized.length > MAX_LOG_LENGTH) {
|
|
148
|
-
sanitized = sanitized.substring(0, MAX_LOG_LENGTH) + '...[TRUNCATED]';
|
|
149
|
-
}
|
|
150
|
-
return sanitized;
|
|
151
|
-
}
|
|
152
|
-
//# sourceMappingURL=input-validator.js.map
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Infrastructure: Embeddings Path Helper
|
|
3
|
-
* Determines per-codebase embeddings database path
|
|
4
|
-
*/
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { existsSync, mkdirSync } from 'fs';
|
|
7
|
-
import { findCodebaseRoot } from '../../domain/services/codebase-detector.js';
|
|
8
|
-
/**
|
|
9
|
-
* Get embeddings database path for a specific codebase
|
|
10
|
-
* Uses codebase root detection to ensure per-repo isolation
|
|
11
|
-
*
|
|
12
|
-
* @param cwd - Current working directory (defaults to process.cwd())
|
|
13
|
-
* @returns Object with root path, database path, and cache directory
|
|
14
|
-
*/
|
|
15
|
-
export function getEmbeddingsDbPath(cwd = process.cwd()) {
|
|
16
|
-
const codebaseInfo = findCodebaseRoot(cwd);
|
|
17
|
-
if (!codebaseInfo) {
|
|
18
|
-
// Fallback to cwd if codebase root can't be determined
|
|
19
|
-
const root = cwd;
|
|
20
|
-
const dbPath = join(root, '.codehere-cache', 'embeddings.sqlite');
|
|
21
|
-
const cacheDir = join(root, '.codehere-cache');
|
|
22
|
-
// Ensure cache directory exists
|
|
23
|
-
if (!existsSync(cacheDir)) {
|
|
24
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
25
|
-
}
|
|
26
|
-
return { root, dbPath, cacheDir };
|
|
27
|
-
}
|
|
28
|
-
const root = codebaseInfo.rootPath;
|
|
29
|
-
const dbPath = join(root, '.codehere-cache', 'embeddings.sqlite');
|
|
30
|
-
const cacheDir = join(root, '.codehere-cache');
|
|
31
|
-
// Ensure cache directory exists
|
|
32
|
-
if (!existsSync(cacheDir)) {
|
|
33
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
return { root, dbPath, cacheDir };
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=embeddings-path.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
|