codehere 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +35 -193
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,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,144 +0,0 @@
1
- /**
2
- * AI fix + prove-closed for a CODE danger (not a leaked secret) — browser-auth, injection, unsafe HTML.
3
- * Spawns claude-code (subscription, async so it never blocks the daemon) to fix the specific issue,
4
- * then RE-SCANS to prove that exact danger no longer fires. Honest if it can't. Uses model spend.
5
- * The verify-loop (validated 0% false-positive) applied to the scan. docs/EXECUTION_PLAN step 2.
6
- */
7
- import { spawn } from 'node:child_process';
8
- import { shipScan } from './ship-scan.js';
9
- // Same pre-granted edit tools the daemon's claude-code adapter uses for headless -p mode.
10
- const CLAUDE_FLAGS = [
11
- '--output-format',
12
- 'stream-json',
13
- '--verbose',
14
- '--allowedTools',
15
- 'Edit',
16
- 'Write',
17
- 'Read',
18
- 'Grep',
19
- 'Glob',
20
- 'MultiEdit',
21
- ];
22
- const baseName = (p) => {
23
- const s = String(p || '');
24
- return s.split('/').pop() || s;
25
- };
26
- /** Turn one claude stream-json event into a plain-language step ('' = nothing worth showing). */
27
- function stepLabel(ev) {
28
- if (!ev || ev.type !== 'assistant')
29
- return '';
30
- const content = ev.message?.content;
31
- if (!Array.isArray(content))
32
- return '';
33
- for (const c of content) {
34
- if (c?.type !== 'tool_use')
35
- continue;
36
- const name = String(c.name || '');
37
- const input = c.input || {};
38
- const f = baseName(input.file_path || input.path);
39
- if (name === 'Edit' || name === 'MultiEdit' || name === 'Write')
40
- return `Editing ${f}`;
41
- if (name === 'Read')
42
- return `Reading ${f}`;
43
- if (name === 'Grep' || name === 'Glob')
44
- return 'Searching the code';
45
- return `Working (${name})`;
46
- }
47
- return '';
48
- }
49
- export async function fixCodeVuln(dir, top, onStep) {
50
- const prompt = `There is a real security vulnerability in ${top.file} (around line ${top.line}): ${top.what} ` +
51
- `Fix it properly so the app still works — change the code, don't just comment it. Edit only the file(s) needed in this project.`;
52
- const env = { ...process.env };
53
- delete env.ANTHROPIC_API_KEY; // use the Claude Code subscription, not pay-as-you-go
54
- delete env.ANTHROPIC_AUTH_TOKEN;
55
- // Don't repeat the same step twice in a row — keeps the live feed readable.
56
- let lastLabel = '';
57
- const step = (label) => {
58
- if (!label || label === lastLabel)
59
- return;
60
- lastLabel = label;
61
- try {
62
- onStep?.(label);
63
- }
64
- catch {
65
- /* a dead client must never crash the fix */
66
- }
67
- };
68
- step('Starting the AI fix');
69
- await new Promise((resolve) => {
70
- let child;
71
- try {
72
- // Capture stdout (the stream-json feed) so we can surface live steps; stderr stays quiet.
73
- child = spawn('claude', ['-p', prompt, ...CLAUDE_FLAGS], {
74
- cwd: dir,
75
- env: env,
76
- stdio: ['ignore', 'pipe', 'ignore'],
77
- });
78
- }
79
- catch {
80
- resolve();
81
- return;
82
- }
83
- let buf = '';
84
- child.stdout?.on('data', (chunk) => {
85
- buf += chunk.toString();
86
- let nl;
87
- while ((nl = buf.indexOf('\n')) >= 0) {
88
- const line = buf.slice(0, nl).trim();
89
- buf = buf.slice(nl + 1);
90
- if (!line)
91
- continue;
92
- let ev;
93
- try {
94
- ev = JSON.parse(line);
95
- }
96
- catch {
97
- continue;
98
- }
99
- step(stepLabel(ev));
100
- }
101
- });
102
- const timer = setTimeout(() => {
103
- try {
104
- child.kill('SIGKILL');
105
- }
106
- catch {
107
- /* best effort */
108
- }
109
- resolve();
110
- }, 180_000);
111
- child.on('close', () => {
112
- clearTimeout(timer);
113
- resolve();
114
- });
115
- child.on('error', () => {
116
- clearTimeout(timer);
117
- resolve();
118
- });
119
- });
120
- step('Re-scanning to prove the danger is gone');
121
- // PROVE: re-scan and check the SAME danger (file + ruleId) is gone.
122
- const after = shipScan(dir);
123
- const stillThere = after.findings.some((f) => f.file === top.file && f.ruleId === top.ruleId);
124
- // A finding can only be proven closed by the scanner that FOUND it. If that scanner failed on the
125
- // re-scan, the danger "disappears" for the same reason a switched-off smoke alarm reports no fire.
126
- // Absence of a finding from a scanner that never ran is not proof, and this loop's whole value is
127
- // that its proof means something. (codehere's own vibeChecks run in-process, so they always ran.)
128
- const proverRan = top.tool === 'codehere' || after.ran[top.tool];
129
- if (!stillThere && !proverRan) {
130
- return {
131
- ok: true,
132
- provenClosed: false,
133
- message: `The AI rewrote ${top.file}, but codehere could not prove the fix: ${top.tool} failed to complete the re-scan, so the "${top.ruleId}" danger is unverified rather than gone. Re-run the scan once ${top.tool} is working.`,
134
- };
135
- }
136
- return {
137
- ok: true,
138
- provenClosed: !stillThere,
139
- message: !stillThere
140
- ? `Proven closed: the AI rewrote ${top.file} and codehere re-scanned it — the "${top.ruleId}" danger is gone. Give the change a quick look before you ship.`
141
- : `The AI tried, but codehere re-scanned and the "${top.ruleId}" danger is STILL in ${top.file}. Not fixed — this one needs your eyes (or a different fix).`,
142
- };
143
- }
144
- //# sourceMappingURL=fix-code.js.map
@@ -1,89 +0,0 @@
1
- /**
2
- * Deterministic "fix + prove closed" for an exposed secret — the #1 vibe-coder ship-blocker.
3
- * ZERO model tokens. Moves the hardcoded secret out of your shippable code (→ process.env + a
4
- * gitignored .env), then PROVES it by re-reading the source. Honest: you must still ROTATE the key
5
- * (moving it doesn't undo the exposure that already happened). See docs/PRODUCT_10X.md (R3).
6
- */
7
- import { spawnSync } from 'node:child_process';
8
- import { mkdtempSync, readFileSync, writeFileSync, existsSync, appendFileSync } from 'node:fs';
9
- import { tmpdir } from 'node:os';
10
- import { join, relative, isAbsolute } from 'node:path';
11
- function topSecret(dir) {
12
- const tmp = join(mkdtempSync(join(tmpdir(), 'fixsec-')), 'gl.json');
13
- spawnSync('gitleaks', ['detect', '--no-git', '--source', dir, '-f', 'json', '-r', tmp], { stdio: 'ignore' });
14
- if (!existsSync(tmp))
15
- return null;
16
- let arr;
17
- try {
18
- arr = JSON.parse(readFileSync(tmp, 'utf8'));
19
- }
20
- catch {
21
- return null;
22
- }
23
- if (!arr || !arr.length)
24
- return null;
25
- // Prefer a secret still living in shippable source (not one already moved into a .env).
26
- const f = arr.find((x) => !String(x.File || '').endsWith('.env')) || arr[0];
27
- return {
28
- file: String(f.File),
29
- secret: String(f.Secret),
30
- line: Number(f.StartLine || 0),
31
- ruleId: String(f.RuleID || 'secret'),
32
- };
33
- }
34
- function deriveVarName(line, ruleId) {
35
- const m = line.match(/(?:const|let|var)?\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*[:=]/);
36
- let name = m ? m[1] : `SECRET_${ruleId}`;
37
- name = name
38
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
39
- .toUpperCase()
40
- .replace(/[^A-Z0-9_]/g, '_');
41
- return name || 'APP_SECRET';
42
- }
43
- export function fixExposedSecret(dir) {
44
- const t = topSecret(dir);
45
- if (!t)
46
- return {
47
- ok: false,
48
- reason: 'no_secret',
49
- message: 'The top issue here is a code change, not a leaked key — auto-fix for code vulnerabilities is the next step (it needs an agent).',
50
- };
51
- if (!existsSync(t.file))
52
- return { ok: false, reason: 'file_gone', message: 'The flagged file is no longer there.' };
53
- let src = readFileSync(t.file, 'utf8');
54
- if (!src.includes(t.secret))
55
- return { ok: false, reason: 'not_found', message: 'Could not locate the secret in the file to replace.' };
56
- const lineText = src.split('\n').find((l) => l.includes(t.secret)) || '';
57
- const varName = deriveVarName(lineText, t.ruleId);
58
- // Replace the literal with process.env.VARNAME (quoted forms first, bare as a fallback).
59
- src = src
60
- .split(`"${t.secret}"`)
61
- .join(`process.env.${varName}`)
62
- .split(`'${t.secret}'`)
63
- .join(`process.env.${varName}`)
64
- .split(t.secret)
65
- .join(`process.env.${varName}`);
66
- writeFileSync(t.file, src);
67
- // Move the value to a gitignored .env.
68
- const env = join(dir, '.env');
69
- if (!existsSync(env) || !readFileSync(env, 'utf8').includes(`${varName}=`))
70
- appendFileSync(env, `${varName}=${t.secret}\n`);
71
- const gi = join(dir, '.gitignore');
72
- const giTxt = existsSync(gi) ? readFileSync(gi, 'utf8') : '';
73
- if (!/^\.env\s*$/m.test(giTxt))
74
- writeFileSync(gi, (giTxt && !giTxt.endsWith('\n') ? `${giTxt}\n` : giTxt) + '.env\n');
75
- // PROVE CLOSED: the secret must be gone from shippable source.
76
- const provenClosed = !readFileSync(t.file, 'utf8').includes(t.secret);
77
- const file = isAbsolute(t.file) ? relative(dir, t.file) : t.file;
78
- return {
79
- ok: true,
80
- file,
81
- varName,
82
- provenClosed,
83
- rotate: true,
84
- message: provenClosed
85
- ? `Proven closed: the secret is gone from ${file} — it now reads process.env.${varName}, and the value moved to .env (now gitignored, so it won't ship or get committed). ⚠ You MUST still rotate this key now — it was already exposed, so revoke the old value.`
86
- : `Moved the secret to .env, but couldn't fully verify removal from ${file} — please check it by hand.`,
87
- };
88
- }
89
- //# sourceMappingURL=fix-secret.js.map
@@ -1,235 +0,0 @@
1
- import { writeFileAtomic } from '../storage/atomic-write.js';
2
- /**
3
- * Per-cwd permissions config — P18 slice 1 primitive.
4
- *
5
- * Reads `.codehere/permissions.yaml` from a project's cwd and lets the
6
- * trust-scorer SKIP findings whose matched text fits an allow pattern.
7
- *
8
- * Use case: codehere blocks `rm -rf node_modules` because the pattern
9
- * matches `rm -rf /`. The user knows it's safe in THIS cwd. They run
10
- * `codehere permission allow "rm -rf node_modules"` (slice 2 CLI) and
11
- * future blocks for that exact pattern in this cwd are skipped.
12
- *
13
- * Schema (.codehere/permissions.yaml):
14
- *
15
- * allow:
16
- * - pattern: "rm -rf node_modules"
17
- * reason: "OK to nuke deps in this repo"
18
- * addedAt: 2026-05-08T20:00:00Z
19
- * - pattern: "ANTHROPIC_API_KEY"
20
- * reason: "Doc explains env vars; not actually leaking"
21
- *
22
- * Allow patterns are STRINGS not regex — exact substring match against
23
- * the audit-finding `matched` field. Keeps the schema simple +
24
- * non-technical-user friendly. Regex support is a v0.7 follow-up.
25
- *
26
- * Per claim_user_persona HIGH: the YAML is human-readable; non-
27
- * technical users can edit it directly. Per overconfidence-guard:
28
- * we DON'T claim the allow-list is "secure" — it's user CONSENT,
29
- * not codehere certification.
30
- */
31
- import { existsSync, readFileSync } from 'fs';
32
- import { join } from 'path';
33
- const EMPTY = { allow: [], deny: [] };
34
- /**
35
- * Load .codehere/permissions.yaml from a cwd. Returns empty config if
36
- * the file doesn't exist or fails to parse. Defensive: never throws —
37
- * caller treats absence as "no overrides", which is the safest default
38
- * (the scanner blocks normally).
39
- */
40
- export function loadPermissions(cwd) {
41
- if (!cwd || typeof cwd !== 'string')
42
- return EMPTY;
43
- const file = join(cwd, '.codehere', 'permissions.yaml');
44
- if (!existsSync(file))
45
- return EMPTY;
46
- let raw;
47
- try {
48
- raw = readFileSync(file, 'utf-8');
49
- }
50
- catch {
51
- return EMPTY;
52
- }
53
- return parsePermissionsYaml(raw);
54
- }
55
- /**
56
- * Tiny YAML subset parser — only handles the shape this file uses
57
- * (top-level allow/deny lists of {pattern, reason, addedAt} objects).
58
- * No external dep; the codehere repo already avoids the `yaml` package
59
- * for read paths to keep cold-start fast. Format errors yield empty
60
- * config (same posture as missing file).
61
- */
62
- export function parsePermissionsYaml(raw) {
63
- const cfg = { allow: [], deny: [] };
64
- let bucket = null;
65
- let pending = null;
66
- for (const line of raw.split('\n')) {
67
- const stripped = line.replace(/#.*$/, '').trimEnd();
68
- if (!stripped.trim())
69
- continue;
70
- // Top-level key: "allow:" or "deny:"
71
- const top = stripped.match(/^(allow|deny)\s*:\s*$/);
72
- if (top) {
73
- // Flush any pending entry into the previous bucket
74
- if (pending && bucket && pending.pattern)
75
- cfg[bucket].push(pending);
76
- pending = null;
77
- bucket = top[1];
78
- continue;
79
- }
80
- if (!bucket)
81
- continue;
82
- // List entry start: " - pattern: ..."
83
- const listStart = stripped.match(/^\s*-\s+pattern\s*:\s*(.+)$/);
84
- if (listStart) {
85
- if (pending && pending.pattern)
86
- cfg[bucket].push(pending);
87
- pending = { pattern: unquote(listStart[1]) };
88
- continue;
89
- }
90
- // Continuation field on a pending entry
91
- const cont = stripped.match(/^\s+(reason|addedAt)\s*:\s*(.+)$/);
92
- if (cont && pending) {
93
- const key = cont[1];
94
- pending[key] = unquote(cont[2]);
95
- }
96
- }
97
- if (pending && bucket && pending.pattern)
98
- cfg[bucket].push(pending);
99
- return cfg;
100
- }
101
- function unquote(s) {
102
- const t = s.trim();
103
- if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
104
- return t.slice(1, -1);
105
- }
106
- return t;
107
- }
108
- /**
109
- * Check whether a finding's matched text is allow-listed in this cwd.
110
- * Substring match — exact-text patterns are simpler for non-technical
111
- * users than regex. Returns the matching entry (for audit-trail
112
- * provenance) or null if no match.
113
- */
114
- export function isAllowed(matchedText, cwd) {
115
- const cfg = loadPermissions(cwd);
116
- for (const entry of cfg.allow) {
117
- if (entry.pattern && matchedText.includes(entry.pattern))
118
- return entry;
119
- }
120
- return null;
121
- }
122
- /**
123
- * Filter a findings list against the allow-list for a cwd. Returns
124
- * the findings that survived (i.e., were NOT allow-listed). Used by
125
- * the trust-scorer / pre-prompt-scan to drop findings that the user
126
- * has explicitly approved.
127
- *
128
- * Side-effect-free: the allow-listed findings are dropped from the
129
- * caller's perspective. The audit log writer can still record the
130
- * full finding list IF the caller wants (then mark which were
131
- * allow-listed). This function is the simple "what should we still
132
- * block on" filter.
133
- */
134
- export function filterAllowed(findings, cwd) {
135
- if (findings.length === 0)
136
- return { surviving: [], allowed: [] };
137
- const cfg = loadPermissions(cwd);
138
- if (cfg.allow.length === 0)
139
- return { surviving: findings, allowed: [] };
140
- const surviving = [];
141
- const allowed = [];
142
- for (const f of findings) {
143
- const hit = cfg.allow.find((e) => e.pattern && f.matched.includes(e.pattern));
144
- if (hit)
145
- allowed.push({ finding: f, entry: hit });
146
- else
147
- surviving.push(f);
148
- }
149
- return { surviving, allowed };
150
- }
151
- // ── Write surface (P18 slice 3) ─────────────────────────────────────────────
152
- // Shared by the CLI (`codehere permission allow ...`) and the HTTP write
153
- // endpoint (POST /api/permissions). Single source of truth for the
154
- // idempotent-add semantics + the YAML serializer.
155
- import { mkdirSync } from 'fs';
156
- /**
157
- * Idempotent add — if the pattern already exists in the bucket, returns
158
- * status='already_present' with the existing entry. Otherwise appends +
159
- * writes the file. Writes via Write tool semantics (overwrites the
160
- * whole YAML each time; entries are O(N) so re-serialization is cheap).
161
- */
162
- export function addPermissionEntry(input) {
163
- const bucket = input.bucket ?? 'allow';
164
- const cfg = loadPermissions(input.cwd);
165
- const list = cfg[bucket];
166
- const existing = list.find((e) => e.pattern === input.pattern);
167
- if (existing) {
168
- return {
169
- status: 'already_present',
170
- bucket,
171
- entry: existing,
172
- filePath: join(input.cwd, '.codehere', 'permissions.yaml'),
173
- };
174
- }
175
- const entry = {
176
- pattern: input.pattern,
177
- ...(input.reason ? { reason: input.reason } : {}),
178
- addedAt: new Date().toISOString(),
179
- };
180
- cfg[bucket].push(entry);
181
- const filePath = writePermissionsConfig(input.cwd, cfg);
182
- return { status: 'added', bucket, entry, filePath };
183
- }
184
- /**
185
- * Idempotent remove — drops every entry whose pattern matches across
186
- * BOTH buckets. Returns count of removed entries.
187
- */
188
- export function removePermissionEntry(cwd, pattern) {
189
- const cfg = loadPermissions(cwd);
190
- const before = cfg.allow.length + cfg.deny.length;
191
- cfg.allow = cfg.allow.filter((e) => e.pattern !== pattern);
192
- cfg.deny = cfg.deny.filter((e) => e.pattern !== pattern);
193
- const after = cfg.allow.length + cfg.deny.length;
194
- const removed = before - after;
195
- if (removed > 0)
196
- writePermissionsConfig(cwd, cfg);
197
- return { removed, filePath: join(cwd, '.codehere', 'permissions.yaml') };
198
- }
199
- function writePermissionsConfig(cwd, cfg) {
200
- const dir = join(cwd, '.codehere');
201
- if (!existsSync(dir))
202
- mkdirSync(dir, { recursive: true });
203
- const filePath = join(dir, 'permissions.yaml');
204
- writeFileAtomic(filePath, serializePermissionsYaml(cfg));
205
- return filePath;
206
- }
207
- export function serializePermissionsYaml(cfg) {
208
- const out = [];
209
- out.push('# codehere permissions — per-folder allow/deny list');
210
- out.push('# See: codehere permission --help');
211
- out.push('');
212
- for (const bucket of ['allow', 'deny']) {
213
- out.push(`${bucket}:`);
214
- if (cfg[bucket].length === 0) {
215
- out.push(' []');
216
- }
217
- else {
218
- for (const e of cfg[bucket]) {
219
- out.push(` - pattern: ${quoteYaml(e.pattern)}`);
220
- if (e.reason)
221
- out.push(` reason: ${quoteYaml(e.reason)}`);
222
- if (e.addedAt)
223
- out.push(` addedAt: ${quoteYaml(e.addedAt)}`);
224
- }
225
- }
226
- out.push('');
227
- }
228
- return out.join('\n');
229
- }
230
- function quoteYaml(s) {
231
- if (s.includes('"') && !s.includes("'"))
232
- return `'${s}'`;
233
- return `"${s.replace(/"/g, '\\"')}"`;
234
- }
235
- //# sourceMappingURL=permissions-config.js.map
@@ -1,10 +0,0 @@
1
- /**
2
- * The shape a scan finding takes, shared by everything that produces or consumes one.
3
- *
4
- * These lived in ship-scan.ts, which made vibe-checks.ts import from the module that imports
5
- * it — a type-only cycle, erased at compile time and harmless at runtime, but the release
6
- * checklist requires zero circular dependencies and a type-only cycle is still a cycle a
7
- * later value import can turn into a real one.
8
- */
9
- export {};
10
- //# sourceMappingURL=scan-types.js.map