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,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-domain trust grade for W-PASS write targets · v0.9 must-ship.
|
|
3
|
-
*
|
|
4
|
-
* Demo v12 + locked v1 tier scope: "linear.app A · *.atlassian.net B ·
|
|
5
|
-
* unknown F · first-write confirmation per domain."
|
|
6
|
-
*
|
|
7
|
-
* Two pieces of state:
|
|
8
|
-
* 1. WELL_KNOWN_GRADES · hardcoded · the platforms we've vetted ourselves
|
|
9
|
-
* ship with grade A. Subdomain wildcards get B. Everything else
|
|
10
|
-
* defaults to F (forces explicit user confirmation on first write).
|
|
11
|
-
* 2. USER_TRUSTED_DOMAINS · ~/.codehere/keys/trusted-domains.json ·
|
|
12
|
-
* domains the user has explicitly trusted after a first-write prompt.
|
|
13
|
-
* Domains here promote from F → B.
|
|
14
|
-
*
|
|
15
|
-
* Pure logic. CLI + HTTP wrap it.
|
|
16
|
-
*/
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
18
|
-
import { homedir } from 'os';
|
|
19
|
-
import { dirname, join } from 'path';
|
|
20
|
-
const WELL_KNOWN_GRADES = [
|
|
21
|
-
// Grade A · platforms codehere has shipped+tested adapters for
|
|
22
|
-
{ pattern: /^api\.linear\.app$/i, grade: 'A', reason: 'official Linear API · codehere ships a vetted adapter' },
|
|
23
|
-
{ pattern: /^api\.github\.com$/i, grade: 'A', reason: 'official GitHub API · codehere ships a vetted adapter' },
|
|
24
|
-
{ pattern: /^api\.figma\.com$/i, grade: 'A', reason: 'official Figma API · codehere ships a vetted adapter' },
|
|
25
|
-
// Grade B · same vendor, surrounding subdomains
|
|
26
|
-
{ pattern: /^[a-z0-9-]+\.linear\.app$/i, grade: 'B', reason: 'Linear subdomain · adjacent to the vetted API' },
|
|
27
|
-
{ pattern: /^[a-z0-9-]+\.github\.com$/i, grade: 'B', reason: 'GitHub subdomain · adjacent to the vetted API' },
|
|
28
|
-
{ pattern: /^[a-z0-9-]+\.figma\.com$/i, grade: 'B', reason: 'Figma subdomain · adjacent to the vetted API' },
|
|
29
|
-
{
|
|
30
|
-
pattern: /^[a-z0-9-]+\.atlassian\.net$/i,
|
|
31
|
-
grade: 'B',
|
|
32
|
-
reason: 'Atlassian-hosted instance · vendor known but instance not vetted',
|
|
33
|
-
},
|
|
34
|
-
// Grade C · public hosts known to be commonly used
|
|
35
|
-
{ pattern: /^raw\.githubusercontent\.com$/i, grade: 'C', reason: 'GitHub-hosted raw content · static, read-mostly' },
|
|
36
|
-
];
|
|
37
|
-
function trustedDomainsPath() {
|
|
38
|
-
const home = process.env.CODEHERE_HOME || homedir();
|
|
39
|
-
return join(home, '.codehere', 'keys', 'trusted-domains.json');
|
|
40
|
-
}
|
|
41
|
-
export function loadTrustedDomains() {
|
|
42
|
-
const path = trustedDomainsPath();
|
|
43
|
-
if (!existsSync(path))
|
|
44
|
-
return { schema: 1, domains: {} };
|
|
45
|
-
try {
|
|
46
|
-
const raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
47
|
-
if (raw && raw.schema === 1 && typeof raw.domains === 'object')
|
|
48
|
-
return raw;
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
/* fall through · empty trust store */
|
|
52
|
-
}
|
|
53
|
-
return { schema: 1, domains: {} };
|
|
54
|
-
}
|
|
55
|
-
export function trustDomain(host, note) {
|
|
56
|
-
const file = loadTrustedDomains();
|
|
57
|
-
file.domains[host.toLowerCase()] = { addedAt: Date.now(), note };
|
|
58
|
-
mkdirSync(dirname(trustedDomainsPath()), { recursive: true });
|
|
59
|
-
writeFileSync(trustedDomainsPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
|
|
60
|
-
}
|
|
61
|
-
export function untrustDomain(host) {
|
|
62
|
-
const file = loadTrustedDomains();
|
|
63
|
-
const key = host.toLowerCase();
|
|
64
|
-
if (!file.domains[key])
|
|
65
|
-
return false;
|
|
66
|
-
delete file.domains[key];
|
|
67
|
-
writeFileSync(trustedDomainsPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
export function gradeDomain(host) {
|
|
71
|
-
const userFile = loadTrustedDomains();
|
|
72
|
-
const userTrusted = !!userFile.domains[host.toLowerCase()];
|
|
73
|
-
for (const rule of WELL_KNOWN_GRADES) {
|
|
74
|
-
if (rule.pattern.test(host)) {
|
|
75
|
-
return {
|
|
76
|
-
host,
|
|
77
|
-
grade: rule.grade,
|
|
78
|
-
reason: rule.reason,
|
|
79
|
-
userTrusted,
|
|
80
|
-
requiresFirstWriteConfirmation: false,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (userTrusted) {
|
|
85
|
-
return {
|
|
86
|
-
host,
|
|
87
|
-
grade: 'B',
|
|
88
|
-
reason: 'previously trusted by you · stored in trusted-domains.json',
|
|
89
|
-
userTrusted: true,
|
|
90
|
-
requiresFirstWriteConfirmation: false,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
host,
|
|
95
|
-
grade: 'F',
|
|
96
|
-
reason: 'unknown domain · not in well-known list · not previously trusted',
|
|
97
|
-
userTrusted: false,
|
|
98
|
-
requiresFirstWriteConfirmation: true,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/** Extract the host from a fully-resolved HTTPS URL. */
|
|
102
|
-
export function hostOf(url) {
|
|
103
|
-
try {
|
|
104
|
-
return new URL(url).host;
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return '';
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
//# sourceMappingURL=domain-trust.js.map
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Final Report — the headline deliverable. At task exit, produce a plain-English
|
|
3
|
-
* summary of what the agent did.
|
|
4
|
-
*
|
|
5
|
-
* Engine (locked 2026-06-03, after the real dogfood found NO report on a real
|
|
6
|
-
* conductor run): REUSE THE AGENT SESSION. The agent already narrates what it
|
|
7
|
-
* did (kind:'say' steps — e.g. "Added subtract(a,b) to utils.js:5"); we use that
|
|
8
|
-
* directly. No fresh summarizer spawn.
|
|
9
|
-
*
|
|
10
|
-
* Why the old path was replaced: it spawned a SEPARATE provider binary chain
|
|
11
|
-
* (`claude-free` → `claude`) to summarize. `claude-free` (the FCC/NIM proxy
|
|
12
|
-
* binary) is on PATH but its proxy was down → 30s hang → null; the `claude`
|
|
13
|
-
* fallback also returned null in the conductor exit-handler. Net: a completed
|
|
14
|
-
* real task showed "⚠ Final Report unavailable" — the headline deliverable
|
|
15
|
-
* missing. Reusing the agent's own words is cheaper, has full context, and can't
|
|
16
|
-
* be taken down by an external proxy.
|
|
17
|
-
*
|
|
18
|
-
* Fallback (locked): when the agent left no usable summary (or it looks like a
|
|
19
|
-
* provider failure), build a DETERMINISTIC report from the recorded steps +
|
|
20
|
-
* artifacts. It states ONLY verified facts, says plainly that no written summary
|
|
21
|
-
* was available, and NEVER implies success — so a completed task always shows a
|
|
22
|
-
* deliverable, and it's honest about which kind it is (see `source`).
|
|
23
|
-
*/
|
|
24
|
-
const MAX_SUMMARY_CHARS = 1600;
|
|
25
|
-
/**
|
|
26
|
-
* UAT-003 — patterns indicating the agent's own text is actually a network /
|
|
27
|
-
* auth / quota failure, not a real summary. Such "summaries" must NOT be shown
|
|
28
|
-
* as the report; they fall through to the deterministic fallback.
|
|
29
|
-
*/
|
|
30
|
-
const NETWORK_FAILURE_PATTERNS = /(?:all connection attempts failed|ECONNREFUSED|ENOTFOUND|fetch failed|network error|request id:\s*req_|connection timed out|getaddrinfo)/i;
|
|
31
|
-
function isLikelyNetworkFailureSummary(text) {
|
|
32
|
-
return NETWORK_FAILURE_PATTERNS.test(text);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* ENGINE: the agent's own narration. kind:'say' steps are the words the agent
|
|
36
|
-
* already emitted (reasoning + wrap-up). Joined newest-context-last, they ARE
|
|
37
|
-
* the summary — no extra spawn. Returns null if the agent said nothing usable.
|
|
38
|
-
*/
|
|
39
|
-
function extractAgentSummary(steps) {
|
|
40
|
-
const says = steps
|
|
41
|
-
.filter((s) => s.kind === 'say' && typeof s.label === 'string' && s.label.trim().length > 0)
|
|
42
|
-
.map((s) => s.label.trim());
|
|
43
|
-
if (!says.length)
|
|
44
|
-
return null;
|
|
45
|
-
const text = says.join('\n\n').slice(0, MAX_SUMMARY_CHARS);
|
|
46
|
-
// Too short to be a real summary (e.g. a one-word ack) → let the fallback give facts.
|
|
47
|
-
return text.length >= 12 ? text : null;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* FALLBACK: deterministic, honest. Facts only, from steps + artifacts. Says
|
|
51
|
-
* plainly that no written summary was available. Never implies success.
|
|
52
|
-
*/
|
|
53
|
-
function buildDeterministicReport(input) {
|
|
54
|
-
const files = input.artifacts?.files ?? [];
|
|
55
|
-
const toolSteps = input.steps.filter((s) => s.kind !== 'say');
|
|
56
|
-
const noGit = input.artifacts?.noGitTracking;
|
|
57
|
-
const agentName = input.agent === 'conductor' ? 'the agent codehere chose' : input.agent;
|
|
58
|
-
const parts = [
|
|
59
|
-
`No plain-English summary was available for this run — ${agentName} didn't leave a written summary, and codehere doesn't fabricate one. Here are the recorded facts.`,
|
|
60
|
-
`The session ran ${input.steps.length} step${input.steps.length === 1 ? '' : 's'}` +
|
|
61
|
-
(toolSteps.length
|
|
62
|
-
? ` (${toolSteps.slice(0, 5).map((s) => s.label).join(', ')}${toolSteps.length > 5 ? ', …' : ''})`
|
|
63
|
-
: '') +
|
|
64
|
-
`.`,
|
|
65
|
-
];
|
|
66
|
-
if (noGit) {
|
|
67
|
-
parts.push(`This folder isn't a git repository, so codehere couldn't track which files changed — check the working tree yourself.`);
|
|
68
|
-
}
|
|
69
|
-
else if (files.length) {
|
|
70
|
-
parts.push(`Files changed: ` +
|
|
71
|
-
files
|
|
72
|
-
.map((f) => `${f.path} (+${f.added}/-${f.removed}${f.trust !== undefined ? `, trust ${f.trust}` : ''})`)
|
|
73
|
-
.join('; ') +
|
|
74
|
-
`.`);
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
parts.push(`No files were changed.`);
|
|
78
|
-
}
|
|
79
|
-
return { summary: parts.join('\n\n'), generatedAt: Date.now(), durationMs: 0, source: 'deterministic' };
|
|
80
|
-
}
|
|
81
|
-
function buildErrorRecoveryReport(input, durationMs) {
|
|
82
|
-
const agentName = input.agent === 'conductor' ? 'the agent codehere chose' : input.agent;
|
|
83
|
-
return {
|
|
84
|
-
summary: `The ${agentName} session ended without completing the task. ` +
|
|
85
|
-
`This usually means a network hiccup, an expired login, or a rate limit on the provider.\n\n` +
|
|
86
|
-
`What to try: check your internet, run \`codehere health\` to confirm reachability + auth, ` +
|
|
87
|
-
`or wait a minute and rerun the same prompt. If it keeps happening, try a different agent ` +
|
|
88
|
-
`from the dropdown — codehere is vendor-neutral on purpose.\n\n` +
|
|
89
|
-
`codehere couldn't confirm any file changes from this attempt.`,
|
|
90
|
-
generatedAt: Date.now(),
|
|
91
|
-
durationMs,
|
|
92
|
-
source: 'error-recovery',
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Generate a Final Report. Reuses the agent's own summary; falls back to a
|
|
97
|
-
* deterministic, honest facts report. A completed task ALWAYS gets a report
|
|
98
|
-
* (never null for non-error terminal states) — no external dependency.
|
|
99
|
-
*/
|
|
100
|
-
export async function generateFinalReport(input) {
|
|
101
|
-
// No mock short-circuit: under CODEHERE_MOCK the SAME engine runs, so the
|
|
102
|
-
// narration gap is reproduced faithfully — claude/aider (emit `say`) get a
|
|
103
|
-
// rich agent-summary; codex/gemini/opencode (no `say`) get the honest
|
|
104
|
-
// deterministic report, exactly as on a real run. (A prior mock-only
|
|
105
|
-
// short-circuit synthesized a report from steps for every agent, hiding the
|
|
106
|
-
// cross-vendor gap — you couldn't dogfood "codex's report is thin" under mock.)
|
|
107
|
-
// Errored session — deterministic recovery hint, no summarization of nothing.
|
|
108
|
-
if (input.status === 'error') {
|
|
109
|
-
return buildErrorRecoveryReport(input, 0);
|
|
110
|
-
}
|
|
111
|
-
// ENGINE: reuse the agent's own narration (no fresh spawn).
|
|
112
|
-
const agentSummary = extractAgentSummary(input.steps);
|
|
113
|
-
if (agentSummary && !isLikelyNetworkFailureSummary(agentSummary)) {
|
|
114
|
-
return { summary: agentSummary, generatedAt: Date.now(), durationMs: 0, source: 'agent-summary' };
|
|
115
|
-
}
|
|
116
|
-
// FALLBACK: deterministic, honest — always a deliverable, never a fake ✓.
|
|
117
|
-
return buildDeterministicReport(input);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Pretty-print to terminal. Used by session-manager when a spawned task exits.
|
|
121
|
-
* Wrapped in try/catch upstream — never let a print failure surface.
|
|
122
|
-
*/
|
|
123
|
-
export function printFinalReportToTerminal(input, output) {
|
|
124
|
-
const banner = '─'.repeat(60);
|
|
125
|
-
process.stdout.write('\n');
|
|
126
|
-
process.stdout.write(`${banner}\n`);
|
|
127
|
-
process.stdout.write(`Final Report · ${input.agent} · ${input.sessionId.slice(0, 8)}\n`);
|
|
128
|
-
process.stdout.write(`${banner}\n`);
|
|
129
|
-
process.stdout.write(`${output.summary}\n`);
|
|
130
|
-
process.stdout.write(`${banner}\n`);
|
|
131
|
-
process.stdout.write(`(generated in ${output.durationMs}ms)\n`);
|
|
132
|
-
process.stdout.write('\n');
|
|
133
|
-
}
|
|
134
|
-
//# sourceMappingURL=generate-final-report.js.map
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PM Narration — v0.6 M3 mid-task summary.
|
|
3
|
-
*
|
|
4
|
-
* While a task is in flight, periodically generates a one-paragraph
|
|
5
|
-
* plain-English summary of what the agent has done so far. Fires on
|
|
6
|
-
* step-count or elapsed-time thresholds (caller decides when).
|
|
7
|
-
*
|
|
8
|
-
* Per claim_user_persona HIGH: non-technical users primary. They don't
|
|
9
|
-
* read tool calls; they read narration. Mid-task narration tells them
|
|
10
|
-
* "the agent is currently editing your auth file and ran the tests"
|
|
11
|
-
* instead of forcing them to interpret a list of Bash/Edit/Read steps.
|
|
12
|
-
*
|
|
13
|
-
* Per feedback_overconfidence_guard.md: narration is an LLM call.
|
|
14
|
-
* Hallucination is possible. Surface MUST frame it as "narrator says…"
|
|
15
|
-
* not authoritative truth. The PROMPT_TEMPLATE includes the same
|
|
16
|
-
* anti-hallucination rule as generate-final-report (ground every claim
|
|
17
|
-
* in the structured data).
|
|
18
|
-
*
|
|
19
|
-
* Provider strategy: same as generate-final-report (try claude-free
|
|
20
|
-
* first, fall back to claude). Returns null if neither is installed.
|
|
21
|
-
*/
|
|
22
|
-
import { spawn } from 'child_process';
|
|
23
|
-
import { loadTemplate, renderTemplate } from './narrator-prompt-template.js';
|
|
24
|
-
import { isMockMode } from '../../infrastructure/orchestration/mocks/resolve-agent-spawn.js';
|
|
25
|
-
import { mockNarration } from '../../infrastructure/orchestration/mocks/mock-synthesis.js';
|
|
26
|
-
/** v0.6 S6 — prompt template lifted into ~/.codehere/narrator-prompt.md so
|
|
27
|
-
* the user can edit the agent's voice. loadTemplate() falls back to the
|
|
28
|
-
* baked-in default on missing file or missing required placeholders, so
|
|
29
|
-
* the prompt is never silently broken. See narrator-prompt-template.ts. */
|
|
30
|
-
function buildPrompt(input) {
|
|
31
|
-
const { template } = loadTemplate();
|
|
32
|
-
return renderTemplate(template, input);
|
|
33
|
-
}
|
|
34
|
-
function selectProviderBinary() {
|
|
35
|
-
// Same precedence as generate-final-report: env override → claude-free → claude.
|
|
36
|
-
const override = process.env.CODEHERE_NARRATOR_BINARY?.trim();
|
|
37
|
-
if (override)
|
|
38
|
-
return [override];
|
|
39
|
-
return ['claude-free', 'claude'];
|
|
40
|
-
}
|
|
41
|
-
export async function generateNarration(input) {
|
|
42
|
-
if (isMockMode()) {
|
|
43
|
-
const m = mockNarration({ steps: input.steps });
|
|
44
|
-
return {
|
|
45
|
-
summary: m.summary,
|
|
46
|
-
generatedAt: Date.now(),
|
|
47
|
-
durationMs: m.durationMs,
|
|
48
|
-
stepCountAtSnapshot: input.steps.length,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
const prompt = buildPrompt(input);
|
|
52
|
-
const candidates = selectProviderBinary();
|
|
53
|
-
for (const binary of candidates) {
|
|
54
|
-
const result = await tryProvider(binary, prompt, input.sessionId, input.steps.length);
|
|
55
|
-
if (result)
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
function tryProvider(binary, prompt, sessionId, stepCount) {
|
|
61
|
-
return new Promise((resolve) => {
|
|
62
|
-
const startedAt = Date.now();
|
|
63
|
-
let stdout = '';
|
|
64
|
-
let stderr = '';
|
|
65
|
-
let resolved = false;
|
|
66
|
-
const finish = (val) => {
|
|
67
|
-
if (resolved)
|
|
68
|
-
return;
|
|
69
|
-
resolved = true;
|
|
70
|
-
resolve(val);
|
|
71
|
-
};
|
|
72
|
-
let child;
|
|
73
|
-
try {
|
|
74
|
-
// Same shape as generate-final-report's tryProvider — claude --bare -p.
|
|
75
|
-
child = spawn(binary, ['--bare', '-p', prompt], {
|
|
76
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
finish(null);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
// 20s timeout — narration should be SNAPPIER than Final Report
|
|
84
|
-
// (which is 60s). Mid-task = the user is actively waiting.
|
|
85
|
-
const timeout = setTimeout(() => {
|
|
86
|
-
try {
|
|
87
|
-
child.kill();
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
/* ignore */
|
|
91
|
-
}
|
|
92
|
-
finish(null);
|
|
93
|
-
}, 20_000);
|
|
94
|
-
child.stdout?.on('data', (chunk) => {
|
|
95
|
-
stdout += chunk.toString('utf-8');
|
|
96
|
-
});
|
|
97
|
-
child.stderr?.on('data', (chunk) => {
|
|
98
|
-
stderr += chunk.toString('utf-8');
|
|
99
|
-
});
|
|
100
|
-
child.on('close', (code) => {
|
|
101
|
-
clearTimeout(timeout);
|
|
102
|
-
if (code !== 0) {
|
|
103
|
-
// Non-zero usually means binary missing OR the model refused. Either
|
|
104
|
-
// way silent fail; caller treats null as "no narration this cycle".
|
|
105
|
-
void stderr;
|
|
106
|
-
void sessionId;
|
|
107
|
-
finish(null);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const summary = stdout.trim();
|
|
111
|
-
if (!summary || summary.length < 10) {
|
|
112
|
-
finish(null);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
finish({
|
|
116
|
-
summary,
|
|
117
|
-
generatedAt: Date.now(),
|
|
118
|
-
durationMs: Date.now() - startedAt,
|
|
119
|
-
stepCountAtSnapshot: stepCount,
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
child.on('error', () => {
|
|
123
|
-
clearTimeout(timeout);
|
|
124
|
-
finish(null);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
//# sourceMappingURL=generate-narration.js.map
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hidden Title agent — v0.6.0 M9.
|
|
3
|
-
*
|
|
4
|
-
* Generates a 4-6 word title for a project thread from its first user prompt.
|
|
5
|
-
* Hidden = users don't pick it from any picker; fires automatically when the
|
|
6
|
-
* project thread gets its first task entry. The title surfaces in the browser
|
|
7
|
-
* tab + future thread switcher UI.
|
|
8
|
-
*
|
|
9
|
-
* Companion to generate-final-report.ts:
|
|
10
|
-
* - Same provider chain pattern (claude-free → claude → graceful skip)
|
|
11
|
-
* - Same single-shot, no streaming, returns string
|
|
12
|
-
* - Different prompt (terse title generation, NOT 3-paragraph narrative)
|
|
13
|
-
* - Cheaper budget (~50 tokens per title vs 200 per Final Report paragraph)
|
|
14
|
-
*
|
|
15
|
-
* Triggering: project-thread-mirror invokes this when seeding the project-meta
|
|
16
|
-
* header for a NEW project (i.e., this cwd's thread.jsonl didn't exist before).
|
|
17
|
-
* Idempotent: subsequent task entries do NOT regenerate the title.
|
|
18
|
-
*
|
|
19
|
-
* Cost: ~1 LLM call per NEW project (not per task). At a typical 1-2 new
|
|
20
|
-
* projects per week per user, the cost is negligible. FCC-routed when
|
|
21
|
-
* available; degrades silently to "(untitled)" when no provider responds.
|
|
22
|
-
*/
|
|
23
|
-
import { spawn } from 'child_process';
|
|
24
|
-
import { isMockMode } from '../../infrastructure/orchestration/mocks/resolve-agent-spawn.js';
|
|
25
|
-
import { mockTitle } from '../../infrastructure/orchestration/mocks/mock-synthesis.js';
|
|
26
|
-
const TITLE_TIMEOUT_MS = 15_000; // titles are short; abort fast if stuck
|
|
27
|
-
const PROMPT_TEMPLATE = (firstPrompt, cwd) => `Generate a 4-6 word project title from this first user prompt.
|
|
28
|
-
|
|
29
|
-
Project working directory: ${cwd}
|
|
30
|
-
First prompt: ${firstPrompt}
|
|
31
|
-
|
|
32
|
-
Rules:
|
|
33
|
-
- 4 to 6 words MAX
|
|
34
|
-
- No quotes, no punctuation, no trailing period
|
|
35
|
-
- Title-case (e.g. "Auth Bug Fix Sprint")
|
|
36
|
-
- Ground the title in the prompt + cwd ONLY. Do NOT invent project subject
|
|
37
|
-
matter you can't derive from the data above (e.g. don't infer "Thread
|
|
38
|
-
Interface Updates" from a "list files" prompt — say what the user
|
|
39
|
-
actually asked, not what you imagine the project is about).
|
|
40
|
-
- If the prompt is too generic to title meaningfully, prefer naming the
|
|
41
|
-
cwd's last path segment (e.g. "Taste V0.6 Workspace") over invention.
|
|
42
|
-
- Output ONLY the title — no preamble, no explanation
|
|
43
|
-
|
|
44
|
-
Title:`;
|
|
45
|
-
function selectProviderBinary() {
|
|
46
|
-
const override = process.env.CODEHERE_TITLE_BINARY;
|
|
47
|
-
if (override)
|
|
48
|
-
return [override];
|
|
49
|
-
return ['claude-free', 'claude'];
|
|
50
|
-
}
|
|
51
|
-
function tryProvider(binary, prompt) {
|
|
52
|
-
return new Promise((resolve) => {
|
|
53
|
-
let stdout = '';
|
|
54
|
-
let settled = false;
|
|
55
|
-
const finish = (result) => {
|
|
56
|
-
if (settled)
|
|
57
|
-
return;
|
|
58
|
-
settled = true;
|
|
59
|
-
resolve(result);
|
|
60
|
-
};
|
|
61
|
-
const child = spawn(binary, ['--bare', '-p', prompt], {
|
|
62
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
63
|
-
});
|
|
64
|
-
const timeout = setTimeout(() => {
|
|
65
|
-
child.kill('SIGTERM');
|
|
66
|
-
if (process.env.CODEHERE_DEBUG === '1') {
|
|
67
|
-
console.error(`[title-agent] ${binary} timeout after ${TITLE_TIMEOUT_MS}ms`);
|
|
68
|
-
}
|
|
69
|
-
finish(null);
|
|
70
|
-
}, TITLE_TIMEOUT_MS);
|
|
71
|
-
child.on('error', () => {
|
|
72
|
-
clearTimeout(timeout);
|
|
73
|
-
finish(null); // ENOENT etc. — silent fallback to next provider
|
|
74
|
-
});
|
|
75
|
-
child.stdout?.on('data', (chunk) => {
|
|
76
|
-
stdout += chunk.toString('utf-8');
|
|
77
|
-
});
|
|
78
|
-
child.on('exit', (code) => {
|
|
79
|
-
clearTimeout(timeout);
|
|
80
|
-
if (code !== 0) {
|
|
81
|
-
finish(null);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const title = sanitizeTitle(stdout);
|
|
85
|
-
if (!title) {
|
|
86
|
-
finish(null);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
finish(title);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Strip whitespace / quotes / trailing punctuation. Cap at 80 chars.
|
|
95
|
-
*
|
|
96
|
-
* Garbage rejection (added 2026-05-06 after dogfood Bug A4): the underlying
|
|
97
|
-
* `claude --bare` call can return error-shaped text on stdout with exit 0
|
|
98
|
-
* (rate-limit messages, auth errors, refusals). Pre-fix those flowed straight
|
|
99
|
-
* into the project sidebar as titles like "Request ID: req_xyz...". We now
|
|
100
|
-
* detect obvious error / refusal patterns and return '' so the caller falls
|
|
101
|
-
* back to the cwd-derived title.
|
|
102
|
-
*/
|
|
103
|
-
export function sanitizeTitle(raw) {
|
|
104
|
-
const lines = raw
|
|
105
|
-
.split('\n')
|
|
106
|
-
.map((l) => l.trim())
|
|
107
|
-
.filter(Boolean);
|
|
108
|
-
if (lines.length === 0)
|
|
109
|
-
return '';
|
|
110
|
-
let title = lines[lines.length - 1]; // last non-empty line is the answer
|
|
111
|
-
// Strip surrounding quotes
|
|
112
|
-
title = title.replace(/^["'`]+|["'`]+$/g, '');
|
|
113
|
-
// Strip trailing punctuation
|
|
114
|
-
title = title.replace(/[.!?,;:]+$/, '');
|
|
115
|
-
// Cap at 80 chars
|
|
116
|
-
if (title.length > 80)
|
|
117
|
-
title = title.slice(0, 77) + '...';
|
|
118
|
-
title = title.trim();
|
|
119
|
-
if (!title)
|
|
120
|
-
return '';
|
|
121
|
-
// Garbage rejection — patterns that indicate an error response, NOT a title.
|
|
122
|
-
// Each pattern matches a real failure mode observed in dogfood:
|
|
123
|
-
// - "Request ID: req_..." → API error envelope leaking through
|
|
124
|
-
// - "Error:" / "ERROR" / "error " → generic error prefix
|
|
125
|
-
// - "I cannot" / "I can't" → model refusal
|
|
126
|
-
// - "I apologize" / "I'm sorry" → model refusal
|
|
127
|
-
// - "Rate limit" / "rate-limited" → quota error
|
|
128
|
-
// - "Authentication" / "Unauthorized" / "401" / "403" → auth failure
|
|
129
|
-
// - Long sentences (>10 words) → not a title; model ignored the rules
|
|
130
|
-
// - Contains http:// or https:// → URL leakage from error envelope
|
|
131
|
-
const lower = title.toLowerCase();
|
|
132
|
-
const garbageMarkers = [
|
|
133
|
-
/^request\s+id\s*[:=]/i,
|
|
134
|
-
/^error\b/i,
|
|
135
|
-
/\berror[:\s]/i,
|
|
136
|
-
/^i\s+(cannot|can'?t|am\s+unable|won'?t)\b/i,
|
|
137
|
-
/^i\s+(apologi[sz]e|am\s+sorry)\b/i,
|
|
138
|
-
/^i'?m\s+sorry\b/i,
|
|
139
|
-
/\brate\s*[-_]?\s*limit/i,
|
|
140
|
-
/\b(authentication|unauthori[sz]ed|forbidden)\b/i,
|
|
141
|
-
/\b(401|403|429|500|502|503)\b/i,
|
|
142
|
-
/https?:\/\//i,
|
|
143
|
-
/api[_-]?key/i,
|
|
144
|
-
];
|
|
145
|
-
if (garbageMarkers.some((re) => re.test(lower))) {
|
|
146
|
-
if (process.env.CODEHERE_DEBUG === '1') {
|
|
147
|
-
console.error(`[title-agent] rejected garbage title: ${title.slice(0, 60)}`);
|
|
148
|
-
}
|
|
149
|
-
return '';
|
|
150
|
-
}
|
|
151
|
-
// Length sanity: a real title is 4-6 words per the prompt; allow up to 10
|
|
152
|
-
// for slack. Anything longer is the model ignoring instructions.
|
|
153
|
-
const wordCount = title.split(/\s+/).filter(Boolean).length;
|
|
154
|
-
if (wordCount > 10) {
|
|
155
|
-
if (process.env.CODEHERE_DEBUG === '1') {
|
|
156
|
-
console.error(`[title-agent] rejected long title (${wordCount} words): ${title.slice(0, 60)}`);
|
|
157
|
-
}
|
|
158
|
-
return '';
|
|
159
|
-
}
|
|
160
|
-
return title;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Generate a thread title from the first user prompt + cwd context.
|
|
164
|
-
* Returns the title string, or null if no provider is reachable.
|
|
165
|
-
* Never throws — failure modes return null.
|
|
166
|
-
*/
|
|
167
|
-
export async function generateThreadTitle(firstPrompt, cwd) {
|
|
168
|
-
if (isMockMode())
|
|
169
|
-
return sanitizeTitle(mockTitle(firstPrompt, cwd)) || null;
|
|
170
|
-
const prompt = PROMPT_TEMPLATE(firstPrompt, cwd);
|
|
171
|
-
for (const binary of selectProviderBinary()) {
|
|
172
|
-
const result = await tryProvider(binary, prompt);
|
|
173
|
-
if (result)
|
|
174
|
-
return result;
|
|
175
|
-
}
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
//# sourceMappingURL=generate-thread-title.js.map
|