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.
Files changed (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,118 +0,0 @@
1
- /**
2
- * Canonical file-collection rules for the trust scanner.
3
- *
4
- * Historic bug: IGNORE_DIRS and the test-file filter lived in three places
5
- * (trust-command, mcp-helpers, report-command) and drifted. The v0.3.0-alpha
6
- * shipreadiness fix patched two of them; report-command kept the old rules
7
- * and produced different finding counts than `trust` for the same directory.
8
- *
9
- * Single source of truth now — every scanner entry point imports from here.
10
- */
11
- import { readdirSync, statSync } from 'fs';
12
- import { join, extname } from 'path';
13
- export const CODE_EXTENSIONS = new Set([
14
- '.ts',
15
- '.tsx',
16
- '.js',
17
- '.jsx',
18
- '.mjs',
19
- '.cjs',
20
- '.py',
21
- '.rb',
22
- '.go',
23
- '.java',
24
- '.cs',
25
- '.php',
26
- '.sh',
27
- '.bash',
28
- '.zsh',
29
- '.sql',
30
- '.html',
31
- '.vue',
32
- '.svelte',
33
- ]);
34
- export const IGNORE_DIRS = new Set([
35
- // JS/TS
36
- 'node_modules',
37
- 'dist',
38
- 'build',
39
- '.next',
40
- '.nuxt',
41
- '.svelte-kit',
42
- '.turbo',
43
- '.parcel-cache',
44
- // Python
45
- 'venv',
46
- '__pycache__',
47
- '.pytest_cache',
48
- '.ruff_cache',
49
- '.mypy_cache',
50
- '.tox',
51
- '__pypackages__',
52
- // Rust / Go / PHP / Java / .NET
53
- 'target',
54
- 'vendor',
55
- '.gradle',
56
- 'obj',
57
- // VCS / tooling / coverage / codehere
58
- '.git',
59
- '.hg',
60
- '.svn',
61
- 'coverage',
62
- '.cache',
63
- '.codehere',
64
- '.codehere-cache',
65
- // Cloned reference repos used for dogfooding / comparison — not the user's code.
66
- // Codehere convention; mirrors how `.git` is hidden tooling-data, not source.
67
- '.oss-repos',
68
- // Test fixtures / mocks (intentionally-vulnerable code used as scanner inputs)
69
- 'fixtures',
70
- 'test-fixtures',
71
- '__fixtures__',
72
- '__mocks__',
73
- ]);
74
- // Test files contain deliberate vulnerability patterns as scanner inputs —
75
- // SQL injection strings, hardcoded "credentials" etc. — that inflate the
76
- // finding count without being real production risks. Skip by default.
77
- export const TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
78
- export function shouldIgnoreDir(name) {
79
- if (IGNORE_DIRS.has(name))
80
- return true;
81
- // Python virtualenv variants — .venv, .venv311, .venv-py3, venv3.11, env-py3
82
- if (name.startsWith('.venv') || name.startsWith('venv'))
83
- return true;
84
- return false;
85
- }
86
- export function shouldIgnoreFile(name) {
87
- return TEST_FILE_RE.test(name);
88
- }
89
- export function isCodeFile(name) {
90
- return CODE_EXTENSIONS.has(extname(name)) && !shouldIgnoreFile(name);
91
- }
92
- /**
93
- * Walk a directory and return paths of code files worth scanning.
94
- * If `target` is a single file, it is returned as-is (explicit targets
95
- * are never excluded — user pointed at it on purpose).
96
- */
97
- export function collectCodeFiles(target) {
98
- const stat = statSync(target);
99
- if (stat.isFile())
100
- return [target];
101
- const files = [];
102
- function walk(dir) {
103
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
104
- if (shouldIgnoreDir(entry.name))
105
- continue;
106
- const full = join(dir, entry.name);
107
- if (entry.isDirectory()) {
108
- walk(full);
109
- }
110
- else if (isCodeFile(entry.name)) {
111
- files.push(full);
112
- }
113
- }
114
- }
115
- walk(target);
116
- return files;
117
- }
118
- //# sourceMappingURL=file-collection.js.map
@@ -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, '&lt;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,234 +0,0 @@
1
- /**
2
- * Per-cwd permissions config — P18 slice 1 primitive.
3
- *
4
- * Reads `.codehere/permissions.yaml` from a project's cwd and lets the
5
- * trust-scorer SKIP findings whose matched text fits an allow pattern.
6
- *
7
- * Use case: codehere blocks `rm -rf node_modules` because the pattern
8
- * matches `rm -rf /`. The user knows it's safe in THIS cwd. They run
9
- * `codehere permission allow "rm -rf node_modules"` (slice 2 CLI) and
10
- * future blocks for that exact pattern in this cwd are skipped.
11
- *
12
- * Schema (.codehere/permissions.yaml):
13
- *
14
- * allow:
15
- * - pattern: "rm -rf node_modules"
16
- * reason: "OK to nuke deps in this repo"
17
- * addedAt: 2026-05-08T20:00:00Z
18
- * - pattern: "ANTHROPIC_API_KEY"
19
- * reason: "Doc explains env vars; not actually leaking"
20
- *
21
- * Allow patterns are STRINGS not regex — exact substring match against
22
- * the audit-finding `matched` field. Keeps the schema simple +
23
- * non-technical-user friendly. Regex support is a v0.7 follow-up.
24
- *
25
- * Per claim_user_persona HIGH: the YAML is human-readable; non-
26
- * technical users can edit it directly. Per overconfidence-guard:
27
- * we DON'T claim the allow-list is "secure" — it's user CONSENT,
28
- * not codehere certification.
29
- */
30
- import { existsSync, readFileSync } from 'fs';
31
- import { join } from 'path';
32
- const EMPTY = { allow: [], deny: [] };
33
- /**
34
- * Load .codehere/permissions.yaml from a cwd. Returns empty config if
35
- * the file doesn't exist or fails to parse. Defensive: never throws —
36
- * caller treats absence as "no overrides", which is the safest default
37
- * (the scanner blocks normally).
38
- */
39
- export function loadPermissions(cwd) {
40
- if (!cwd || typeof cwd !== 'string')
41
- return EMPTY;
42
- const file = join(cwd, '.codehere', 'permissions.yaml');
43
- if (!existsSync(file))
44
- return EMPTY;
45
- let raw;
46
- try {
47
- raw = readFileSync(file, 'utf-8');
48
- }
49
- catch {
50
- return EMPTY;
51
- }
52
- return parsePermissionsYaml(raw);
53
- }
54
- /**
55
- * Tiny YAML subset parser — only handles the shape this file uses
56
- * (top-level allow/deny lists of {pattern, reason, addedAt} objects).
57
- * No external dep; the codehere repo already avoids the `yaml` package
58
- * for read paths to keep cold-start fast. Format errors yield empty
59
- * config (same posture as missing file).
60
- */
61
- export function parsePermissionsYaml(raw) {
62
- const cfg = { allow: [], deny: [] };
63
- let bucket = null;
64
- let pending = null;
65
- for (const line of raw.split('\n')) {
66
- const stripped = line.replace(/#.*$/, '').trimEnd();
67
- if (!stripped.trim())
68
- continue;
69
- // Top-level key: "allow:" or "deny:"
70
- const top = stripped.match(/^(allow|deny)\s*:\s*$/);
71
- if (top) {
72
- // Flush any pending entry into the previous bucket
73
- if (pending && bucket && pending.pattern)
74
- cfg[bucket].push(pending);
75
- pending = null;
76
- bucket = top[1];
77
- continue;
78
- }
79
- if (!bucket)
80
- continue;
81
- // List entry start: " - pattern: ..."
82
- const listStart = stripped.match(/^\s*-\s+pattern\s*:\s*(.+)$/);
83
- if (listStart) {
84
- if (pending && pending.pattern)
85
- cfg[bucket].push(pending);
86
- pending = { pattern: unquote(listStart[1]) };
87
- continue;
88
- }
89
- // Continuation field on a pending entry
90
- const cont = stripped.match(/^\s+(reason|addedAt)\s*:\s*(.+)$/);
91
- if (cont && pending) {
92
- const key = cont[1];
93
- pending[key] = unquote(cont[2]);
94
- }
95
- }
96
- if (pending && bucket && pending.pattern)
97
- cfg[bucket].push(pending);
98
- return cfg;
99
- }
100
- function unquote(s) {
101
- const t = s.trim();
102
- if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
103
- return t.slice(1, -1);
104
- }
105
- return t;
106
- }
107
- /**
108
- * Check whether a finding's matched text is allow-listed in this cwd.
109
- * Substring match — exact-text patterns are simpler for non-technical
110
- * users than regex. Returns the matching entry (for audit-trail
111
- * provenance) or null if no match.
112
- */
113
- export function isAllowed(matchedText, cwd) {
114
- const cfg = loadPermissions(cwd);
115
- for (const entry of cfg.allow) {
116
- if (entry.pattern && matchedText.includes(entry.pattern))
117
- return entry;
118
- }
119
- return null;
120
- }
121
- /**
122
- * Filter a findings list against the allow-list for a cwd. Returns
123
- * the findings that survived (i.e., were NOT allow-listed). Used by
124
- * the trust-scorer / pre-prompt-scan to drop findings that the user
125
- * has explicitly approved.
126
- *
127
- * Side-effect-free: the allow-listed findings are dropped from the
128
- * caller's perspective. The audit log writer can still record the
129
- * full finding list IF the caller wants (then mark which were
130
- * allow-listed). This function is the simple "what should we still
131
- * block on" filter.
132
- */
133
- export function filterAllowed(findings, cwd) {
134
- if (findings.length === 0)
135
- return { surviving: [], allowed: [] };
136
- const cfg = loadPermissions(cwd);
137
- if (cfg.allow.length === 0)
138
- return { surviving: findings, allowed: [] };
139
- const surviving = [];
140
- const allowed = [];
141
- for (const f of findings) {
142
- const hit = cfg.allow.find((e) => e.pattern && f.matched.includes(e.pattern));
143
- if (hit)
144
- allowed.push({ finding: f, entry: hit });
145
- else
146
- surviving.push(f);
147
- }
148
- return { surviving, allowed };
149
- }
150
- // ── Write surface (P18 slice 3) ─────────────────────────────────────────────
151
- // Shared by the CLI (`codehere permission allow ...`) and the HTTP write
152
- // endpoint (POST /api/permissions). Single source of truth for the
153
- // idempotent-add semantics + the YAML serializer.
154
- import { mkdirSync, writeFileSync } from 'fs';
155
- /**
156
- * Idempotent add — if the pattern already exists in the bucket, returns
157
- * status='already_present' with the existing entry. Otherwise appends +
158
- * writes the file. Writes via Write tool semantics (overwrites the
159
- * whole YAML each time; entries are O(N) so re-serialization is cheap).
160
- */
161
- export function addPermissionEntry(input) {
162
- const bucket = input.bucket ?? 'allow';
163
- const cfg = loadPermissions(input.cwd);
164
- const list = cfg[bucket];
165
- const existing = list.find((e) => e.pattern === input.pattern);
166
- if (existing) {
167
- return {
168
- status: 'already_present',
169
- bucket,
170
- entry: existing,
171
- filePath: join(input.cwd, '.codehere', 'permissions.yaml'),
172
- };
173
- }
174
- const entry = {
175
- pattern: input.pattern,
176
- ...(input.reason ? { reason: input.reason } : {}),
177
- addedAt: new Date().toISOString(),
178
- };
179
- cfg[bucket].push(entry);
180
- const filePath = writePermissionsConfig(input.cwd, cfg);
181
- return { status: 'added', bucket, entry, filePath };
182
- }
183
- /**
184
- * Idempotent remove — drops every entry whose pattern matches across
185
- * BOTH buckets. Returns count of removed entries.
186
- */
187
- export function removePermissionEntry(cwd, pattern) {
188
- const cfg = loadPermissions(cwd);
189
- const before = cfg.allow.length + cfg.deny.length;
190
- cfg.allow = cfg.allow.filter((e) => e.pattern !== pattern);
191
- cfg.deny = cfg.deny.filter((e) => e.pattern !== pattern);
192
- const after = cfg.allow.length + cfg.deny.length;
193
- const removed = before - after;
194
- if (removed > 0)
195
- writePermissionsConfig(cwd, cfg);
196
- return { removed, filePath: join(cwd, '.codehere', 'permissions.yaml') };
197
- }
198
- function writePermissionsConfig(cwd, cfg) {
199
- const dir = join(cwd, '.codehere');
200
- if (!existsSync(dir))
201
- mkdirSync(dir, { recursive: true });
202
- const filePath = join(dir, 'permissions.yaml');
203
- writeFileSync(filePath, serializePermissionsYaml(cfg), 'utf-8');
204
- return filePath;
205
- }
206
- export function serializePermissionsYaml(cfg) {
207
- const out = [];
208
- out.push('# codehere permissions — per-folder allow/deny list');
209
- out.push('# See: codehere permission --help');
210
- out.push('');
211
- for (const bucket of ['allow', 'deny']) {
212
- out.push(`${bucket}:`);
213
- if (cfg[bucket].length === 0) {
214
- out.push(' []');
215
- }
216
- else {
217
- for (const e of cfg[bucket]) {
218
- out.push(` - pattern: ${quoteYaml(e.pattern)}`);
219
- if (e.reason)
220
- out.push(` reason: ${quoteYaml(e.reason)}`);
221
- if (e.addedAt)
222
- out.push(` addedAt: ${quoteYaml(e.addedAt)}`);
223
- }
224
- }
225
- out.push('');
226
- }
227
- return out.join('\n');
228
- }
229
- function quoteYaml(s) {
230
- if (s.includes('"') && !s.includes("'"))
231
- return `'${s}'`;
232
- return `"${s.replace(/"/g, '\\"')}"`;
233
- }
234
- //# sourceMappingURL=permissions-config.js.map