codehere 0.7.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/dist/application/services/verdict-service.js +154 -0
- package/dist/application/use-cases/assess-cross-vendor.js +126 -0
- package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
- package/dist/application/use-cases/export-aat.js +107 -0
- package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
- package/dist/application/use-cases/generate-final-report.js +140 -15
- package/dist/application/use-cases/generate-narration.js +5 -3
- package/dist/application/use-cases/generate-thread-title.js +5 -2
- package/dist/application/use-cases/narrator-prompt-template.js +1 -1
- package/dist/application/use-cases/plan-auto-review.js +57 -0
- package/dist/application/use-cases/reconcile-audit.js +100 -0
- package/dist/application/use-cases/rerun-tests.js +226 -0
- package/dist/application/use-cases/search-sessions.js +70 -0
- package/dist/application/use-cases/should-auto-review.js +71 -0
- package/dist/application/use-cases/thread-export.js +90 -0
- package/dist/application/use-cases/verify-claim.js +187 -5
- package/dist/domain/audit.js +11 -0
- package/dist/domain/services/codebase-detector.js +0 -6
- package/dist/domain/task.js +91 -0
- package/dist/error-handler.js +3 -115
- package/dist/index.js +279 -31
- package/dist/infrastructure/completion/bash-completion.js +7 -3
- package/dist/infrastructure/completion/fish-completion.js +7 -12
- package/dist/infrastructure/completion/zsh-completion.js +7 -12
- package/dist/infrastructure/config/config-manager.js +4 -3
- package/dist/infrastructure/config/product-context.js +1 -19
- package/dist/infrastructure/context/adapters/figma-source.js +37 -2
- package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
- package/dist/infrastructure/context/github-api.js +110 -0
- package/dist/infrastructure/context/github-device-flow.js +65 -0
- package/dist/infrastructure/context/oauth-store.js +5 -2
- package/dist/infrastructure/context/registry-bootstrap.js +0 -4
- package/dist/infrastructure/cost/budget-config.js +4 -3
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +91 -16
- package/dist/infrastructure/cost/team-ledger.js +355 -0
- package/dist/infrastructure/discovery/discovery-log.js +85 -0
- package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +139 -3
- package/dist/infrastructure/hooks/hook-mode.js +20 -4
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
- package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
- package/dist/infrastructure/observability/execution-tracer.js +1 -1
- package/dist/infrastructure/orchestration/agents-config-seed.js +23 -11
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- package/dist/infrastructure/orchestration/cassette-recorder.js +1 -1
- package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
- package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
- package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
- package/dist/infrastructure/orchestration/context-handoff.js +32 -14
- package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
- package/dist/infrastructure/orchestration/failover-config.js +1 -11
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +134 -3
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/memory-injector.js +4 -1
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +167 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +275 -23
- package/dist/infrastructure/orchestration/session-manager.js +593 -94
- package/dist/infrastructure/orchestration/session-persistence.js +177 -41
- package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
- package/dist/infrastructure/protocols/cloud-sync.js +2 -2
- package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1498 -354
- package/dist/infrastructure/protocols/mcp-tools.js +401 -0
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
- package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
- package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
- package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
- package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
- package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
- package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
- package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
- package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
- package/dist/infrastructure/protocols/ui/styles.js +2147 -0
- package/dist/infrastructure/protocols/ui/tokens.js +96 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +116 -4126
- package/dist/infrastructure/protocols/verify-page.js +884 -148
- package/dist/infrastructure/security/fix-code.js +144 -0
- package/dist/infrastructure/security/fix-secret.js +89 -0
- package/dist/infrastructure/security/permissions-config.js +3 -2
- package/dist/infrastructure/security/scan-types.js +10 -0
- package/dist/infrastructure/security/ship-scan.js +195 -0
- package/dist/infrastructure/security/trust-scorer.js +70 -3
- package/dist/infrastructure/security/vibe-checks.js +99 -0
- package/dist/infrastructure/storage/atomic-write.js +86 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +151 -4
- package/dist/infrastructure/storage/project-thread-reader.js +262 -25
- package/dist/infrastructure/storage/project-thread-title-backfill.js +1 -1
- package/dist/monitoring.js +2 -2
- package/dist/presentation/cli/commands/audit-command.js +79 -4
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/check-command.js +96 -0
- package/dist/presentation/cli/commands/context-source-command.js +20 -1
- package/dist/presentation/cli/commands/help-command.js +5 -5
- package/dist/presentation/cli/commands/hook-command.js +5 -5
- package/dist/presentation/cli/commands/init-command.js +89 -66
- package/dist/presentation/cli/commands/memory-command.js +28 -28
- package/dist/presentation/cli/commands/permission-command.js +1 -12
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/protocols-command.js +4 -4
- package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
- package/dist/presentation/cli/commands/report-command.js +48 -32
- package/dist/presentation/cli/commands/scan-command.js +52 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
- package/dist/presentation/cli/commands/search-command.js +51 -0
- package/dist/presentation/cli/commands/setup-command.js +31 -8
- package/dist/presentation/cli/commands/status-command.js +39 -86
- package/dist/presentation/cli/commands/team-command.js +256 -0
- package/dist/presentation/cli/commands/thread-command.js +87 -5
- package/dist/presentation/cli/commands/trust-command.js +53 -31
- package/dist/presentation/cli/commands/verify-command.js +74 -9
- package/dist/presentation/cli/error-display.js +0 -8
- package/dist/presentation/cli/repl.js +15 -7
- package/dist/presentation/cli/task-deliverable.js +36 -10
- package/dist/testing/replay-adapter.js +0 -19
- package/dist/ui.js +51 -239
- package/dist/utils/version.js +0 -6
- package/package.json +30 -10
- package/dist/domain/entities/task.js +0 -61
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/task-repository.js +0 -206
package/dist/monitoring.js
CHANGED
|
@@ -201,10 +201,10 @@ export function getMetrics() {
|
|
|
201
201
|
* Format health status for display
|
|
202
202
|
*/
|
|
203
203
|
export function formatHealthStatus(health) {
|
|
204
|
-
const statusIcon = health.status === 'healthy' ? '✓' : health.status === 'degraded' ? '
|
|
204
|
+
const statusIcon = health.status === 'healthy' ? '✓' : health.status === 'degraded' ? '!' : '✕';
|
|
205
205
|
let output = ` ${statusIcon} ${health.status.toUpperCase()}\n\n`;
|
|
206
206
|
for (const [component, check] of Object.entries(health.checks)) {
|
|
207
|
-
const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '
|
|
207
|
+
const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '!' : '✕';
|
|
208
208
|
output += ` ${icon} ${component.padEnd(12)} ${check.message}\n`;
|
|
209
209
|
}
|
|
210
210
|
return output;
|
|
@@ -19,13 +19,19 @@
|
|
|
19
19
|
import { existsSync, readFileSync } from 'fs';
|
|
20
20
|
import { colors, symbols } from '../../../ui.js';
|
|
21
21
|
import { AUDIT_FILE, verifyAuditChain } from '../../../infrastructure/hooks/claude-sdk-hook.js';
|
|
22
|
+
import { verifyAuditTip, tipBlocksVerification } from '../../../infrastructure/hooks/audit-tip-signature.js';
|
|
23
|
+
import { exportAat, aatToJsonl } from '../../../application/use-cases/export-aat.js';
|
|
24
|
+
import { writeFileSync as writeFileSyncFs } from 'fs';
|
|
25
|
+
import { trustedKeySet } from '../../../infrastructure/orchestration/thread-key-store.js';
|
|
22
26
|
export function executeAuditCommand(sub, arg, options = {}) {
|
|
23
27
|
if (!sub || sub === 'verify')
|
|
24
28
|
return verify(options.json ?? false);
|
|
25
29
|
if (sub === 'show')
|
|
26
30
|
return show(arg, options.json ?? false);
|
|
31
|
+
if (sub === 'export')
|
|
32
|
+
return exportAatCmd(arg);
|
|
27
33
|
process.stderr.write(colors.error(`${symbols.error} Unknown audit subcommand: ${sub}\n`) +
|
|
28
|
-
colors.dim('Try: codehere audit verify | codehere audit show <seq
|
|
34
|
+
colors.dim('Try: codehere audit verify | codehere audit show <seq> | codehere audit export [out.jsonl]\n'));
|
|
29
35
|
return 2;
|
|
30
36
|
}
|
|
31
37
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
@@ -33,16 +39,45 @@ export function executeAuditCommand(sub, arg, options = {}) {
|
|
|
33
39
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
34
40
|
function verify(json) {
|
|
35
41
|
const result = verifyAuditChain(AUDIT_FILE);
|
|
42
|
+
// R2 · the tip signature catches a re-walked forgery the hash chain alone
|
|
43
|
+
// can't (a determined edit + recomputed hashes verifies green, but the signed
|
|
44
|
+
// entry's hash changes). ANCESTOR check: honest appends since signing are
|
|
45
|
+
// expected and pass; an edit at/below the signed seq, truncation, an untrusted
|
|
46
|
+
// re-sign, or a deleted .tipsig on a previously-signed chain all fail.
|
|
47
|
+
const tip = verifyAuditTip(AUDIT_FILE, trustedKeySet());
|
|
48
|
+
const tipTampered = tipBlocksVerification(tip);
|
|
49
|
+
// An empty or missing log has nothing to verify, and a chain walk over zero entries
|
|
50
|
+
// trivially succeeds. Reporting that as valid:true is the failure this product exists to
|
|
51
|
+
// catch: deleting the audit log is exactly the attack the hash chain is meant to detect,
|
|
52
|
+
// and the machine interface was answering "intact" to a machine that asked. The human
|
|
53
|
+
// path already says "No audit entries yet" and claims nothing, so only --json was lying.
|
|
54
|
+
// "Is my chain intact?" over no chain is not answered by yes.
|
|
55
|
+
const verifiable = result.totalEntries > 0;
|
|
56
|
+
const ok = verifiable && result.valid && !tipTampered;
|
|
36
57
|
if (json) {
|
|
37
58
|
process.stdout.write(JSON.stringify({
|
|
38
59
|
path: AUDIT_FILE,
|
|
39
|
-
valid:
|
|
60
|
+
valid: ok,
|
|
61
|
+
verifiable,
|
|
62
|
+
...(verifiable ? {} : { reason: 'no audit entries — nothing to verify (is the hook installed?)' }),
|
|
63
|
+
chainHashesValid: result.valid,
|
|
64
|
+
writerMix: result.writerMix,
|
|
65
|
+
tipSignature: {
|
|
66
|
+
present: tip.present,
|
|
67
|
+
expectedButMissing: tip.expectedButMissing,
|
|
68
|
+
signatureValid: tip.signatureValid,
|
|
69
|
+
ancestorIntact: tip.ancestorIntact,
|
|
70
|
+
matchesTip: tip.matchesTip,
|
|
71
|
+
trusted: tip.trusted,
|
|
72
|
+
signer: tip.signerKeyB64,
|
|
73
|
+
reason: tip.reason,
|
|
74
|
+
},
|
|
40
75
|
totalEntries: result.totalEntries,
|
|
41
76
|
chainedEntries: result.chainedEntries,
|
|
42
77
|
legacyEntries: result.legacyEntries,
|
|
43
78
|
errors: result.errors,
|
|
44
79
|
}, null, 2) + '\n');
|
|
45
|
-
return
|
|
80
|
+
return ok ? 0 : 1;
|
|
46
81
|
}
|
|
47
82
|
// Human output · header, verdict line, counts, then errors if any.
|
|
48
83
|
process.stdout.write('\n' + colors.dim('audit log: ') + colors.gray(AUDIT_FILE) + '\n\n');
|
|
@@ -63,6 +98,24 @@ function verify(json) {
|
|
|
63
98
|
colors.dim(` ${result.errors.length} break point${result.errors.length === 1 ? '' : 's'} detected.`) +
|
|
64
99
|
'\n\n');
|
|
65
100
|
}
|
|
101
|
+
// R1.5 · writer-provenance mix — honest about WHO wrote the entries. The
|
|
102
|
+
// verify surface must never imply everything went through the daemon when
|
|
103
|
+
// some entries are the hook's direct fallback or predate provenance.
|
|
104
|
+
const wm = result.writerMix;
|
|
105
|
+
if (result.chainedEntries > 0) {
|
|
106
|
+
process.stdout.write(colors.dim(` written by: ${wm.daemon} daemon \u00B7 ${wm.hookDirect} hook-direct (no daemon reachable) \u00B7 ${wm.unattributed} unattributed (no writer field)\n\n`));
|
|
107
|
+
}
|
|
108
|
+
// R2 · tip-signature line — catches a re-walked forgery the hash chain can't.
|
|
109
|
+
if (tip.present && tip.signatureValid && tip.ancestorIntact && tip.trusted) {
|
|
110
|
+
process.stdout.write(colors.green(`${symbols.success} Chain signed`) +
|
|
111
|
+
colors.dim(tip.matchesTip
|
|
112
|
+
? " by a trusted key · no edits by anyone without this machine's key."
|
|
113
|
+
: ' by a trusted key · the signed prefix is intact (the chain has grown since).') +
|
|
114
|
+
'\n\n');
|
|
115
|
+
}
|
|
116
|
+
else if (tipTampered) {
|
|
117
|
+
process.stdout.write(colors.error(`${symbols.error} Tip signature FAILED`) + colors.dim(` ${tip.reason}.`) + '\n\n');
|
|
118
|
+
}
|
|
66
119
|
process.stdout.write(colors.bold('Counts:\n'));
|
|
67
120
|
process.stdout.write(` ${result.totalEntries.toString().padStart(6)} total entries\n`);
|
|
68
121
|
process.stdout.write(` ${result.chainedEntries.toString().padStart(6)} hash-chained (verifiable)\n`);
|
|
@@ -80,7 +133,7 @@ function verify(json) {
|
|
|
80
133
|
}
|
|
81
134
|
process.stdout.write('\n');
|
|
82
135
|
}
|
|
83
|
-
return
|
|
136
|
+
return ok ? 0 : 1;
|
|
84
137
|
}
|
|
85
138
|
function show(seqStr, json) {
|
|
86
139
|
if (!seqStr || !/^\d+$/.test(seqStr)) {
|
|
@@ -123,4 +176,26 @@ function show(seqStr, json) {
|
|
|
123
176
|
colors.dim(' Tip: run `codehere audit verify` to see the chained-entry count.\n'));
|
|
124
177
|
return 2;
|
|
125
178
|
}
|
|
179
|
+
/* ──────────────────────────────────────────────────────────────────────── */
|
|
180
|
+
/** R-AAT · export the chain as IETF draft-sharif-agent-audit-trail-00 JSONL.
|
|
181
|
+
* The header line carries the honest scope notes (L0 trust level, codehere
|
|
182
|
+
* canonicalization, hashed inputs) so the FILE can't claim more than it is. */
|
|
183
|
+
function exportAatCmd(outPath) {
|
|
184
|
+
const exp = exportAat(AUDIT_FILE);
|
|
185
|
+
const target = outPath && outPath.trim() ? outPath.trim() : 'codehere-audit-aat.jsonl';
|
|
186
|
+
try {
|
|
187
|
+
writeFileSyncFs(target, aatToJsonl(exp), 'utf-8');
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
process.stderr.write(colors.error(`${symbols.error} Could not write ${target}: ${e instanceof Error ? e.message : String(e)}\n`));
|
|
191
|
+
return 2;
|
|
192
|
+
}
|
|
193
|
+
process.stdout.write(colors.green(`${symbols.success} Exported ${exp.header.record_count} records`) +
|
|
194
|
+
colors.dim(` › ${target} (AAT draft-00 JSONL)\n`) +
|
|
195
|
+
(exp.skippedLegacy > 0
|
|
196
|
+
? colors.dim(` ${exp.skippedLegacy} legacy pre-chain entries skipped (no hash-chained position to export)\n`)
|
|
197
|
+
: '') +
|
|
198
|
+
colors.dim(' trust_level=L0 throughout · agents carry no cryptographic identity; the daemon signs the chain tip (recorder identity). Details in the header line.\n'));
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
126
201
|
//# sourceMappingURL=audit-command.js.map
|
|
@@ -41,7 +41,7 @@ function showBudget() {
|
|
|
41
41
|
newline();
|
|
42
42
|
console.log(info("Today's estimated spend:"), fmtUsd(spend.totalUsd), `(${spend.sessionCount} session${spend.sessionCount === 1 ? '' : 's'})`);
|
|
43
43
|
if (cfg.dailyUsd === null) {
|
|
44
|
-
console.log(info('Daily budget:'), 'none
|
|
44
|
+
console.log(info('Daily budget:'), 'none · no enforcement');
|
|
45
45
|
console.log(info('Set one with:'), 'codehere budget set 5');
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
@@ -60,7 +60,7 @@ function showBudget() {
|
|
|
60
60
|
function setBudget(value) {
|
|
61
61
|
const n = Number(value.replace(/[$,]/g, ''));
|
|
62
62
|
if (!Number.isFinite(n) || n <= 0) {
|
|
63
|
-
console.error(error('Invalid amount:'), value, '
|
|
63
|
+
console.error(error('Invalid amount:'), value, '· expected a positive USD number.');
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
|
66
66
|
const cfg = loadBudget();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI: check — the simplest proof. codehere independently RE-RUNS your project's
|
|
3
|
+
* tests and reports the real result, so you trust what the AI actually did, not
|
|
4
|
+
* what it claimed. Zero setup, no daemon, no API. Exit 1 on failure (CI-usable).
|
|
5
|
+
*/
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
import { colors, symbols } from '../../../ui.js';
|
|
9
|
+
import { rerunTests } from '../../../application/use-cases/rerun-tests.js';
|
|
10
|
+
/** Short commit SHA of `cwd`'s repo, or null when it isn't a git checkout. */
|
|
11
|
+
function gitShortSha(cwd) {
|
|
12
|
+
try {
|
|
13
|
+
return execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
14
|
+
.toString()
|
|
15
|
+
.trim();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A paste-ready "verified outcome receipt": the one thing a reviewer can trust
|
|
23
|
+
* without re-running anything — codehere independently re-ran the suite, here's the
|
|
24
|
+
* observed result, pinned to a commit + timestamp, WITH the exact command to
|
|
25
|
+
* reproduce it independently. Plain markdown so it drops into a PR description or
|
|
26
|
+
* review comment as-is.
|
|
27
|
+
*
|
|
28
|
+
* Why reproducibility is the trust mechanism (not a digest): a self-computed hash
|
|
29
|
+
* over self-reported facts proves nothing — a sender could edit the facts AND the
|
|
30
|
+
* hash. What a third party can actually trust is REPRODUCING it: check out the
|
|
31
|
+
* pinned commit, run `codehere check`, get the same exit code. So the receipt leads
|
|
32
|
+
* with the reproduce command and is honest about SCOPE (a test re-run, not a
|
|
33
|
+
* security/logic audit) — it never says more than it verified.
|
|
34
|
+
*
|
|
35
|
+
* ponytail: this is the standalone (no-daemon) receipt. Upgrade path to true
|
|
36
|
+
* tamper-EVIDENCE (authenticity, not just reproducibility): when a ~/.codehere audit
|
|
37
|
+
* chain covers this commit, fold in its verified hash (audit-chain.ts) so the receipt
|
|
38
|
+
* also carries a digest a third party can check against an append-only log.
|
|
39
|
+
*/
|
|
40
|
+
function buildProofBlock(cwd, r) {
|
|
41
|
+
const sha = gitShortSha(cwd);
|
|
42
|
+
const when = new Date().toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
|
|
43
|
+
const at = sha ? `commit \`${sha}\`` : 'the working tree';
|
|
44
|
+
if (!r.ran) {
|
|
45
|
+
return `> **codehere · verified outcome receipt** — no suite re-run (${r.skippedReason || 'none detected'}) · nothing verified · ${when}\n`;
|
|
46
|
+
}
|
|
47
|
+
if (r.exitCode === 126 || r.exitCode === 127) {
|
|
48
|
+
return `> **codehere · verified outcome receipt** — couldn't run \`${r.command}\` on ${at} (runner not installed, exit ${r.exitCode}) · unverified · ${when}\n`;
|
|
49
|
+
}
|
|
50
|
+
const passed = (r.exitCode ?? 1) === 0;
|
|
51
|
+
const counts = typeof r.passed === 'number' || typeof r.failed === 'number'
|
|
52
|
+
? ` (${r.passed ?? '?'} passed · ${r.failed ?? 0} failed)`
|
|
53
|
+
: '';
|
|
54
|
+
const verdict = passed ? '✓ tests pass' : '✕ tests FAIL';
|
|
55
|
+
// Reproduce line: the real, verifiable trust — anyone can re-run and confirm.
|
|
56
|
+
const reproduce = sha ? `> Reproduce independently: \`git checkout ${sha} && codehere check\`\n` : '';
|
|
57
|
+
return (`> **codehere · verified outcome receipt** · ${verdict}${counts}\n` +
|
|
58
|
+
`> codehere independently re-ran \`${r.command}\` on ${at} — exit ${r.exitCode}. Observed, not the AI's claim. · ${when}\n` +
|
|
59
|
+
reproduce +
|
|
60
|
+
`> Scope: the project's test suite — not a security or logic audit.\n`);
|
|
61
|
+
}
|
|
62
|
+
export async function executeCheckCommand(target, options = {}) {
|
|
63
|
+
const cwd = resolve(target || '.');
|
|
64
|
+
const r = await rerunTests(cwd);
|
|
65
|
+
if (options.json) {
|
|
66
|
+
process.stdout.write(JSON.stringify({ cwd, ...r }, null, 2) + '\n');
|
|
67
|
+
return r.ran && (r.exitCode ?? 1) !== 0 ? 1 : 0;
|
|
68
|
+
}
|
|
69
|
+
if (options.proof) {
|
|
70
|
+
process.stdout.write(buildProofBlock(cwd, r));
|
|
71
|
+
return r.ran && (r.exitCode ?? 1) !== 0 ? 1 : 0;
|
|
72
|
+
}
|
|
73
|
+
if (!r.ran) {
|
|
74
|
+
process.stdout.write(colors.dim(`No test suite codehere can run here — ${r.skippedReason || 'none detected'}.\n`) +
|
|
75
|
+
colors.dim('codehere proves the AI by re-running your tests; add one (npm test / node --test / pytest / go test / cargo test) and run check again.\n'));
|
|
76
|
+
return 0; // nothing to verify is not a failure
|
|
77
|
+
}
|
|
78
|
+
// Honesty: exit 126/127 means the test command couldn't be RUN (binary missing /
|
|
79
|
+
// deps not installed), not that the tests failed. Never report "FAIL" for that.
|
|
80
|
+
if (r.exitCode === 126 || r.exitCode === 127) {
|
|
81
|
+
process.stdout.write('\n' +
|
|
82
|
+
colors.error(`${symbols.error} Couldn't run the tests`) +
|
|
83
|
+
colors.dim(` — '${r.command}' exited ${r.exitCode} (the runner isn't installed; try installing deps, then re-run).\n\n`));
|
|
84
|
+
return 1; // non-zero for CI (unverified), but NOT a false "tests failed"
|
|
85
|
+
}
|
|
86
|
+
const passed = (r.exitCode ?? 1) === 0;
|
|
87
|
+
const counts = typeof r.passed === 'number' || typeof r.failed === 'number'
|
|
88
|
+
? ` ${r.passed ?? '?'} passed · ${r.failed ?? 0} failed`
|
|
89
|
+
: '';
|
|
90
|
+
process.stdout.write('\n' +
|
|
91
|
+
(passed ? colors.success(`${symbols.success} Tests pass`) : colors.error(`${symbols.error} Tests FAIL`)) +
|
|
92
|
+
colors.dim(` — codehere re-ran them itself (${r.command}, exit ${r.exitCode})${counts}\n`) +
|
|
93
|
+
colors.dim("This is observed reality, not the agent's claim.\n\n"));
|
|
94
|
+
return passed ? 0 : 1;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=check-command.js.map
|
|
@@ -92,6 +92,10 @@ function statusTag(s) {
|
|
|
92
92
|
return colors.gray('● manual ');
|
|
93
93
|
case 'disconnected':
|
|
94
94
|
return colors.gray('○ disconnected');
|
|
95
|
+
default:
|
|
96
|
+
// A status this CLI doesn't know yet must never print "undefined"
|
|
97
|
+
// (2026-06-07 audit, Lane C: draw.io/GitHub rows did exactly that).
|
|
98
|
+
return colors.gray(('○ ' + String(s ?? 'unknown')).padEnd(14).slice(0, 14));
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
@@ -128,9 +132,24 @@ async function connect(providerArg, json, openBrowser) {
|
|
|
128
132
|
if (json) {
|
|
129
133
|
process.stdout.write(JSON.stringify({ error: 'connect_failed', detail: r.error }, null, 2) + '\n');
|
|
130
134
|
}
|
|
131
|
-
else {
|
|
135
|
+
else if (r.status === 0) {
|
|
136
|
+
// Only a NETWORK failure means the daemon is unreachable.
|
|
132
137
|
process.stderr.write(daemonUnreachableNote(r.error ?? ''));
|
|
133
138
|
}
|
|
139
|
+
else {
|
|
140
|
+
// The daemon answered — show ITS message, don't blame the connection
|
|
141
|
+
// (2026-06-07 audit, Lane C: a 404 from a live daemon printed
|
|
142
|
+
// "Daemon not reachable").
|
|
143
|
+
let msg = r.error ?? `request failed (${r.status})`;
|
|
144
|
+
try {
|
|
145
|
+
const j = JSON.parse(msg);
|
|
146
|
+
msg = j.error || j.message || msg;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
/* raw text is fine */
|
|
150
|
+
}
|
|
151
|
+
process.stderr.write(colors.error(`${symbols.error} ${providerArg}: ${msg}\n`));
|
|
152
|
+
}
|
|
134
153
|
return 2;
|
|
135
154
|
}
|
|
136
155
|
const url = r.data?.authorizeUrl;
|
|
@@ -47,7 +47,7 @@ const COMMAND_INFO = {
|
|
|
47
47
|
},
|
|
48
48
|
trust: {
|
|
49
49
|
name: 'trust',
|
|
50
|
-
description: 'Scan your project for security issues
|
|
50
|
+
description: 'Scan your project for security issues · instant score, no API key needed',
|
|
51
51
|
usage: 'codehere trust [path] [options]',
|
|
52
52
|
examples: [
|
|
53
53
|
'codehere trust .',
|
|
@@ -82,7 +82,7 @@ const COMMAND_INFO = {
|
|
|
82
82
|
},
|
|
83
83
|
status: {
|
|
84
84
|
name: 'status',
|
|
85
|
-
description: 'Dashboard
|
|
85
|
+
description: 'Dashboard · sessions, blocked calls, audit trends',
|
|
86
86
|
usage: 'codehere status [options]',
|
|
87
87
|
examples: ['codehere status', 'codehere status --sessions', 'codehere status --weekly', 'codehere status --json'],
|
|
88
88
|
options: [
|
|
@@ -172,7 +172,7 @@ const COMMAND_INFO = {
|
|
|
172
172
|
},
|
|
173
173
|
health: {
|
|
174
174
|
name: 'health',
|
|
175
|
-
description: 'System health check
|
|
175
|
+
description: 'System health check · filesystem, memory, audit log accessible',
|
|
176
176
|
usage: 'codehere health [options]',
|
|
177
177
|
examples: ['codehere health', 'codehere health --json'],
|
|
178
178
|
options: [{ flag: '--json', description: 'Output as JSON' }],
|
|
@@ -269,7 +269,7 @@ function displayCommandHelp(commandName) {
|
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
newline();
|
|
272
|
-
console.log(colors.bold(command.name) + colors.dim('
|
|
272
|
+
console.log(colors.bold(command.name) + colors.dim(' · ' + command.description));
|
|
273
273
|
newline();
|
|
274
274
|
console.log(colors.bold('Usage'));
|
|
275
275
|
console.log(' ' + colors.cyan(command.usage));
|
|
@@ -302,7 +302,7 @@ function printCmd(name, desc) {
|
|
|
302
302
|
}
|
|
303
303
|
function displayGeneralHelp() {
|
|
304
304
|
newline();
|
|
305
|
-
console.log(colors.bold('codehere') + colors.dim('
|
|
305
|
+
console.log(colors.bold('codehere') + colors.dim(' · Your AI coding agents: audited, cost-bounded, cross-vendor'));
|
|
306
306
|
newline();
|
|
307
307
|
console.log(colors.bold('Delegate'));
|
|
308
308
|
for (const cmd of ['start', 'task', 'kill']) {
|
|
@@ -132,10 +132,10 @@ export async function executeHookCommand(subcommand, options = {}) {
|
|
|
132
132
|
console.log(colors.dim(` Skills installed to: ${SKILLS_TARGET_DIR}`));
|
|
133
133
|
newline();
|
|
134
134
|
console.log(colors.bold('Slash commands now available in your AI assistant:'));
|
|
135
|
-
console.log(colors.dim(' /codehere:trust
|
|
136
|
-
console.log(colors.dim(' /codehere:report
|
|
137
|
-
console.log(colors.dim(' /codehere:audit
|
|
138
|
-
console.log(colors.dim(' /codehere:hook-status
|
|
135
|
+
console.log(colors.dim(' /codehere:trust · scan current project'));
|
|
136
|
+
console.log(colors.dim(' /codehere:report · generate shareable audit report'));
|
|
137
|
+
console.log(colors.dim(' /codehere:audit · pre-merge security check'));
|
|
138
|
+
console.log(colors.dim(' /codehere:hook-status · check hook health'));
|
|
139
139
|
}
|
|
140
140
|
newline();
|
|
141
141
|
console.log(colors.dim(' Restart your AI assistant for changes to take effect.'));
|
|
@@ -219,7 +219,7 @@ export async function executeHookCommand(subcommand, options = {}) {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
else {
|
|
222
|
-
console.log(colors.dim(" No events logged yet
|
|
222
|
+
console.log(colors.dim(" No events logged yet · run Claude Code and it'll start tracking."));
|
|
223
223
|
newline();
|
|
224
224
|
}
|
|
225
225
|
break;
|
|
@@ -117,7 +117,7 @@ Things you've been burned by.]
|
|
|
117
117
|
|
|
118
118
|
## Implement a change
|
|
119
119
|
|
|
120
|
-
> "Read .codehere/CONTEXT.md. [Describe the change in user-visible terms
|
|
120
|
+
> "Read .codehere/CONTEXT.md. [Describe the change in user-visible terms · what
|
|
121
121
|
> behavior is added, removed, or changed.] Match existing conventions. Don't
|
|
122
122
|
> touch unrelated files. Don't add new dependencies."
|
|
123
123
|
|
|
@@ -225,7 +225,7 @@ export const F3_TEMPLATES = [
|
|
|
225
225
|
"version": "v0.5",
|
|
226
226
|
"patterns_builtin": [],
|
|
227
227
|
"patterns_project": [],
|
|
228
|
-
"_note": "Run 'codehere protocols export' to populate patterns_builtin[] from the trust-scorer pattern bank. Add project-specific patterns to patterns_project[]
|
|
228
|
+
"_note": "Run 'codehere protocols export' to populate patterns_builtin[] from the trust-scorer pattern bank. Add project-specific patterns to patterns_project[] · those are preserved across re-exports. Do NOT hand-edit patterns_builtin[] · it's regenerated on each export."
|
|
229
229
|
}
|
|
230
230
|
`,
|
|
231
231
|
},
|
|
@@ -430,69 +430,93 @@ function writeTemplate(codehereDir, tmpl, force) {
|
|
|
430
430
|
writeFileSync(target, tmpl.body);
|
|
431
431
|
return 'created';
|
|
432
432
|
}
|
|
433
|
+
export function scaffoldProjectAt(root, options = {}) {
|
|
434
|
+
const codehereDir = join(root, '.codehere');
|
|
435
|
+
const empty = { created: [], skipped: [], refused: [], refusedDirs: [] };
|
|
436
|
+
const noEpisodic = { result: 'failed', sourcePath: '', targetPath: '' };
|
|
437
|
+
// Refuse if .codehere itself is a symlink — would redirect every subsequent write.
|
|
438
|
+
if (existsSync(codehereDir) && isSymlink(codehereDir)) {
|
|
439
|
+
return {
|
|
440
|
+
ok: false,
|
|
441
|
+
reason: 'codehere-dir-is-symlink',
|
|
442
|
+
...empty,
|
|
443
|
+
episodic: noEpisodic,
|
|
444
|
+
gitignore: { result: 'skipped' },
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (!existsSync(codehereDir)) {
|
|
448
|
+
mkdirSync(codehereDir, { recursive: true });
|
|
449
|
+
}
|
|
450
|
+
else if (!statSync(codehereDir).isDirectory()) {
|
|
451
|
+
return {
|
|
452
|
+
ok: false,
|
|
453
|
+
reason: 'codehere-not-a-directory',
|
|
454
|
+
...empty,
|
|
455
|
+
episodic: noEpisodic,
|
|
456
|
+
gitignore: { result: 'skipped' },
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
// F3 layout: ensure all directories exist before writing any seeds.
|
|
460
|
+
// Per-segment symlink check on EACH directory's ancestors; if .codehere/memory
|
|
461
|
+
// (or skills, or protocols, etc.) is a symlink, refuse to mkdir into it.
|
|
462
|
+
const refusedDirs = [];
|
|
463
|
+
for (const dir of F3_DIRECTORIES) {
|
|
464
|
+
const offending = findSymlinkAncestor(codehereDir, dir);
|
|
465
|
+
const fullPath = join(codehereDir, dir);
|
|
466
|
+
if (offending !== null || isSymlink(fullPath)) {
|
|
467
|
+
refusedDirs.push(dir);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
mkdirSync(fullPath, { recursive: true });
|
|
471
|
+
}
|
|
472
|
+
const created = [];
|
|
473
|
+
const skipped = [];
|
|
474
|
+
const refused = [];
|
|
475
|
+
// v0.4.1 templates first (CONTEXT/HANDOVER/PROMPTS at the top level).
|
|
476
|
+
for (const tmpl of TEMPLATES) {
|
|
477
|
+
const outcome = writeTemplate(codehereDir, tmpl, !!options.force);
|
|
478
|
+
if (outcome === 'created')
|
|
479
|
+
created.push(tmpl.filename);
|
|
480
|
+
else if (outcome === 'skipped')
|
|
481
|
+
skipped.push(tmpl.filename);
|
|
482
|
+
else
|
|
483
|
+
refused.push(tmpl.filename);
|
|
484
|
+
}
|
|
485
|
+
// F3 v0.5 seed files (memory layers + skills + protocols).
|
|
486
|
+
for (const tmpl of F3_TEMPLATES) {
|
|
487
|
+
const outcome = writeTemplate(codehereDir, tmpl, !!options.force);
|
|
488
|
+
if (outcome === 'created')
|
|
489
|
+
created.push(tmpl.filename);
|
|
490
|
+
else if (outcome === 'skipped')
|
|
491
|
+
skipped.push(tmpl.filename);
|
|
492
|
+
else
|
|
493
|
+
refused.push(tmpl.filename);
|
|
494
|
+
}
|
|
495
|
+
// F3 Task 3 — episodic AGENT_LEARNINGS.jsonl symlink to user's session log.
|
|
496
|
+
const episodic = ensureEpisodicSymlink(codehereDir, resolveHome());
|
|
497
|
+
const gitignore = options.noGitignore ? { result: 'skipped' } : ensureGitignore(root);
|
|
498
|
+
return { ok: true, created, skipped, refused, refusedDirs, episodic, gitignore };
|
|
499
|
+
}
|
|
433
500
|
export function executeInitCommand(options = {}) {
|
|
434
501
|
try {
|
|
435
502
|
const { root, type } = resolveTargetRoot();
|
|
436
|
-
const codehereDir = join(root, '.codehere');
|
|
437
503
|
newline();
|
|
438
504
|
console.log(colors.bold('codehere init'));
|
|
439
505
|
console.log(colors.dim(` scaffolding .codehere/ at ${type} root: ${root}`));
|
|
440
506
|
newline();
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
|
|
507
|
+
const r = scaffoldProjectAt(root, options);
|
|
508
|
+
if (!r.ok) {
|
|
509
|
+
if (r.reason === 'codehere-dir-is-symlink') {
|
|
510
|
+
console.log(` ${error('refused')} .codehere is a symlink · refusing to follow. Remove it first.`);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
console.log(` ${error('refused')} .codehere exists but is not a directory.`);
|
|
514
|
+
}
|
|
444
515
|
newline();
|
|
445
516
|
process.exitCode = 1;
|
|
446
517
|
return;
|
|
447
518
|
}
|
|
448
|
-
|
|
449
|
-
mkdirSync(codehereDir, { recursive: true });
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
const codehereStat = statSync(codehereDir);
|
|
453
|
-
if (!codehereStat.isDirectory()) {
|
|
454
|
-
console.log(` ${error('refused')} .codehere exists but is not a directory.`);
|
|
455
|
-
newline();
|
|
456
|
-
process.exitCode = 1;
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
// F3 layout: ensure all directories exist before writing any seeds.
|
|
461
|
-
// Per-segment symlink check on EACH directory's ancestors; if .codehere/memory
|
|
462
|
-
// (or skills, or protocols, etc.) is a symlink, refuse to mkdir into it.
|
|
463
|
-
const refusedDirs = [];
|
|
464
|
-
for (const dir of F3_DIRECTORIES) {
|
|
465
|
-
const offending = findSymlinkAncestor(codehereDir, dir);
|
|
466
|
-
const fullPath = join(codehereDir, dir);
|
|
467
|
-
if (offending !== null || isSymlink(fullPath)) {
|
|
468
|
-
refusedDirs.push(dir);
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
mkdirSync(fullPath, { recursive: true });
|
|
472
|
-
}
|
|
473
|
-
const created = [];
|
|
474
|
-
const skipped = [];
|
|
475
|
-
const refused = [];
|
|
476
|
-
// v0.4.1 templates first (CONTEXT/HANDOVER/PROMPTS at the top level).
|
|
477
|
-
for (const tmpl of TEMPLATES) {
|
|
478
|
-
const outcome = writeTemplate(codehereDir, tmpl, !!options.force);
|
|
479
|
-
if (outcome === 'created')
|
|
480
|
-
created.push(tmpl.filename);
|
|
481
|
-
else if (outcome === 'skipped')
|
|
482
|
-
skipped.push(tmpl.filename);
|
|
483
|
-
else
|
|
484
|
-
refused.push(tmpl.filename);
|
|
485
|
-
}
|
|
486
|
-
// F3 v0.5 seed files (memory layers + skills + protocols).
|
|
487
|
-
for (const tmpl of F3_TEMPLATES) {
|
|
488
|
-
const outcome = writeTemplate(codehereDir, tmpl, !!options.force);
|
|
489
|
-
if (outcome === 'created')
|
|
490
|
-
created.push(tmpl.filename);
|
|
491
|
-
else if (outcome === 'skipped')
|
|
492
|
-
skipped.push(tmpl.filename);
|
|
493
|
-
else
|
|
494
|
-
refused.push(tmpl.filename);
|
|
495
|
-
}
|
|
519
|
+
const { created, skipped, refused, refusedDirs } = r;
|
|
496
520
|
for (const f of created) {
|
|
497
521
|
console.log(` ${success(`created`)} .codehere/${f}`);
|
|
498
522
|
}
|
|
@@ -500,36 +524,35 @@ export function executeInitCommand(options = {}) {
|
|
|
500
524
|
console.log(` ${info(`exists `)} .codehere/${f} ${colors.dim('(use --force to overwrite)')}`);
|
|
501
525
|
}
|
|
502
526
|
for (const f of refused) {
|
|
503
|
-
console.log(` ${error(`refused`)} .codehere/${f} ${colors.dim('(symlink
|
|
527
|
+
console.log(` ${error(`refused`)} .codehere/${f} ${colors.dim('(symlink · remove it first)')}`);
|
|
504
528
|
}
|
|
505
529
|
for (const d of refusedDirs) {
|
|
506
|
-
console.log(` ${error(`refused`)} .codehere/${d}/ ${colors.dim('(intermediate symlink
|
|
530
|
+
console.log(` ${error(`refused`)} .codehere/${d}/ ${colors.dim('(intermediate symlink · remove it first)')}`);
|
|
507
531
|
}
|
|
508
532
|
// F3 Task 3 — episodic AGENT_LEARNINGS.jsonl symlink to user's session log.
|
|
509
|
-
const
|
|
510
|
-
const episodic = ensureEpisodicSymlink(codehereDir, home);
|
|
533
|
+
const episodic = r.episodic;
|
|
511
534
|
switch (episodic.result) {
|
|
512
535
|
case 'created':
|
|
513
|
-
console.log(` ${success('linked ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl
|
|
536
|
+
console.log(` ${success('linked ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl › ${episodic.targetPath}`);
|
|
514
537
|
break;
|
|
515
538
|
case 're-linked':
|
|
516
|
-
console.log(` ${success('re-linked')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl
|
|
539
|
+
console.log(` ${success('re-linked')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl › ${episodic.targetPath} ${colors.dim('(target updated)')}`);
|
|
517
540
|
break;
|
|
518
541
|
case 'exists':
|
|
519
542
|
// Quiet on the no-op case to avoid output noise on re-runs.
|
|
520
543
|
break;
|
|
521
544
|
case 'refused':
|
|
522
|
-
console.log(` ${error('refused ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl ${colors.dim('(real file or symlinked ancestor present
|
|
545
|
+
console.log(` ${error('refused ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl ${colors.dim('(real file or symlinked ancestor present · remove it first to enable the symlink)')}`);
|
|
523
546
|
break;
|
|
524
547
|
case 'root-skipped':
|
|
525
|
-
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(running as root
|
|
548
|
+
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(running as root · would create root-owned files in HOME; re-run as your normal user)')}`);
|
|
526
549
|
break;
|
|
527
550
|
case 'failed':
|
|
528
|
-
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(could not write
|
|
551
|
+
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(could not write · HOME may be read-only)')}`);
|
|
529
552
|
break;
|
|
530
553
|
}
|
|
531
554
|
if (!options.noGitignore) {
|
|
532
|
-
const gitignoreResult =
|
|
555
|
+
const gitignoreResult = r.gitignore;
|
|
533
556
|
newline();
|
|
534
557
|
switch (gitignoreResult.result) {
|
|
535
558
|
case 'updated':
|
|
@@ -542,7 +565,7 @@ export function executeInitCommand(options = {}) {
|
|
|
542
565
|
console.log(` ${warning('skipped')} .gitignore ${colors.dim('(no .git directory at root)')}`);
|
|
543
566
|
break;
|
|
544
567
|
case 'refused-symlink':
|
|
545
|
-
console.log(` ${error('refused')} .gitignore is a symlink
|
|
568
|
+
console.log(` ${error('refused')} .gitignore is a symlink · left untouched.`);
|
|
546
569
|
break;
|
|
547
570
|
}
|
|
548
571
|
}
|
|
@@ -560,7 +583,7 @@ export function executeInitCommand(options = {}) {
|
|
|
560
583
|
}
|
|
561
584
|
catch (e) {
|
|
562
585
|
const msg = e instanceof Error ? e.message : String(e);
|
|
563
|
-
console.error(`\n ${error('failed')} codehere init
|
|
586
|
+
console.error(`\n ${error('failed')} codehere init · ${msg}\n`);
|
|
564
587
|
process.exitCode = 1;
|
|
565
588
|
}
|
|
566
589
|
}
|