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,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `codehere narrator` — read or customize the PM narrator prompt template.
|
|
3
|
-
*
|
|
4
|
-
* Subcommands:
|
|
5
|
-
* show — print active template + source (user / default)
|
|
6
|
-
* and the path codehere reads
|
|
7
|
-
* edit — write the default template to ~/.codehere/narrator-prompt.md
|
|
8
|
-
* if it doesn't already exist, then open in $EDITOR
|
|
9
|
-
* (or print the path if no $EDITOR set)
|
|
10
|
-
* reset — delete the user-edited file, fall back to default
|
|
11
|
-
*
|
|
12
|
-
* The template uses {{placeholder}} substitution. Required placeholders:
|
|
13
|
-
* {{prompt}}, {{agent}}, {{elapsed_seconds}}, {{steps}}
|
|
14
|
-
* Missing any → silent fallback to baked-in default at render time, so a
|
|
15
|
-
* typo never breaks narration.
|
|
16
|
-
*
|
|
17
|
-
* Per claim_user_persona HIGH: the narrator IS the agent's voice for
|
|
18
|
-
* non-technical users. Letting them control it is a v1 promise.
|
|
19
|
-
*/
|
|
20
|
-
import { spawnSync } from 'child_process';
|
|
21
|
-
import { existsSync, readFileSync } from 'fs';
|
|
22
|
-
import { loadTemplate, resetTemplate, templatePath, writeDefaultTemplate, DEFAULT_TEMPLATE, } from '../../../application/use-cases/narrator-prompt-template.js';
|
|
23
|
-
import { error, info, newline, success } from '../../../ui.js';
|
|
24
|
-
export async function executeNarratorCommand(subcommand) {
|
|
25
|
-
const sub = subcommand ?? 'show';
|
|
26
|
-
switch (sub) {
|
|
27
|
-
case 'show':
|
|
28
|
-
return showNarrator();
|
|
29
|
-
case 'edit':
|
|
30
|
-
return editNarrator();
|
|
31
|
-
case 'reset':
|
|
32
|
-
return resetNarrator();
|
|
33
|
-
default:
|
|
34
|
-
console.error(error('Unknown subcommand:'), sub);
|
|
35
|
-
console.error(info('Try:'), 'codehere narrator [show | edit | reset]');
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function showNarrator() {
|
|
40
|
-
const { source, template } = loadTemplate();
|
|
41
|
-
const path = templatePath();
|
|
42
|
-
newline();
|
|
43
|
-
console.log(info('Active template source:'), source === 'user' ? 'user-edited file' : 'baked-in default');
|
|
44
|
-
console.log(info('Path:'), path);
|
|
45
|
-
if (source === 'user') {
|
|
46
|
-
const sizeBytes = existsSync(path) ? readFileSync(path, 'utf-8').length : 0;
|
|
47
|
-
console.log(info('User file size:'), `${sizeBytes} bytes`);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
console.log(info('Customize with:'), 'codehere narrator edit');
|
|
51
|
-
}
|
|
52
|
-
newline();
|
|
53
|
-
console.log(info('Template (first 500 chars):'));
|
|
54
|
-
console.log(template.slice(0, 500) + (template.length > 500 ? '\n…(truncated)' : ''));
|
|
55
|
-
newline();
|
|
56
|
-
}
|
|
57
|
-
function editNarrator() {
|
|
58
|
-
const { wrote, path } = writeDefaultTemplate(false);
|
|
59
|
-
if (wrote) {
|
|
60
|
-
console.log(success('Wrote default template to:'), path);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
console.log(info('Existing template at:'), path);
|
|
64
|
-
}
|
|
65
|
-
const editor = process.env.EDITOR || process.env.VISUAL;
|
|
66
|
-
if (!editor) {
|
|
67
|
-
console.log(info('No $EDITOR set. Open this file in your editor of choice:'));
|
|
68
|
-
console.log(' ', path);
|
|
69
|
-
console.log(info('Then verify with:'), 'codehere narrator show');
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
console.log(info('Opening in $EDITOR:'), editor);
|
|
73
|
-
// Fire the editor synchronously, inheriting tty so the user can edit.
|
|
74
|
-
const result = spawnSync(editor, [path], { stdio: 'inherit' });
|
|
75
|
-
if (result.status !== 0) {
|
|
76
|
-
console.error(error('Editor exited non-zero.'), 'Edits (if any) still saved.');
|
|
77
|
-
}
|
|
78
|
-
// Reload + report — gives the user immediate confirmation their edits parsed.
|
|
79
|
-
const after = loadTemplate();
|
|
80
|
-
if (after.source === 'user') {
|
|
81
|
-
console.log(success('Active template:'), 'user-edited file (parses, all placeholders present).');
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
console.log(error('Template missing required placeholders.'), 'Falling back to default until fixed.');
|
|
85
|
-
console.log(info('Required:'), '{{prompt}}, {{agent}}, {{elapsed_seconds}}, {{steps}}');
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function resetNarrator() {
|
|
89
|
-
const { removed, path } = resetTemplate();
|
|
90
|
-
if (removed) {
|
|
91
|
-
console.log(success('Removed user-edited template.'), 'Narrator now uses the baked-in default.');
|
|
92
|
-
console.log(info('File was at:'), path);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
console.log(info('No user-edited template to remove.'), 'Already on the baked-in default.');
|
|
96
|
-
}
|
|
97
|
-
// Don't load DEFAULT_TEMPLATE for any side effect — just acknowledge it's the
|
|
98
|
-
// active source now. (Imported above so future test shape stays available.)
|
|
99
|
-
void DEFAULT_TEMPLATE;
|
|
100
|
-
}
|
|
101
|
-
//# sourceMappingURL=narrator-command.js.map
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `codehere permission` — manage per-cwd .codehere/permissions.yaml.
|
|
3
|
-
*
|
|
4
|
-
* Subcommands:
|
|
5
|
-
* list — show current allow/deny entries
|
|
6
|
-
* allow <pattern> [--reason X] — append an allow entry
|
|
7
|
-
* deny <pattern> [--reason X] — append a deny entry (reserved; no-op today)
|
|
8
|
-
* remove <pattern> — drop the matching entry from both buckets
|
|
9
|
-
*
|
|
10
|
-
* Always operates on the CURRENT working directory's
|
|
11
|
-
* .codehere/permissions.yaml. Creates the file if missing. Writes are
|
|
12
|
-
* idempotent — re-running `allow X` won't add a duplicate.
|
|
13
|
-
*
|
|
14
|
-
* Per claim_user_persona HIGH: the CLI is the developer escape hatch;
|
|
15
|
-
* the slice-2 web-UI panel (forthcoming) wraps the same primitive for
|
|
16
|
-
* non-technical users. The YAML format is the source of truth either way.
|
|
17
|
-
*/
|
|
18
|
-
import { existsSync, readFileSync } from 'fs';
|
|
19
|
-
import { join } from 'path';
|
|
20
|
-
import { loadPermissions, addPermissionEntry, removePermissionEntry, } from '../../../infrastructure/security/permissions-config.js';
|
|
21
|
-
import { colors, success, error, warning, info, newline } from '../../../ui.js';
|
|
22
|
-
export async function executePermissionCommand(subcommand, pattern, options = {}) {
|
|
23
|
-
const sub = subcommand ?? 'list';
|
|
24
|
-
switch (sub) {
|
|
25
|
-
case 'list':
|
|
26
|
-
return listPermissions();
|
|
27
|
-
case 'allow':
|
|
28
|
-
if (!pattern) {
|
|
29
|
-
console.error(error('Usage:'), 'codehere permission allow <pattern> [--reason "..."]');
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
return addEntry('allow', pattern, options.reason);
|
|
33
|
-
case 'deny':
|
|
34
|
-
if (!pattern) {
|
|
35
|
-
console.error(error('Usage:'), 'codehere permission deny <pattern> [--reason "..."]');
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
return addEntry('deny', pattern, options.reason);
|
|
39
|
-
case 'remove':
|
|
40
|
-
if (!pattern) {
|
|
41
|
-
console.error(error('Usage:'), 'codehere permission remove <pattern>');
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
return removeEntry(pattern);
|
|
45
|
-
default:
|
|
46
|
-
console.error(error('Unknown subcommand:'), sub);
|
|
47
|
-
console.error(info('Available:'), 'list · allow · deny · remove');
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function permissionsPath() {
|
|
52
|
-
return join(process.cwd(), '.codehere', 'permissions.yaml');
|
|
53
|
-
}
|
|
54
|
-
function listPermissions() {
|
|
55
|
-
const cfg = loadPermissions(process.cwd());
|
|
56
|
-
if (cfg.allow.length === 0 && cfg.deny.length === 0) {
|
|
57
|
-
console.log(colors.dim(' (no permissions.yaml in this folder yet)'));
|
|
58
|
-
console.log(colors.dim(' Run `codehere permission allow <pattern> --reason "..."` to add an entry.'));
|
|
59
|
-
newline();
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (cfg.allow.length > 0) {
|
|
63
|
-
console.log(success('allow:'));
|
|
64
|
-
for (const e of cfg.allow) {
|
|
65
|
-
console.log(` • ${e.pattern}`);
|
|
66
|
-
if (e.reason)
|
|
67
|
-
console.log(colors.dim(` reason: ${e.reason}`));
|
|
68
|
-
if (e.addedAt)
|
|
69
|
-
console.log(colors.dim(` added: ${e.addedAt}`));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (cfg.deny.length > 0) {
|
|
73
|
-
console.log(warning('deny (reserved — currently no-op):'));
|
|
74
|
-
for (const e of cfg.deny) {
|
|
75
|
-
console.log(` • ${e.pattern}`);
|
|
76
|
-
if (e.reason)
|
|
77
|
-
console.log(colors.dim(` reason: ${e.reason}`));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
newline();
|
|
81
|
-
}
|
|
82
|
-
function addEntry(bucket, pattern, reason) {
|
|
83
|
-
const result = addPermissionEntry({ cwd: process.cwd(), pattern, bucket, reason });
|
|
84
|
-
if (result.status === 'already_present') {
|
|
85
|
-
console.log(warning(`Pattern already in ${bucket}-list:`), pattern);
|
|
86
|
-
if (result.entry.reason)
|
|
87
|
-
console.log(colors.dim(` reason: ${result.entry.reason}`));
|
|
88
|
-
if (reason && reason !== result.entry.reason) {
|
|
89
|
-
console.log(colors.dim(' (use `codehere permission remove <pattern>` first to change the reason)'));
|
|
90
|
-
}
|
|
91
|
-
newline();
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
console.log(success(`Added to ${bucket}-list:`), pattern);
|
|
95
|
-
if (reason)
|
|
96
|
-
console.log(colors.dim(` reason: ${reason}`));
|
|
97
|
-
console.log(colors.dim(` file: ${result.filePath}`));
|
|
98
|
-
newline();
|
|
99
|
-
}
|
|
100
|
-
function removeEntry(pattern) {
|
|
101
|
-
const { removed } = removePermissionEntry(process.cwd(), pattern);
|
|
102
|
-
if (removed === 0) {
|
|
103
|
-
console.log(warning('No entry found for pattern:'), pattern);
|
|
104
|
-
newline();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
console.log(success(`Removed ${removed} entr${removed === 1 ? 'y' : 'ies'} for:`), pattern);
|
|
108
|
-
newline();
|
|
109
|
-
}
|
|
110
|
-
// Ensure file is read on disk after writes (smoke test in unit tests)
|
|
111
|
-
export function _readRaw() {
|
|
112
|
-
if (!existsSync(permissionsPath()))
|
|
113
|
-
return '';
|
|
114
|
-
return readFileSync(permissionsPath(), 'utf-8');
|
|
115
|
-
}
|
|
116
|
-
//# sourceMappingURL=permission-command.js.map
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* F3 Task 7 — protocols/hook_patterns.json exporter.
|
|
3
|
-
*
|
|
4
|
-
* Reads the trust-scorer's CODE_PATTERNS bank (the in-code TypeScript
|
|
5
|
-
* source of truth for the safety hook's pattern matchers) and exports it
|
|
6
|
-
* to .codehere/protocols/hook_patterns.json so the patterns are:
|
|
7
|
-
*
|
|
8
|
-
* - inspectable (auditors, security reviewers can read the JSON)
|
|
9
|
-
* - version-controllable (the file lives in the repo)
|
|
10
|
-
* - shareable (cross-tool: e.g. PR our patterns into the
|
|
11
|
-
* agentic-stack ecosystem per ADR-006)
|
|
12
|
-
* - extensible (project-specific patterns under `patterns_project[]`
|
|
13
|
-
* survive re-exports)
|
|
14
|
-
*
|
|
15
|
-
* Schema split (regenerable / preserved):
|
|
16
|
-
* patterns_builtin[] — regenerated from CODE_PATTERNS on each export
|
|
17
|
-
* patterns_project[] — user-edited; preserved across re-exports
|
|
18
|
-
*
|
|
19
|
-
* Each pattern is identified by a stable `id` slugified from category +
|
|
20
|
-
* description. Same code → same id, so users can reference patterns
|
|
21
|
-
* deterministically (e.g. "ignore the `secrets-hardcoded-credential`
|
|
22
|
-
* pattern in this folder").
|
|
23
|
-
*
|
|
24
|
-
* Forward-compatibility: regex is serialized as a JSON-portable string
|
|
25
|
-
* (no special characters that would break JSON parsing); the flags are
|
|
26
|
-
* a separate field. This avoids the JSON-vs-RegExp impedance mismatch.
|
|
27
|
-
*/
|
|
28
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
29
|
-
import { join, dirname } from 'path';
|
|
30
|
-
import { CODE_PATTERNS } from '../../../infrastructure/security/trust-scorer.js';
|
|
31
|
-
/**
|
|
32
|
-
* Slugify category + description into a stable id.
|
|
33
|
-
*
|
|
34
|
-
* "Secrets" + "Hardcoded credential" → "secrets-hardcoded-credential"
|
|
35
|
-
* "SQL Injection" + "String interpolation in SQL query" → "sql-injection-string-interpolation-in-sql-query"
|
|
36
|
-
*
|
|
37
|
-
* Stability matters: re-exports must produce the same id from the same
|
|
38
|
-
* source. Cite this slug in test fixtures, downstream tools, project
|
|
39
|
-
* overrides etc.
|
|
40
|
-
*/
|
|
41
|
-
function slugifyPatternId(category, description) {
|
|
42
|
-
return (category + ' ' + description)
|
|
43
|
-
.toLowerCase()
|
|
44
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
45
|
-
.replace(/^-+|-+$/g, '');
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Serialize a JS RegExp to a JSON-portable {regex, flags} pair.
|
|
49
|
-
* `regex` is the source string (without surrounding `/`); `flags` is the
|
|
50
|
-
* flags string ('gi' etc.).
|
|
51
|
-
*/
|
|
52
|
-
function serializeRegex(re) {
|
|
53
|
-
return { regex: re.source, flags: re.flags };
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Assign stable, source-order-independent ids to a list of patterns.
|
|
57
|
-
*
|
|
58
|
-
* The sort key is (category, description, regex.source) — a 3-tier
|
|
59
|
-
* comparator. The first two tiers come from K2 Round 1 (sort by
|
|
60
|
-
* category + description so duplicates get deterministic suffix
|
|
61
|
-
* assignment). The 3rd tier (regex source) is the codex Round 2 fix:
|
|
62
|
-
* (category, description) alone leaves stable-sort fall-through to
|
|
63
|
-
* source order when two patterns share both fields, which means
|
|
64
|
-
* reordering CODE_PATTERNS would still flip which one gets the base id
|
|
65
|
-
* vs `-2`. Adding regex.source disambiguates definitively — two patterns
|
|
66
|
-
* with identical category + description + regex are the SAME pattern;
|
|
67
|
-
* they can't both exist.
|
|
68
|
-
*
|
|
69
|
-
* Returns a new sorted array with `id` attached. Pure; safe to test
|
|
70
|
-
* with arbitrary fixtures.
|
|
71
|
-
*/
|
|
72
|
-
export function assignStableIds(patterns) {
|
|
73
|
-
const sorted = [...patterns].sort((a, b) => {
|
|
74
|
-
const c = a.category.localeCompare(b.category);
|
|
75
|
-
if (c !== 0)
|
|
76
|
-
return c;
|
|
77
|
-
const d = a.description.localeCompare(b.description);
|
|
78
|
-
if (d !== 0)
|
|
79
|
-
return d;
|
|
80
|
-
return a.regex.source.localeCompare(b.regex.source);
|
|
81
|
-
});
|
|
82
|
-
const seenIds = new Map();
|
|
83
|
-
return sorted.map((p) => {
|
|
84
|
-
const baseId = slugifyPatternId(p.category, p.description);
|
|
85
|
-
const seen = seenIds.get(baseId) ?? 0;
|
|
86
|
-
seenIds.set(baseId, seen + 1);
|
|
87
|
-
const id = seen === 0 ? baseId : `${baseId}-${seen + 1}`;
|
|
88
|
-
return { ...p, id };
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Build the export object from CODE_PATTERNS. Pure — does not touch disk.
|
|
93
|
-
*
|
|
94
|
-
* Delegates id assignment to `assignStableIds` so the source-order-
|
|
95
|
-
* independence property has a direct unit test (see protocols-export.test.ts).
|
|
96
|
-
*/
|
|
97
|
-
export function buildHookPatternsExport() {
|
|
98
|
-
const withIds = assignStableIds(CODE_PATTERNS);
|
|
99
|
-
const builtin = withIds.map((p) => {
|
|
100
|
-
const { regex, flags } = serializeRegex(p.regex);
|
|
101
|
-
const out = {
|
|
102
|
-
id: p.id,
|
|
103
|
-
regex,
|
|
104
|
-
regex_flags: flags,
|
|
105
|
-
severity: p.severity,
|
|
106
|
-
category: p.category,
|
|
107
|
-
description: p.description,
|
|
108
|
-
};
|
|
109
|
-
if (p.cwe)
|
|
110
|
-
out.cwe = p.cwe;
|
|
111
|
-
if (p.scanRaw)
|
|
112
|
-
out.scanRaw = p.scanRaw;
|
|
113
|
-
if (p.requiresContext) {
|
|
114
|
-
const ctx = serializeRegex(p.requiresContext);
|
|
115
|
-
out.requiresContext = { regex: ctx.regex, flags: ctx.flags };
|
|
116
|
-
}
|
|
117
|
-
return out;
|
|
118
|
-
});
|
|
119
|
-
return {
|
|
120
|
-
version: 'v0.5',
|
|
121
|
-
generatedAt: new Date().toISOString(),
|
|
122
|
-
source: 'agent/src/infrastructure/security/trust-scorer.ts',
|
|
123
|
-
_note: "Exported from codehere's trust-scorer pattern bank via 'codehere protocols export'. " +
|
|
124
|
-
"DO NOT hand-edit `patterns_builtin[]` — it's regenerated on every export. " +
|
|
125
|
-
'Add project-specific patterns to `patterns_project[]` — those are preserved across re-exports. ' +
|
|
126
|
-
'Pattern ids are stable slugs of category + description; reference them deterministically.',
|
|
127
|
-
patterns_builtin: builtin,
|
|
128
|
-
patterns_project: [],
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Detect whether the on-disk file is the init-time placeholder.
|
|
133
|
-
*
|
|
134
|
-
* Two placeholder shapes are recognized:
|
|
135
|
-
*
|
|
136
|
-
* 1. POST-2026-04-28 seed (current init) — split schema, empty arrays:
|
|
137
|
-
* { version, patterns_builtin: [], patterns_project: [], _note }
|
|
138
|
-
*
|
|
139
|
-
* 2. PRE-2026-04-28 seed (legacy init) — single empty `patterns` array:
|
|
140
|
-
* { version, patterns: [], _note }
|
|
141
|
-
*
|
|
142
|
-
* Either one is safe to overwrite without --force. A NON-empty legacy
|
|
143
|
-
* `patterns` array is NOT a placeholder — see `extractLegacyProjectPatterns`
|
|
144
|
-
* below which migrates user content into `patterns_project[]` rather than
|
|
145
|
-
* silently discarding it (codex Round 1 fix — the legacy seed's _note
|
|
146
|
-
* explicitly told users to add patterns to the legacy field, so users may
|
|
147
|
-
* have followed that guidance).
|
|
148
|
-
*/
|
|
149
|
-
function isInitPlaceholder(parsed) {
|
|
150
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
151
|
-
return false;
|
|
152
|
-
const obj = parsed;
|
|
153
|
-
// Current shape: split schema, both arrays empty.
|
|
154
|
-
if (Array.isArray(obj.patterns_builtin) &&
|
|
155
|
-
obj.patterns_builtin.length === 0 &&
|
|
156
|
-
Array.isArray(obj.patterns_project) &&
|
|
157
|
-
obj.patterns_project.length === 0) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
// Legacy shape: single empty `patterns` array, no _builtin/_project.
|
|
161
|
-
if (Array.isArray(obj.patterns) &&
|
|
162
|
-
obj.patterns.length === 0 &&
|
|
163
|
-
obj.patterns_builtin === undefined &&
|
|
164
|
-
obj.patterns_project === undefined) {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Extract user-added patterns from the LEGACY `patterns[]` field, if any.
|
|
171
|
-
* Used to migrate non-empty legacy files into `patterns_project[]` so users
|
|
172
|
-
* who followed the legacy seed's _note advice ("add patterns to `patterns`")
|
|
173
|
-
* don't lose their work on first export (codex Round 1 fix).
|
|
174
|
-
*
|
|
175
|
-
* Returns `null` if the file isn't a legacy-shape file with non-empty
|
|
176
|
-
* patterns; callers fall through to the normal --rebuild path which reads
|
|
177
|
-
* `patterns_project[]` directly.
|
|
178
|
-
*/
|
|
179
|
-
function extractLegacyProjectPatterns(parsed) {
|
|
180
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
181
|
-
return null;
|
|
182
|
-
const obj = parsed;
|
|
183
|
-
if (Array.isArray(obj.patterns) &&
|
|
184
|
-
obj.patterns.length > 0 &&
|
|
185
|
-
obj.patterns_builtin === undefined &&
|
|
186
|
-
obj.patterns_project === undefined) {
|
|
187
|
-
return obj.patterns;
|
|
188
|
-
}
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Disk-side wrapper: build + write to .codehere/protocols/hook_patterns.json.
|
|
193
|
-
*
|
|
194
|
-
* If the existing file is the init-time placeholder, overwrite without
|
|
195
|
-
* requiring --rebuild. Otherwise:
|
|
196
|
-
* - existing file present + no --rebuild → 'exists' (refuse)
|
|
197
|
-
* - existing file present + --rebuild → preserve patterns_project[],
|
|
198
|
-
* regenerate patterns_builtin[]
|
|
199
|
-
* - no existing file → write fresh
|
|
200
|
-
*/
|
|
201
|
-
export function writeHookPatternsExport(codehereDir, options = {}) {
|
|
202
|
-
const targetPath = join(codehereDir, 'protocols/hook_patterns.json');
|
|
203
|
-
let preservedProject = [];
|
|
204
|
-
if (existsSync(targetPath)) {
|
|
205
|
-
let parsed;
|
|
206
|
-
try {
|
|
207
|
-
parsed = JSON.parse(readFileSync(targetPath, 'utf8'));
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
return { result: 'failed', path: targetPath, error: 'existing file is not valid JSON' };
|
|
211
|
-
}
|
|
212
|
-
// Codex Round 1 fix: migrate non-empty legacy `patterns[]` content into
|
|
213
|
-
// `patterns_project[]` BEFORE the placeholder/rebuild branches. The
|
|
214
|
-
// legacy init seed's _note told users to add patterns to the legacy
|
|
215
|
-
// field; a fresh export must preserve any content they added rather
|
|
216
|
-
// than silently discard it.
|
|
217
|
-
const legacy = extractLegacyProjectPatterns(parsed);
|
|
218
|
-
if (legacy !== null) {
|
|
219
|
-
preservedProject = legacy;
|
|
220
|
-
// Fall through to writing — we treat a non-empty-legacy file as
|
|
221
|
-
// overwriteable (the legacy patterns now live in preservedProject).
|
|
222
|
-
}
|
|
223
|
-
else if (isInitPlaceholder(parsed)) {
|
|
224
|
-
// Empty placeholder (either schema shape) — overwrite without --rebuild.
|
|
225
|
-
}
|
|
226
|
-
else if (!options.rebuild) {
|
|
227
|
-
return { result: 'exists', path: targetPath };
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
// --rebuild + non-placeholder, non-legacy existing file → preserve
|
|
231
|
-
// patterns_project[].
|
|
232
|
-
const obj = parsed;
|
|
233
|
-
if (Array.isArray(obj.patterns_project)) {
|
|
234
|
-
preservedProject = obj.patterns_project;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const built = buildHookPatternsExport();
|
|
239
|
-
built.patterns_project = preservedProject;
|
|
240
|
-
try {
|
|
241
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
242
|
-
writeFileSync(targetPath, JSON.stringify(built, null, 2) + '\n');
|
|
243
|
-
}
|
|
244
|
-
catch (e) {
|
|
245
|
-
return { result: 'failed', path: targetPath, error: e instanceof Error ? e.message : String(e) };
|
|
246
|
-
}
|
|
247
|
-
return {
|
|
248
|
-
result: 'exported',
|
|
249
|
-
path: targetPath,
|
|
250
|
-
builtinCount: built.patterns_builtin.length,
|
|
251
|
-
projectCount: built.patterns_project.length,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
//# sourceMappingURL=protocols-builder.js.map
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Protocols Command — F3 v0.5 protocols layer operations.
|
|
3
|
-
*
|
|
4
|
-
* Today: `codehere protocols export` writes the trust-scorer's pattern
|
|
5
|
-
* bank to .codehere/protocols/hook_patterns.json. Future: `protocols
|
|
6
|
-
* import`, `protocols verify`, `protocols diff` etc.
|
|
7
|
-
*
|
|
8
|
-
* The dispatch shape mirrors `memory-command.ts` so future actions slot
|
|
9
|
-
* in without touching index.ts beyond a single .option() addition.
|
|
10
|
-
*/
|
|
11
|
-
import { existsSync } from 'fs';
|
|
12
|
-
import { join } from 'path';
|
|
13
|
-
import { colors, success, info, error, newline } from '../../../ui.js';
|
|
14
|
-
import { findCodebaseRoot } from '../../../domain/services/codebase-detector.js';
|
|
15
|
-
import { writeHookPatternsExport } from './protocols-builder.js';
|
|
16
|
-
function executeProtocolsExport(options) {
|
|
17
|
-
const codeInfo = findCodebaseRoot();
|
|
18
|
-
const root = codeInfo?.rootPath ?? process.cwd();
|
|
19
|
-
const codehereDir = join(root, '.codehere');
|
|
20
|
-
newline();
|
|
21
|
-
console.log(colors.bold('codehere protocols export'));
|
|
22
|
-
console.log(colors.dim(` exporting trust-scorer pattern bank → ${codehereDir}/protocols/hook_patterns.json`));
|
|
23
|
-
newline();
|
|
24
|
-
if (!existsSync(codehereDir)) {
|
|
25
|
-
console.log(` ${error('refused')} .codehere/ not found at this root.`);
|
|
26
|
-
console.log(` ${colors.dim('Run')} ${colors.cyan('codehere init')} ${colors.dim('first.')}`);
|
|
27
|
-
newline();
|
|
28
|
-
process.exitCode = 1;
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const result = writeHookPatternsExport(codehereDir, { rebuild: !!options.rebuild });
|
|
32
|
-
switch (result.result) {
|
|
33
|
-
case 'exported': {
|
|
34
|
-
const projectNote = result.projectCount && result.projectCount > 0
|
|
35
|
-
? ` · ${result.projectCount} project pattern${result.projectCount === 1 ? '' : 's'} preserved`
|
|
36
|
-
: '';
|
|
37
|
-
console.log(` ${success('exported')} ${result.path} ${colors.dim(`(${result.builtinCount} built-in patterns${projectNote})`)}`);
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
case 'exists':
|
|
41
|
-
console.log(` ${info('exists ')} ${result.path} ${colors.dim('(use --rebuild to refresh built-in patterns; project patterns are preserved)')}`);
|
|
42
|
-
break;
|
|
43
|
-
case 'failed':
|
|
44
|
-
console.log(` ${error('failed ')} ${result.path} ${colors.dim(`(${result.error})`)}`);
|
|
45
|
-
process.exitCode = 1;
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
newline();
|
|
49
|
-
console.log(colors.bold('Next:'));
|
|
50
|
-
console.log(` - Inspect ${colors.cyan('.codehere/protocols/hook_patterns.json')} to see what the safety hook matches`);
|
|
51
|
-
console.log(` - Add project-specific patterns under ${colors.cyan('patterns_project[]')} — they survive re-exports`);
|
|
52
|
-
console.log(` ${colors.dim('(Edit patterns_builtin[] is a no-op — re-exports overwrite it from trust-scorer.ts.)')}`);
|
|
53
|
-
newline();
|
|
54
|
-
}
|
|
55
|
-
export function executeProtocolsCommand(action, options = {}) {
|
|
56
|
-
try {
|
|
57
|
-
switch (action) {
|
|
58
|
-
case 'export':
|
|
59
|
-
executeProtocolsExport(options);
|
|
60
|
-
return;
|
|
61
|
-
case undefined:
|
|
62
|
-
case 'help':
|
|
63
|
-
console.log(`
|
|
64
|
-
codehere protocols <action> — F3 protocols layer operations
|
|
65
|
-
|
|
66
|
-
export Export trust-scorer pattern bank to .codehere/protocols/hook_patterns.json
|
|
67
|
-
|
|
68
|
-
Coming later:
|
|
69
|
-
import Apply patterns from a file (cross-tool ingestion)
|
|
70
|
-
verify Check that the on-disk file matches the in-code pattern bank
|
|
71
|
-
`);
|
|
72
|
-
return;
|
|
73
|
-
default:
|
|
74
|
-
console.error(`\n ${error('failed')} unknown protocols action: ${action}\n Try: codehere protocols help\n`);
|
|
75
|
-
process.exitCode = 1;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch (e) {
|
|
79
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
80
|
-
console.error(`\n ${error('failed')} codehere protocols ${action ?? ''} — ${msg}\n`);
|
|
81
|
-
process.exitCode = 1;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
//# sourceMappingURL=protocols-command.js.map
|