codehere 1.0.1 → 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 +36 -192
- 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 +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- 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/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- 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 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- 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/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- 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 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- 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 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- 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 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- 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 -105
- 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 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,590 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Init Command — scaffold a `.codehere/` directory at the repo root.
|
|
3
|
-
*
|
|
4
|
-
* The user-side analog to `git init`: gives the project a small, conventional
|
|
5
|
-
* shape codehere can read from when an agent runs here.
|
|
6
|
-
*
|
|
7
|
-
* v0.3.2 surface (the original "three files"):
|
|
8
|
-
* .codehere/CONTEXT.md — what this repo is, conventions, constraints
|
|
9
|
-
* .codehere/HANDOVER.md — manual cross-session continuity (until F3 ships)
|
|
10
|
-
* .codehere/PROMPTS.md — your library of recurring task prompts
|
|
11
|
-
*
|
|
12
|
-
* v0.5 F3 surface — the four-layer memory tree per docs/design/f3_memory_spec.md:
|
|
13
|
-
* .codehere/memory/working/ — current-session scratch (gitignored)
|
|
14
|
-
* .codehere/memory/episodic/ — execution record (committable)
|
|
15
|
-
* .codehere/memory/semantic/ — graduated lessons (committed)
|
|
16
|
-
* .codehere/memory/personal/ — per-developer prefs (gitignored)
|
|
17
|
-
* .codehere/memory/candidates/ — staged patterns awaiting graduate (gitignored)
|
|
18
|
-
* .codehere/skills/ — task-shape library (committed)
|
|
19
|
-
* .codehere/protocols/ — safety + tool config (committed)
|
|
20
|
-
*
|
|
21
|
-
* F3 Task 1 = scaffold these directories + minimal seed files. Migration of
|
|
22
|
-
* the existing CONTEXT.md/HANDOVER.md/PROMPTS.md INTO the new layout is Task 2
|
|
23
|
-
* (`codehere memory init`). Until then, both layouts coexist — the v0.4.1
|
|
24
|
-
* files stay where they are; the F3 layout is added alongside.
|
|
25
|
-
*
|
|
26
|
-
* HOME is overridable via `CODEHERE_HOME` for test isolation (so the test
|
|
27
|
-
* suite never writes to the real user's home).
|
|
28
|
-
*
|
|
29
|
-
* Conventions:
|
|
30
|
-
* - CONTEXT.md and PROMPTS.md are committed (project config, shared)
|
|
31
|
-
* - HANDOVER.md is git-ignored by default (per-machine working state)
|
|
32
|
-
* - F3 working/, personal/, candidates/ are git-ignored (per-machine state)
|
|
33
|
-
* - F3 episodic/, semantic/, skills/, protocols/ are committable
|
|
34
|
-
*
|
|
35
|
-
* Targets the repo root (via findCodebaseRoot), not cwd, so `cd src && codehere init`
|
|
36
|
-
* still scaffolds at the repo root, not inside src/.
|
|
37
|
-
*
|
|
38
|
-
* Idempotent: re-running detects existing files and skips them unless --force.
|
|
39
|
-
* Refuses to follow symlinks (lstat check) so a hostile/misplaced symlink at
|
|
40
|
-
* `.codehere/CONTEXT.md` or `.gitignore` cannot redirect writes outside the repo.
|
|
41
|
-
*/
|
|
42
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, lstatSync, statSync, symlinkSync, readlinkSync, rmSync, } from 'fs';
|
|
43
|
-
import { homedir } from 'os';
|
|
44
|
-
import { join, dirname } from 'path';
|
|
45
|
-
import { colors, success, info, warning, error, newline } from '../../../ui.js';
|
|
46
|
-
import { findCodebaseRoot } from '../../../domain/services/codebase-detector.js';
|
|
47
|
-
const TEMPLATES = [
|
|
48
|
-
{
|
|
49
|
-
filename: 'CONTEXT.md',
|
|
50
|
-
body: `# Project Context
|
|
51
|
-
|
|
52
|
-
> codehere reads this when an agent runs in this directory.
|
|
53
|
-
> Keep it short. Keep it true. The agent will follow what's written here.
|
|
54
|
-
|
|
55
|
-
## What this is
|
|
56
|
-
|
|
57
|
-
[One-paragraph description of what this repo does and who uses it.]
|
|
58
|
-
|
|
59
|
-
## Conventions
|
|
60
|
-
|
|
61
|
-
[How this codebase is organized — entry points, file naming, framework version.
|
|
62
|
-
Anything an agent would otherwise have to discover by grep.]
|
|
63
|
-
|
|
64
|
-
## Style
|
|
65
|
-
|
|
66
|
-
[Tone for generated code. Test framework. Lint rules. Imports style.
|
|
67
|
-
Prefer X. Avoid Y.]
|
|
68
|
-
|
|
69
|
-
## Constraints
|
|
70
|
-
|
|
71
|
-
[Don't do X. Always do Y. Files that are off-limits.
|
|
72
|
-
Things you've been burned by.]
|
|
73
|
-
|
|
74
|
-
## Useful commands
|
|
75
|
-
|
|
76
|
-
\`\`\`
|
|
77
|
-
[build, test, lint, dev server — the agent will run these]
|
|
78
|
-
\`\`\`
|
|
79
|
-
`,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
filename: 'HANDOVER.md',
|
|
83
|
-
body: `# Handover
|
|
84
|
-
|
|
85
|
-
> Update at the end of each working session.
|
|
86
|
-
> codehere has no project memory across sessions yet (F3 ships in v0.5).
|
|
87
|
-
> This file is the manual handover so the next session — or the next agent —
|
|
88
|
-
> can pick up without you re-explaining.
|
|
89
|
-
|
|
90
|
-
## Last session
|
|
91
|
-
|
|
92
|
-
- **Date:**
|
|
93
|
-
- **What got done:**
|
|
94
|
-
- **What's in progress:**
|
|
95
|
-
- **What to pick up next:**
|
|
96
|
-
- **Open questions:**
|
|
97
|
-
|
|
98
|
-
## Decisions worth remembering
|
|
99
|
-
|
|
100
|
-
[Decisions made this session that future sessions should respect.]
|
|
101
|
-
|
|
102
|
-
## Files touched recently
|
|
103
|
-
|
|
104
|
-
[List the files you've been working on. The agent can prioritize them.]
|
|
105
|
-
`,
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
filename: 'PROMPTS.md',
|
|
109
|
-
body: `# Prompts
|
|
110
|
-
|
|
111
|
-
> Your library of recurring task prompts.
|
|
112
|
-
> Copy + adapt + delegate via \`codehere task\` or the web UI.
|
|
113
|
-
>
|
|
114
|
-
> The codehere prompt formula: **Context + End State + Constraints**.
|
|
115
|
-
> Stack-specific details (test framework, language, file conventions) belong
|
|
116
|
-
> in CONTEXT.md, not duplicated in every prompt here.
|
|
117
|
-
|
|
118
|
-
## Implement a change
|
|
119
|
-
|
|
120
|
-
> "Read .codehere/CONTEXT.md. [Describe the change in user-visible terms · what
|
|
121
|
-
> behavior is added, removed, or changed.] Match existing conventions. Don't
|
|
122
|
-
> touch unrelated files. Don't add new dependencies."
|
|
123
|
-
|
|
124
|
-
## Add tests
|
|
125
|
-
|
|
126
|
-
> "Read .codehere/CONTEXT.md. Add tests for [FILE / behavior]. Cover happy
|
|
127
|
-
> path, edge cases, and error paths. Match the existing test style and
|
|
128
|
-
> framework. Don't lower the strictness of any existing test."
|
|
129
|
-
|
|
130
|
-
## Refactor safely
|
|
131
|
-
|
|
132
|
-
> "Read .codehere/CONTEXT.md. Refactor [FILE / module] to [GOAL]. Keep the
|
|
133
|
-
> public API stable. All existing tests must still pass. Don't introduce
|
|
134
|
-
> new dependencies. Show the diff before applying."
|
|
135
|
-
|
|
136
|
-
## Review changes
|
|
137
|
-
|
|
138
|
-
> "Read .codehere/CONTEXT.md. Review the changes in [PR / branch / file] for:
|
|
139
|
-
> security issues, missing tests, breaking changes, style violations, and
|
|
140
|
-
> alignment with the constraints in CONTEXT.md. Output a short markdown
|
|
141
|
-
> report; don't change any code."
|
|
142
|
-
`,
|
|
143
|
-
},
|
|
144
|
-
];
|
|
145
|
-
/**
|
|
146
|
-
* F3 v0.5 seed files — coexist with the v0.4.1 TEMPLATES above.
|
|
147
|
-
*
|
|
148
|
-
* Bodies are deliberately minimal — these are scaffolds, not docs. The richer
|
|
149
|
-
* content lands when memory accumulates (episodic auto-fill from sessions,
|
|
150
|
-
* semantic graduate via `codehere memory graduate`, skills authored by hand).
|
|
151
|
-
*
|
|
152
|
-
* EXPORTED so `memory-command.ts` can recognize "this file is still the
|
|
153
|
-
* default seed (user hasn't edited)" and overwrite it during migration —
|
|
154
|
-
* without that check, an init run before memory init would block migration
|
|
155
|
-
* (target exists ≠ user-edited target exists).
|
|
156
|
-
*/
|
|
157
|
-
export const F3_TEMPLATES = [
|
|
158
|
-
{
|
|
159
|
-
filename: 'memory/working/session.md',
|
|
160
|
-
body: `# Working session
|
|
161
|
-
|
|
162
|
-
> Per-machine, gitignored. What's in flight right now.
|
|
163
|
-
>
|
|
164
|
-
> codehere reads this on agent spawn. Keep it short.
|
|
165
|
-
> When the session ends, optionally graduate useful patterns into
|
|
166
|
-
> \`memory/semantic/lessons.jsonl\` via \`codehere memory graduate\`.
|
|
167
|
-
|
|
168
|
-
## In flight
|
|
169
|
-
|
|
170
|
-
- [ ]
|
|
171
|
-
|
|
172
|
-
## Decisions made this session
|
|
173
|
-
|
|
174
|
-
-
|
|
175
|
-
|
|
176
|
-
## Files touched
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
`,
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
filename: 'memory/semantic/lessons.jsonl',
|
|
183
|
-
body: '',
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
filename: 'memory/personal/preferences.json',
|
|
187
|
-
body: '{}\n',
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
filename: 'skills/_index.md',
|
|
191
|
-
body: `# Skills
|
|
192
|
-
|
|
193
|
-
> One folder per skill. Each \`<skill-name>/SKILL.md\` is a task-shape
|
|
194
|
-
> template — the prompt formula plus example inputs/outputs.
|
|
195
|
-
>
|
|
196
|
-
> Migrating from the flat \`PROMPTS.md\`? Run \`codehere memory init\` (Task 2
|
|
197
|
-
> in the F3 spec). Until then both formats coexist — agents read whichever
|
|
198
|
-
> is present.
|
|
199
|
-
`,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
filename: 'skills/_manifest.jsonl',
|
|
203
|
-
body: '',
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
filename: 'protocols/permissions.md',
|
|
207
|
-
body: `# Permissions
|
|
208
|
-
|
|
209
|
-
> Human-readable allow/deny policy. The codehere PreToolUse hook reads
|
|
210
|
-
> \`hook_patterns.json\` next to this file for the machine-readable rules;
|
|
211
|
-
> this Markdown is the rationale and exceptions.
|
|
212
|
-
|
|
213
|
-
## Default policy
|
|
214
|
-
|
|
215
|
-
[Allow: read, build, test. Confirm: write, network. Deny: secret access.]
|
|
216
|
-
|
|
217
|
-
## Project-specific exceptions
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
`,
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
filename: 'protocols/hook_patterns.json',
|
|
224
|
-
body: `{
|
|
225
|
-
"version": "v0.5",
|
|
226
|
-
"patterns_builtin": [],
|
|
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[] · those are preserved across re-exports. Do NOT hand-edit patterns_builtin[] · it's regenerated on each export."
|
|
229
|
-
}
|
|
230
|
-
`,
|
|
231
|
-
},
|
|
232
|
-
];
|
|
233
|
-
/**
|
|
234
|
-
* Subdirectories created blindly (regardless of seed files). These ensure
|
|
235
|
-
* the F3 memory tree is fully present even before the first auto-populated
|
|
236
|
-
* artifact lands (e.g. episodic/AGENT_LEARNINGS.jsonl is a Task-3 symlink,
|
|
237
|
-
* not seeded here).
|
|
238
|
-
*/
|
|
239
|
-
const F3_DIRECTORIES = [
|
|
240
|
-
'memory/working',
|
|
241
|
-
'memory/episodic',
|
|
242
|
-
'memory/semantic',
|
|
243
|
-
'memory/personal',
|
|
244
|
-
'memory/candidates',
|
|
245
|
-
'skills',
|
|
246
|
-
'protocols',
|
|
247
|
-
];
|
|
248
|
-
const GITIGNORE_MARKER = '# Added by codehere init';
|
|
249
|
-
const GITIGNORE_LINES = [
|
|
250
|
-
'.codehere/HANDOVER.md',
|
|
251
|
-
'.codehere/memory/working/',
|
|
252
|
-
'.codehere/memory/personal/',
|
|
253
|
-
'.codehere/memory/candidates/',
|
|
254
|
-
];
|
|
255
|
-
/**
|
|
256
|
-
* Resolve the directory to scaffold. Prefers git/package root over cwd so
|
|
257
|
-
* `cd repo/src && codehere init` still lands at repo/.
|
|
258
|
-
*/
|
|
259
|
-
function resolveTargetRoot() {
|
|
260
|
-
const info = findCodebaseRoot();
|
|
261
|
-
if (info)
|
|
262
|
-
return { root: info.rootPath, type: info.type };
|
|
263
|
-
return { root: process.cwd(), type: 'directory' };
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Resolve the user "home" for HOME-scoped state. Override-able via
|
|
267
|
-
* `CODEHERE_HOME` for test isolation (so test suites never write to the real
|
|
268
|
-
* user's home).
|
|
269
|
-
*/
|
|
270
|
-
function resolveHome() {
|
|
271
|
-
return process.env.CODEHERE_HOME || homedir();
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Reject a path that exists AND is a symlink. Refusing on symlinks (rather
|
|
275
|
-
* than following) prevents an init run from clobbering files outside the
|
|
276
|
-
* repo if a hostile or misplaced symlink already exists at the target.
|
|
277
|
-
*/
|
|
278
|
-
function isSymlink(path) {
|
|
279
|
-
try {
|
|
280
|
-
return lstatSync(path).isSymbolicLink();
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Walk every intermediate ancestor BELOW codehereDir up to (but not
|
|
288
|
-
* including) the leaf, refusing if any segment is a symlink. The outer
|
|
289
|
-
* .codehere symlink check in executeInitCommand handles codehereDir itself;
|
|
290
|
-
* the leaf is checked separately by writeTemplate / the F3_DIRECTORIES loop.
|
|
291
|
-
*
|
|
292
|
-
* Without this, an attacker can plant
|
|
293
|
-
* `.codehere/memory` as a symlink to `/etc` BEFORE init runs, and the
|
|
294
|
-
* subsequent `mkdirSync({ recursive: true })` + `writeFileSync` would happily
|
|
295
|
-
* follow it — bypassing the leaf-level symlink check that only guards the
|
|
296
|
-
* final filename.
|
|
297
|
-
*
|
|
298
|
-
* Caught by codex Round 1 review of F3 Task 1: the original v0.3.2 init
|
|
299
|
-
* only had one level of nesting (.codehere/CONTEXT.md), so checking
|
|
300
|
-
* .codehere itself + the leaf was enough. Nested F3 paths
|
|
301
|
-
* (memory/working/session.md, protocols/permissions.md, ...) need every
|
|
302
|
-
* intermediate dir checked.
|
|
303
|
-
*
|
|
304
|
-
* Returns the offending segment if any is a symlink, or null if the path is
|
|
305
|
-
* safe to mkdir/write into.
|
|
306
|
-
*/
|
|
307
|
-
function findSymlinkAncestor(codehereDir, relativePath) {
|
|
308
|
-
// Split on both POSIX and Windows separators (codex Round 2 fix). Without
|
|
309
|
-
// this, on Windows where `sep === '\\'`, the slash-delimited literals our
|
|
310
|
-
// callers pass (e.g. 'memory/episodic/AGENT_LEARNINGS.jsonl') would yield a
|
|
311
|
-
// single-segment array, the loop would never iterate, and the entire
|
|
312
|
-
// ancestor-symlink defense would silently bypass. POSIX-only is the stated
|
|
313
|
-
// assumption, but defending the security check against the easy
|
|
314
|
-
// platform-separator footgun costs one regex.
|
|
315
|
-
const segments = relativePath.split(/[/\\]/);
|
|
316
|
-
// Defensive: reject any relative path containing .. — even though templates
|
|
317
|
-
// are hard-coded, this protects against future schema additions slipping a
|
|
318
|
-
// traversal through.
|
|
319
|
-
if (segments.some((seg) => seg === '..' || seg === '')) {
|
|
320
|
-
return relativePath;
|
|
321
|
-
}
|
|
322
|
-
// Walk codehereDir, codehereDir/seg1, codehereDir/seg1/seg2, ... (excluding
|
|
323
|
-
// the final leaf — writeTemplate handles the leaf separately).
|
|
324
|
-
let current = codehereDir;
|
|
325
|
-
for (let i = 0; i < segments.length - 1; i++) {
|
|
326
|
-
current = join(current, segments[i]);
|
|
327
|
-
if (isSymlink(current))
|
|
328
|
-
return current;
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
function ensureGitignore(repoRoot) {
|
|
333
|
-
const gitDir = join(repoRoot, '.git');
|
|
334
|
-
if (!existsSync(gitDir))
|
|
335
|
-
return { result: 'no-git', addedLines: 0 };
|
|
336
|
-
const gitignorePath = join(repoRoot, '.gitignore');
|
|
337
|
-
if (isSymlink(gitignorePath))
|
|
338
|
-
return { result: 'refused-symlink', addedLines: 0 };
|
|
339
|
-
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';
|
|
340
|
-
// De-duplicate: only append lines that aren't already present.
|
|
341
|
-
const missing = GITIGNORE_LINES.filter((line) => !existing.includes(line));
|
|
342
|
-
if (missing.length === 0)
|
|
343
|
-
return { result: 'already-present', addedLines: 0 };
|
|
344
|
-
const block = (existing.length > 0 && !existing.endsWith('\n') ? '\n' : '') +
|
|
345
|
-
`\n${GITIGNORE_MARKER}\n` +
|
|
346
|
-
missing.join('\n') +
|
|
347
|
-
'\n';
|
|
348
|
-
appendFileSync(gitignorePath, block);
|
|
349
|
-
return { result: 'updated', addedLines: missing.length };
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* F3 Task 3 — bridge `.codehere/memory/episodic/AGENT_LEARNINGS.jsonl`
|
|
353
|
-
* to the user-scoped `~/.codehere/sessions.jsonl` execution log via a
|
|
354
|
-
* symlink. Lets any spawned agent read the project's full execution
|
|
355
|
-
* history through a single conventional path under .codehere/.
|
|
356
|
-
*
|
|
357
|
-
* Behavior:
|
|
358
|
-
* - source already a symlink pointing at the correct target → 'exists'
|
|
359
|
-
* - source already a symlink pointing elsewhere → re-link → 're-linked'
|
|
360
|
-
* (handles HOME-moved scenarios, fresh clone on a new machine, etc.)
|
|
361
|
-
* - source is a real file → 'refused' (DO NOT clobber user data)
|
|
362
|
-
* - target file missing → touch it (zero-byte; the real session log
|
|
363
|
-
* accumulates from agent runs)
|
|
364
|
-
* - else → create symlink → 'created'
|
|
365
|
-
*
|
|
366
|
-
* The symlink target is ABSOLUTE because the source lives inside the repo
|
|
367
|
-
* and the target lives in HOME — a relative path would be a long, fragile
|
|
368
|
-
* climb across many parent directories.
|
|
369
|
-
*/
|
|
370
|
-
export function ensureEpisodicSymlink(codehereDir, home) {
|
|
371
|
-
const sourcePath = join(codehereDir, 'memory/episodic/AGENT_LEARNINGS.jsonl');
|
|
372
|
-
const targetPath = join(home, '.codehere/sessions.jsonl');
|
|
373
|
-
// K2 Round 1 review fix — refuse if running as root. Otherwise this would
|
|
374
|
-
// touch ~/.codehere/sessions.jsonl as root, leaving a root-owned file the
|
|
375
|
-
// user's normal codehere invocation can't read or rotate.
|
|
376
|
-
if (typeof process.getuid === 'function' && process.getuid() === 0) {
|
|
377
|
-
return { result: 'root-skipped', sourcePath, targetPath };
|
|
378
|
-
}
|
|
379
|
-
// Codex Round 1 BLOCKER fix — same nested-symlink defense Task 1 applied
|
|
380
|
-
// to writeTemplate / F3_DIRECTORIES. Without this, an attacker plants
|
|
381
|
-
// `.codehere/memory` → `/etc` and `mkdirSync({recursive:true})` followed by
|
|
382
|
-
// `symlinkSync` would happily write outside the repo. Reuses the helper.
|
|
383
|
-
if (findSymlinkAncestor(codehereDir, 'memory/episodic/AGENT_LEARNINGS.jsonl') !== null) {
|
|
384
|
-
return { result: 'refused', sourcePath, targetPath };
|
|
385
|
-
}
|
|
386
|
-
try {
|
|
387
|
-
// 1. Refuse if source is a real file (would silently destroy user content).
|
|
388
|
-
if (existsSync(sourcePath) && !isSymlink(sourcePath)) {
|
|
389
|
-
return { result: 'refused', sourcePath, targetPath };
|
|
390
|
-
}
|
|
391
|
-
// 2. Already a symlink — check its target.
|
|
392
|
-
let wasReLinked = false;
|
|
393
|
-
if (isSymlink(sourcePath)) {
|
|
394
|
-
const current = readlinkSync(sourcePath);
|
|
395
|
-
if (current === targetPath) {
|
|
396
|
-
return { result: 'exists', sourcePath, targetPath };
|
|
397
|
-
}
|
|
398
|
-
// Wrong target — re-link. Track so we report 're-linked', not 'created'.
|
|
399
|
-
// (Codex Round 1 fix — the previous version returned 'created' here,
|
|
400
|
-
// making the documented 're-linked' outcome unreachable.)
|
|
401
|
-
rmSync(sourcePath);
|
|
402
|
-
wasReLinked = true;
|
|
403
|
-
}
|
|
404
|
-
// 3. Ensure target file exists (touch zero-byte if missing).
|
|
405
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
406
|
-
if (!existsSync(targetPath))
|
|
407
|
-
writeFileSync(targetPath, '');
|
|
408
|
-
// 4. Ensure source's parent dir exists (F3_DIRECTORIES creates this in
|
|
409
|
-
// init, but memory-command may call us before/without that path).
|
|
410
|
-
mkdirSync(dirname(sourcePath), { recursive: true });
|
|
411
|
-
// 5. Create the symlink with an absolute target.
|
|
412
|
-
symlinkSync(targetPath, sourcePath);
|
|
413
|
-
return { result: wasReLinked ? 're-linked' : 'created', sourcePath, targetPath };
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
return { result: 'failed', sourcePath, targetPath };
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
function writeTemplate(codehereDir, tmpl, force) {
|
|
420
|
-
const target = join(codehereDir, tmpl.filename);
|
|
421
|
-
// Refuse if ANY ancestor under .codehere/ is a symlink (codex Round 1 fix).
|
|
422
|
-
if (findSymlinkAncestor(codehereDir, tmpl.filename) !== null)
|
|
423
|
-
return 'refused';
|
|
424
|
-
if (isSymlink(target))
|
|
425
|
-
return 'refused';
|
|
426
|
-
if (existsSync(target) && !force)
|
|
427
|
-
return 'skipped';
|
|
428
|
-
// Ensure parent dir for nested seeds. Safe now that ancestors are verified.
|
|
429
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
430
|
-
writeFileSync(target, tmpl.body);
|
|
431
|
-
return 'created';
|
|
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
|
-
}
|
|
500
|
-
export function executeInitCommand(options = {}) {
|
|
501
|
-
try {
|
|
502
|
-
const { root, type } = resolveTargetRoot();
|
|
503
|
-
newline();
|
|
504
|
-
console.log(colors.bold('codehere init'));
|
|
505
|
-
console.log(colors.dim(` scaffolding .codehere/ at ${type} root: ${root}`));
|
|
506
|
-
newline();
|
|
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
|
-
}
|
|
515
|
-
newline();
|
|
516
|
-
process.exitCode = 1;
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
const { created, skipped, refused, refusedDirs } = r;
|
|
520
|
-
for (const f of created) {
|
|
521
|
-
console.log(` ${success(`created`)} .codehere/${f}`);
|
|
522
|
-
}
|
|
523
|
-
for (const f of skipped) {
|
|
524
|
-
console.log(` ${info(`exists `)} .codehere/${f} ${colors.dim('(use --force to overwrite)')}`);
|
|
525
|
-
}
|
|
526
|
-
for (const f of refused) {
|
|
527
|
-
console.log(` ${error(`refused`)} .codehere/${f} ${colors.dim('(symlink · remove it first)')}`);
|
|
528
|
-
}
|
|
529
|
-
for (const d of refusedDirs) {
|
|
530
|
-
console.log(` ${error(`refused`)} .codehere/${d}/ ${colors.dim('(intermediate symlink · remove it first)')}`);
|
|
531
|
-
}
|
|
532
|
-
// F3 Task 3 — episodic AGENT_LEARNINGS.jsonl symlink to user's session log.
|
|
533
|
-
const episodic = r.episodic;
|
|
534
|
-
switch (episodic.result) {
|
|
535
|
-
case 'created':
|
|
536
|
-
console.log(` ${success('linked ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl › ${episodic.targetPath}`);
|
|
537
|
-
break;
|
|
538
|
-
case 're-linked':
|
|
539
|
-
console.log(` ${success('re-linked')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl › ${episodic.targetPath} ${colors.dim('(target updated)')}`);
|
|
540
|
-
break;
|
|
541
|
-
case 'exists':
|
|
542
|
-
// Quiet on the no-op case to avoid output noise on re-runs.
|
|
543
|
-
break;
|
|
544
|
-
case 'refused':
|
|
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)')}`);
|
|
546
|
-
break;
|
|
547
|
-
case 'root-skipped':
|
|
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)')}`);
|
|
549
|
-
break;
|
|
550
|
-
case 'failed':
|
|
551
|
-
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(could not write · HOME may be read-only)')}`);
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
if (!options.noGitignore) {
|
|
555
|
-
const gitignoreResult = r.gitignore;
|
|
556
|
-
newline();
|
|
557
|
-
switch (gitignoreResult.result) {
|
|
558
|
-
case 'updated':
|
|
559
|
-
console.log(` ${success('updated')} .gitignore ${colors.dim(`(+${gitignoreResult.addedLines} entr${gitignoreResult.addedLines === 1 ? 'y' : 'ies'} for HANDOVER + memory/working/, /personal/, /candidates/)`)}`);
|
|
560
|
-
break;
|
|
561
|
-
case 'already-present':
|
|
562
|
-
console.log(` ${info('exists ')} .gitignore ${colors.dim('(codehere entries already ignored)')}`);
|
|
563
|
-
break;
|
|
564
|
-
case 'no-git':
|
|
565
|
-
console.log(` ${warning('skipped')} .gitignore ${colors.dim('(no .git directory at root)')}`);
|
|
566
|
-
break;
|
|
567
|
-
case 'refused-symlink':
|
|
568
|
-
console.log(` ${error('refused')} .gitignore is a symlink · left untouched.`);
|
|
569
|
-
break;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
newline();
|
|
573
|
-
console.log(colors.bold('Next:'));
|
|
574
|
-
console.log(` 1. Open ${colors.cyan('.codehere/CONTEXT.md')} and fill in what this repo is`);
|
|
575
|
-
console.log(` 2. Run ${colors.cyan('codehere start')} and delegate your first task`);
|
|
576
|
-
console.log(` 3. Track in-flight work in ${colors.cyan('.codehere/HANDOVER.md')} as you go`);
|
|
577
|
-
// Power-user / advanced layout entry points moved to docs/advanced/ per the
|
|
578
|
-
// "one canonical entry" rule (May 2 CUT pass). Used to print two extra
|
|
579
|
-
// dim lines pointing at `codehere memory show injection` and the F3
|
|
580
|
-
// canonical layout migration — both confused new users in real-task
|
|
581
|
-
// dogfood. Power users find them in docs/MEMORY.md.
|
|
582
|
-
newline();
|
|
583
|
-
}
|
|
584
|
-
catch (e) {
|
|
585
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
586
|
-
console.error(`\n ${error('failed')} codehere init · ${msg}\n`);
|
|
587
|
-
process.exitCode = 1;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
//# sourceMappingURL=init-command.js.map
|