@yagni-app/code 0.1.0 → 0.2.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 +65 -11
- package/dist/claudeCompat.d.ts +109 -0
- package/dist/claudeCompat.js +260 -0
- package/dist/claudePlugins.d.ts +109 -0
- package/dist/claudePlugins.js +336 -0
- package/dist/cli.d.ts +1 -5
- package/dist/cli.js +79 -20
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/crashReport.d.ts +135 -0
- package/dist/crashReport.js +291 -0
- package/dist/credentials.js +2 -1
- package/dist/distribution.d.ts +15 -0
- package/dist/distribution.js +45 -0
- package/dist/doctor.d.ts +27 -0
- package/dist/doctor.js +81 -3
- package/dist/extension/advisor.d.ts +84 -0
- package/dist/extension/advisor.js +101 -0
- package/dist/extension/askAdvisorTool.d.ts +83 -0
- package/dist/extension/askAdvisorTool.js +194 -0
- package/dist/extension/askYagniTool.d.ts +39 -0
- package/dist/extension/askYagniTool.js +63 -0
- package/dist/extension/bless.d.ts +45 -0
- package/dist/extension/bless.js +87 -0
- package/dist/extension/boostCommand.d.ts +144 -0
- package/dist/extension/boostCommand.js +263 -0
- package/dist/extension/branding.d.ts +79 -0
- package/dist/extension/branding.js +156 -0
- package/dist/extension/chipEditor.d.ts +117 -0
- package/dist/extension/chipEditor.js +373 -0
- package/dist/extension/claudeRules.d.ts +54 -0
- package/dist/extension/claudeRules.js +180 -0
- package/dist/extension/config.d.ts +173 -0
- package/dist/extension/config.js +194 -0
- package/dist/extension/costHud.d.ts +186 -0
- package/dist/extension/costHud.js +293 -0
- package/dist/extension/crashReport.d.ts +89 -0
- package/dist/extension/crashReport.js +241 -0
- package/dist/extension/decisionCapture.d.ts +52 -0
- package/dist/extension/decisionCapture.js +66 -0
- package/dist/extension/decisions.d.ts +83 -0
- package/dist/extension/decisions.js +200 -0
- package/dist/extension/diagnostics.d.ts +41 -0
- package/dist/extension/diagnostics.js +110 -0
- package/dist/extension/index.d.ts +155 -0
- package/dist/extension/index.js +597 -0
- package/dist/extension/initDone.d.ts +28 -0
- package/dist/extension/initDone.js +66 -0
- package/dist/extension/initPass.d.ts +170 -0
- package/dist/extension/initPass.js +394 -0
- package/dist/extension/mcpTools.d.ts +57 -0
- package/dist/extension/mcpTools.js +132 -0
- package/dist/extension/nextWorkTool.d.ts +51 -0
- package/dist/extension/nextWorkTool.js +80 -0
- package/dist/extension/permission.d.ts +91 -0
- package/dist/extension/permission.js +236 -0
- package/dist/extension/pipeline/activity.d.ts +37 -0
- package/dist/extension/pipeline/activity.js +151 -0
- package/dist/extension/pipeline/activityFeed.d.ts +59 -0
- package/dist/extension/pipeline/activityFeed.js +175 -0
- package/dist/extension/pipeline/budget.d.ts +48 -0
- package/dist/extension/pipeline/budget.js +68 -0
- package/dist/extension/pipeline/checkpoint.d.ts +31 -0
- package/dist/extension/pipeline/checkpoint.js +176 -0
- package/dist/extension/pipeline/eval.d.ts +205 -0
- package/dist/extension/pipeline/eval.js +226 -0
- package/dist/extension/pipeline/events.d.ts +56 -0
- package/dist/extension/pipeline/events.js +147 -0
- package/dist/extension/pipeline/findings.d.ts +42 -0
- package/dist/extension/pipeline/findings.js +144 -0
- package/dist/extension/pipeline/finish.d.ts +128 -0
- package/dist/extension/pipeline/finish.js +307 -0
- package/dist/extension/pipeline/goCommand.d.ts +146 -0
- package/dist/extension/pipeline/goCommand.js +1085 -0
- package/dist/extension/pipeline/goCompareCommand.d.ts +58 -0
- package/dist/extension/pipeline/goCompareCommand.js +203 -0
- package/dist/extension/pipeline/goFlags.d.ts +40 -0
- package/dist/extension/pipeline/goFlags.js +46 -0
- package/dist/extension/pipeline/goStatusCommands.d.ts +59 -0
- package/dist/extension/pipeline/goStatusCommands.js +188 -0
- package/dist/extension/pipeline/invocation.d.ts +45 -0
- package/dist/extension/pipeline/invocation.js +64 -0
- package/dist/extension/pipeline/orchestrator.d.ts +131 -0
- package/dist/extension/pipeline/orchestrator.js +645 -0
- package/dist/extension/pipeline/personas.d.ts +44 -0
- package/dist/extension/pipeline/personas.js +248 -0
- package/dist/extension/pipeline/resilience.d.ts +85 -0
- package/dist/extension/pipeline/resilience.js +166 -0
- package/dist/extension/pipeline/resume.d.ts +18 -0
- package/dist/extension/pipeline/resume.js +106 -0
- package/dist/extension/pipeline/runCostTable.d.ts +37 -0
- package/dist/extension/pipeline/runCostTable.js +165 -0
- package/dist/extension/pipeline/runRegistry.d.ts +112 -0
- package/dist/extension/pipeline/runRegistry.js +202 -0
- package/dist/extension/pipeline/runSession.d.ts +152 -0
- package/dist/extension/pipeline/runSession.js +167 -0
- package/dist/extension/pipeline/runState.d.ts +177 -0
- package/dist/extension/pipeline/runState.js +275 -0
- package/dist/extension/pipeline/runner.d.ts +98 -0
- package/dist/extension/pipeline/runner.js +310 -0
- package/dist/extension/pipeline/scrubSecrets.d.ts +15 -0
- package/dist/extension/pipeline/scrubSecrets.js +32 -0
- package/dist/extension/pipeline/stages.d.ts +53 -0
- package/dist/extension/pipeline/stages.js +115 -0
- package/dist/extension/pipeline/ticketResolution.d.ts +34 -0
- package/dist/extension/pipeline/ticketResolution.js +75 -0
- package/dist/extension/pipeline/types.d.ts +439 -0
- package/dist/extension/pipeline/types.js +48 -0
- package/dist/extension/pipeline/verify.d.ts +277 -0
- package/dist/extension/pipeline/verify.js +763 -0
- package/dist/extension/pipeline/workspace.d.ts +32 -0
- package/dist/extension/pipeline/workspace.js +73 -0
- package/dist/extension/pipeline/worktree.d.ts +107 -0
- package/dist/extension/pipeline/worktree.js +202 -0
- package/dist/extension/provider.d.ts +17 -0
- package/dist/extension/provider.js +33 -0
- package/dist/extension/recall.d.ts +93 -0
- package/dist/extension/recall.js +190 -0
- package/dist/extension/recordContextTool.d.ts +38 -0
- package/dist/extension/recordContextTool.js +85 -0
- package/dist/extension/recordDecisionTool.d.ts +52 -0
- package/dist/extension/recordDecisionTool.js +102 -0
- package/dist/extension/repoDocs.d.ts +81 -0
- package/dist/extension/repoDocs.js +260 -0
- package/dist/extension/rerouteNotice.d.ts +34 -0
- package/dist/extension/rerouteNotice.js +79 -0
- package/dist/extension/resilientFetch.d.ts +60 -0
- package/dist/extension/resilientFetch.js +133 -0
- package/dist/extension/reviewTool.d.ts +34 -0
- package/dist/extension/reviewTool.js +81 -0
- package/dist/extension/sessionRuns.d.ts +45 -0
- package/dist/extension/sessionRuns.js +77 -0
- package/dist/extension/spool.d.ts +92 -0
- package/dist/extension/spool.js +266 -0
- package/dist/extension/stateHome.d.ts +2 -0
- package/dist/extension/stateHome.js +6 -0
- package/dist/extension/subagents.d.ts +145 -0
- package/dist/extension/subagents.js +326 -0
- package/dist/extension/surface.d.ts +10 -0
- package/dist/extension/surface.js +12 -0
- package/dist/extension/todos.d.ts +110 -0
- package/dist/extension/todos.js +217 -0
- package/dist/extension/tokenProvider.d.ts +93 -0
- package/dist/extension/tokenProvider.js +234 -0
- package/dist/launch.d.ts +25 -3
- package/dist/launch.js +27 -9
- package/dist/login.d.ts +7 -0
- package/dist/login.js +3 -1
- package/dist/paths.d.ts +13 -4
- package/dist/paths.js +17 -5
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +5 -2
- package/dist/upgrade.d.ts +97 -0
- package/dist/upgrade.js +284 -0
- package/package.json +11 -15
package/README.md
CHANGED
|
@@ -39,8 +39,9 @@ flag) — ask your workspace admin. Maintainers: the alpha checklist is
|
|
|
39
39
|
## Develop
|
|
40
40
|
|
|
41
41
|
Working in this monorepo, install from the repo instead of the registry. Build
|
|
42
|
-
the extension first — the CLI
|
|
43
|
-
|
|
42
|
+
the extension first — the CLI's build copies `pi-extension-yagni`'s `dist/` into
|
|
43
|
+
its own `dist/extension/`, and fails loudly if the extension is not built — then
|
|
44
|
+
build and link the CLI:
|
|
44
45
|
|
|
45
46
|
```bash
|
|
46
47
|
pnpm --filter pi-extension-yagni build
|
|
@@ -65,6 +66,10 @@ Notes:
|
|
|
65
66
|
|
|
66
67
|
- **Order matters when changing both.** The CLI bundles the extension, so build
|
|
67
68
|
(or have a watcher running) `pi-extension-yagni` before `@yagni-app/code`.
|
|
69
|
+
- **Watch mode does not re-bundle.** The copy into `dist/extension/` happens on
|
|
70
|
+
`build`, not on `tsc --watch`, and the launcher prefers that copy. After an
|
|
71
|
+
extension change, run `pnpm --filter @yagni-app/code bundle` (cheap, copy
|
|
72
|
+
only) or a full `build`, or you will keep running the previous extension.
|
|
68
73
|
- **`yagni` is long-lived.** A rebuild only takes effect the next time you
|
|
69
74
|
launch it — restart the CLI to pick up new code.
|
|
70
75
|
- **Frontend/backend changes** (e.g. the `/v1` dev proxy) are served by
|
|
@@ -83,12 +88,16 @@ yagni -p "explain the deploy pipeline; ask_yagni if unsure" # one-shot / print
|
|
|
83
88
|
yagni logout # revoke the token and clear it locally
|
|
84
89
|
```
|
|
85
90
|
|
|
86
|
-
The agent runs on
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
The agent runs on five opaque model tiers routed by YAGNI: `balanced` (the
|
|
92
|
+
interactive default), `peak` (the strongest, for hard judgment), `advanced`,
|
|
93
|
+
`standard` (for execution) and `efficient` (cheapest). `balanced` is a strategy
|
|
94
|
+
rather than a single rung: the session drives on the `advanced` tier and
|
|
95
|
+
escalates a bounded number of hard judgment calls per session to `peak`, so the
|
|
96
|
+
strongest model is consulted where being wrong is expensive instead of being
|
|
97
|
+
paid for on every turn. Pass `--model peak`, `--model standard`, or `--model
|
|
98
|
+
efficient` to pin a run to one tier. The `/go` pipeline routes its own steps
|
|
99
|
+
automatically: judgment steps (plan, review) on `peak`, execution steps (map,
|
|
100
|
+
implement, fix) on `standard`.
|
|
92
101
|
|
|
93
102
|
`login` opens a device-code flow: it prints a short code and a URL. Open the URL
|
|
94
103
|
in a browser where you are signed in to YAGNI, enter the code, and approve. The
|
|
@@ -107,6 +116,22 @@ flags like `--model` and `--print/-p` work.
|
|
|
107
116
|
agent calls **`ask_yagni`** and gets a cited answer from the YAGNI app instead
|
|
108
117
|
of guessing.
|
|
109
118
|
|
|
119
|
+
### Works with your existing repo setup
|
|
120
|
+
|
|
121
|
+
- A repo that already carries Claude Code assets keeps working with nothing to
|
|
122
|
+
edit: `.claude/skills` and `.claude/commands/*.md` load automatically (each
|
|
123
|
+
command becomes a `/name` slash command). Project-local assets load only
|
|
124
|
+
after a one-time per-folder trust prompt; set `YAGNI_DISABLE_CLAUDE_COMPAT=1`
|
|
125
|
+
to turn the bridge off.
|
|
126
|
+
- `AGENTS.md` and `CLAUDE.md` context files load from the working directory and
|
|
127
|
+
its ancestors, unmodified.
|
|
128
|
+
- MCP servers registered in your YAGNI workspace are available in-session: run
|
|
129
|
+
`/mcp` to list them. Calls are proxied through the YAGNI backend with your
|
|
130
|
+
own identity, so server credentials never reach the laptop and every call is
|
|
131
|
+
audited; mutating tools go through the same permission gate as file edits.
|
|
132
|
+
There is no local MCP config file: servers and credentials are managed in the
|
|
133
|
+
web app (Connections).
|
|
134
|
+
|
|
110
135
|
## Environments (profiles)
|
|
111
136
|
|
|
112
137
|
YAGNI Code keeps a **sticky active environment**. Each environment ("profile") binds a
|
|
@@ -134,14 +159,43 @@ yagni use prod # switch back (sticky); prod is the def
|
|
|
134
159
|
|
|
135
160
|
## Configuration
|
|
136
161
|
|
|
137
|
-
| Variable
|
|
138
|
-
|
|
|
139
|
-
| `YAGNI_BASE_URL`
|
|
162
|
+
| Variable | Default | Purpose |
|
|
163
|
+
| ----------------------------- | ------------------------ | -------------------------------------------------- |
|
|
164
|
+
| `YAGNI_BASE_URL` | active environment's URL | Override the base URL for a **single run** (escape hatch). Prefer `yagni use` for anything sticky. |
|
|
165
|
+
| `YAGNI_DISABLE_BRANDING` | unset | `1` skips the YAGNI Code system-prompt rewrite entirely, so the engine's assembled prompt passes through byte-exact (no identity swap, no company-brief injection). |
|
|
166
|
+
| `YAGNI_DISABLE_UPDATE_CHECK` | unset | `1` silences the new-version notice and the background update check. |
|
|
167
|
+
| `YAGNI_DISABLE_CLAUDE_COMPAT` | unset | `1` turns off the zero-config `.claude` assets bridge (skills and commands). |
|
|
168
|
+
| `YAGNI_DISABLE_CRASH_REPORTS` | unset | `1` disables crash reporting (see "What leaves your machine"). |
|
|
140
169
|
|
|
141
170
|
Credentials live in `~/.yagni-code/profiles/<name>.json` (mode `0600`); the active
|
|
142
171
|
environment is recorded in `~/.yagni-code/config.json`. A pre-profiles
|
|
143
172
|
`~/.yagni-code/credentials.json` is migrated automatically on first run.
|
|
144
173
|
|
|
174
|
+
Device tokens are revocable from both ends: `yagni logout` revokes the current
|
|
175
|
+
one, and a workspace admin can list every connected device and revoke any token
|
|
176
|
+
from the web app (Settings, YAGNI Code, Connected devices). Tokens are stored
|
|
177
|
+
hashed on the server and a revoked token stops validating immediately.
|
|
178
|
+
|
|
179
|
+
### What leaves your machine
|
|
180
|
+
|
|
181
|
+
Everything the agent needs (model calls, grounding lookups, MCP calls, usage
|
|
182
|
+
metering) goes to your YAGNI backend over the authenticated session. No
|
|
183
|
+
third-party analytics or telemetry SDK runs on your laptop, and the underlying
|
|
184
|
+
engine's own telemetry and version pings are disabled at launch. Two outbound
|
|
185
|
+
flows exist beyond your YAGNI backend and your own repo tooling, disclosed
|
|
186
|
+
precisely:
|
|
187
|
+
|
|
188
|
+
- **Update check.** In the background after launch, the CLI asks the npm
|
|
189
|
+
registry (or your configured private registry mirror) whether a newer
|
|
190
|
+
version of this package exists, so the next launch can show a one-line
|
|
191
|
+
nudge. Package metadata only; no code or workspace data. Disable with
|
|
192
|
+
`YAGNI_DISABLE_UPDATE_CHECK=1`.
|
|
193
|
+
- **Crash reports.** When the client itself crashes, a sanitized crash report
|
|
194
|
+
(error class, scrubbed message and stack trace; secrets and env values
|
|
195
|
+
redacted, paths reduced) is sent to your YAGNI backend, not to any
|
|
196
|
+
third-party crash service, so we can fix the crash before you have to report
|
|
197
|
+
it. Disable with `YAGNI_DISABLE_CRASH_REPORTS=1`.
|
|
198
|
+
|
|
145
199
|
## Troubleshooting
|
|
146
200
|
|
|
147
201
|
- **`Not logged in to environment "<name>" … Run \`yagni login\` first.`** — no
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config Claude Code repo compat.
|
|
3
|
+
*
|
|
4
|
+
* A repo that already carries Claude Code assets keeps working under YAGNI
|
|
5
|
+
* Code with nothing to edit: `.claude/skills` loads through pi's skill
|
|
6
|
+
* discovery and `.claude/commands/*.md` through pi's prompt templates (the
|
|
7
|
+
* same markdown-with-frontmatter format Claude Code uses — filename becomes
|
|
8
|
+
* the `/name` command). The launcher passes both as per-run `--skill` /
|
|
9
|
+
* `--prompt-template` flags, so nothing repo-local is ever written and no
|
|
10
|
+
* per-profile settings file accumulates stale per-repo paths.
|
|
11
|
+
*
|
|
12
|
+
* The same seams carry installed/enabled Claude Code plugins and the repo's
|
|
13
|
+
* own `.claude-plugin/marketplace.json` plugins (see claudePlugins.ts):
|
|
14
|
+
* plugin skills → `--skill`, plugin commands → `--prompt-template`, plugin
|
|
15
|
+
* `agents/` dirs → YAGNI_CLAUDE_AGENT_DIRS for the extension's subagent
|
|
16
|
+
* discovery. `.claude/rules` dirs travel as YAGNI_CLAUDE_RULES_DIRS for the
|
|
17
|
+
* extension's system-prompt rules injection. Design + on-disk evidence:
|
|
18
|
+
* docs/superpowers/specs/2026-08-08-claude-plugins-design.md.
|
|
19
|
+
*
|
|
20
|
+
* Trust: pi gates project-local resources behind a per-folder trust decision
|
|
21
|
+
* stored in `<agentDir>/trust.json`. Explicit CLI paths bypass that gate, so
|
|
22
|
+
* the launcher enforces the SAME decision itself: project `.claude` dirs are
|
|
23
|
+
* wired only when the folder is trusted. With no recorded decision it asks
|
|
24
|
+
* once (interactive runs only) and persists the answer into pi's own
|
|
25
|
+
* trust.json — one trust model, shared with pi's prompt, and a "no" is
|
|
26
|
+
* remembered too. User-scope `~/.claude` dirs are the user's own machine and
|
|
27
|
+
* load without ceremony, matching how pi treats `~/.agents/skills`.
|
|
28
|
+
*
|
|
29
|
+
* Set YAGNI_DISABLE_CLAUDE_COMPAT=1 to turn the whole bridge off.
|
|
30
|
+
*/
|
|
31
|
+
import { type PluginAssets } from "./claudePlugins.js";
|
|
32
|
+
export declare const CLAUDE_COMPAT_DISABLE_ENV = "YAGNI_DISABLE_CLAUDE_COMPAT";
|
|
33
|
+
/** Plugin `agents/` dirs for the extension's subagent discovery (delimiter-joined). */
|
|
34
|
+
export declare const CLAUDE_AGENT_DIRS_ENV = "YAGNI_CLAUDE_AGENT_DIRS";
|
|
35
|
+
/** `.claude/rules` dirs for the extension's rules injection (delimiter-joined). */
|
|
36
|
+
export declare const CLAUDE_RULES_DIRS_ENV = "YAGNI_CLAUDE_RULES_DIRS";
|
|
37
|
+
export interface ClaudeAssetDirs {
|
|
38
|
+
skills: string | null;
|
|
39
|
+
commands: string | null;
|
|
40
|
+
rules: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface ClaudeCompatProbe {
|
|
43
|
+
project: ClaudeAssetDirs;
|
|
44
|
+
user: ClaudeAssetDirs;
|
|
45
|
+
/** Locally-present plugins sourced from repo config; trust-gated. */
|
|
46
|
+
projectPlugins: PluginAssets[];
|
|
47
|
+
/** Plugins the user enabled in their own `~/.claude`; no ceremony. */
|
|
48
|
+
userPlugins: PluginAssets[];
|
|
49
|
+
/** Nearest recorded trust decision for the cwd (pi trust.json), if any. */
|
|
50
|
+
projectTrust: boolean | null;
|
|
51
|
+
interactive: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface ClaudeCompatDecision {
|
|
54
|
+
/** Flags for user-scope dirs; always safe to pass. */
|
|
55
|
+
userArgv: string[];
|
|
56
|
+
/** Flags for project dirs; pass only once trust is established. */
|
|
57
|
+
projectArgv: string[];
|
|
58
|
+
/** Plugin agent dirs, split by the same trust posture. */
|
|
59
|
+
userAgentDirs: string[];
|
|
60
|
+
projectAgentDirs: string[];
|
|
61
|
+
/** `.claude/rules` dirs, user first so project rules win in the prompt. */
|
|
62
|
+
userRulesDirs: string[];
|
|
63
|
+
projectRulesDirs: string[];
|
|
64
|
+
/** Ask the user for a trust decision before wiring any project content. */
|
|
65
|
+
needsPrompt: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Pure policy: what to wire, and whether a trust prompt is required first. */
|
|
68
|
+
export declare function decideClaudeCompat(probe: ClaudeCompatProbe): ClaudeCompatDecision;
|
|
69
|
+
/** The `.claude` asset dirs under `root` that actually exist. */
|
|
70
|
+
export declare function probeClaudeDirs(root: string): ClaudeAssetDirs;
|
|
71
|
+
/**
|
|
72
|
+
* The nearest recorded decision for `cwd` or an ancestor, else null. Read-only
|
|
73
|
+
* and fail-soft: a missing or corrupt trust file just means "not decided".
|
|
74
|
+
*/
|
|
75
|
+
export declare function readTrustDecision(agentDir: string, cwd: string): boolean | null;
|
|
76
|
+
/**
|
|
77
|
+
* Persist a decision into pi's trust.json, holding pi's mkdir-based lock
|
|
78
|
+
* (`trust.json.lock`) so a concurrently-running session can't lose a write.
|
|
79
|
+
* Fail-soft: if the lock never frees, skip the write — worst case the user is
|
|
80
|
+
* asked again next launch.
|
|
81
|
+
*/
|
|
82
|
+
export declare function writeTrustDecision(agentDir: string, cwd: string, decision: boolean): void;
|
|
83
|
+
export interface ClaudeCompatDeps {
|
|
84
|
+
cwd: string;
|
|
85
|
+
/** The hermetic pi agent dir for the active profile (holds trust.json). */
|
|
86
|
+
agentDir: string;
|
|
87
|
+
homeDir?: string;
|
|
88
|
+
env?: NodeJS.ProcessEnv;
|
|
89
|
+
interactive?: boolean;
|
|
90
|
+
/** The one-time trust question; injectable for tests. */
|
|
91
|
+
confirm?: (question: string) => Promise<boolean>;
|
|
92
|
+
}
|
|
93
|
+
export declare function compatDisabled(env: NodeJS.ProcessEnv): boolean;
|
|
94
|
+
export interface ClaudeCompatLaunch {
|
|
95
|
+
/** Extra pi flags (`--skill` / `--prompt-template` paths). */
|
|
96
|
+
argv: string[];
|
|
97
|
+
/** Extra child env (plugin agent dirs, rules dirs) for the extension. */
|
|
98
|
+
env: Record<string, string>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The extra pi argv + child env for this launch: user-scope dirs and plugins
|
|
102
|
+
* unconditionally, project-sourced dirs/plugins/rules once trusted, asking
|
|
103
|
+
* (and persisting) the trust decision when needed. Everything is fail-soft —
|
|
104
|
+
* compat can only add flags/env, never break a launch.
|
|
105
|
+
*/
|
|
106
|
+
export declare function claudeCompatArgs(deps: ClaudeCompatDeps): Promise<ClaudeCompatLaunch>;
|
|
107
|
+
/** Whether pi's trust file exists yet (used only for messaging). */
|
|
108
|
+
export declare function trustFileExists(agentDir: string): boolean;
|
|
109
|
+
//# sourceMappingURL=claudeCompat.d.ts.map
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config Claude Code repo compat.
|
|
3
|
+
*
|
|
4
|
+
* A repo that already carries Claude Code assets keeps working under YAGNI
|
|
5
|
+
* Code with nothing to edit: `.claude/skills` loads through pi's skill
|
|
6
|
+
* discovery and `.claude/commands/*.md` through pi's prompt templates (the
|
|
7
|
+
* same markdown-with-frontmatter format Claude Code uses — filename becomes
|
|
8
|
+
* the `/name` command). The launcher passes both as per-run `--skill` /
|
|
9
|
+
* `--prompt-template` flags, so nothing repo-local is ever written and no
|
|
10
|
+
* per-profile settings file accumulates stale per-repo paths.
|
|
11
|
+
*
|
|
12
|
+
* The same seams carry installed/enabled Claude Code plugins and the repo's
|
|
13
|
+
* own `.claude-plugin/marketplace.json` plugins (see claudePlugins.ts):
|
|
14
|
+
* plugin skills → `--skill`, plugin commands → `--prompt-template`, plugin
|
|
15
|
+
* `agents/` dirs → YAGNI_CLAUDE_AGENT_DIRS for the extension's subagent
|
|
16
|
+
* discovery. `.claude/rules` dirs travel as YAGNI_CLAUDE_RULES_DIRS for the
|
|
17
|
+
* extension's system-prompt rules injection. Design + on-disk evidence:
|
|
18
|
+
* docs/superpowers/specs/2026-08-08-claude-plugins-design.md.
|
|
19
|
+
*
|
|
20
|
+
* Trust: pi gates project-local resources behind a per-folder trust decision
|
|
21
|
+
* stored in `<agentDir>/trust.json`. Explicit CLI paths bypass that gate, so
|
|
22
|
+
* the launcher enforces the SAME decision itself: project `.claude` dirs are
|
|
23
|
+
* wired only when the folder is trusted. With no recorded decision it asks
|
|
24
|
+
* once (interactive runs only) and persists the answer into pi's own
|
|
25
|
+
* trust.json — one trust model, shared with pi's prompt, and a "no" is
|
|
26
|
+
* remembered too. User-scope `~/.claude` dirs are the user's own machine and
|
|
27
|
+
* load without ceremony, matching how pi treats `~/.agents/skills`.
|
|
28
|
+
*
|
|
29
|
+
* Set YAGNI_DISABLE_CLAUDE_COMPAT=1 to turn the whole bridge off.
|
|
30
|
+
*/
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmdirSync, statSync, writeFileSync } from "node:fs";
|
|
32
|
+
import { homedir } from "node:os";
|
|
33
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
34
|
+
import { discoverClaudePlugins } from "./claudePlugins.js";
|
|
35
|
+
export const CLAUDE_COMPAT_DISABLE_ENV = "YAGNI_DISABLE_CLAUDE_COMPAT";
|
|
36
|
+
/** Plugin `agents/` dirs for the extension's subagent discovery (delimiter-joined). */
|
|
37
|
+
export const CLAUDE_AGENT_DIRS_ENV = "YAGNI_CLAUDE_AGENT_DIRS";
|
|
38
|
+
/** `.claude/rules` dirs for the extension's rules injection (delimiter-joined). */
|
|
39
|
+
export const CLAUDE_RULES_DIRS_ENV = "YAGNI_CLAUDE_RULES_DIRS";
|
|
40
|
+
function dirsToArgv(dirs) {
|
|
41
|
+
const argv = [];
|
|
42
|
+
if (dirs.skills)
|
|
43
|
+
argv.push("--skill", dirs.skills);
|
|
44
|
+
if (dirs.commands)
|
|
45
|
+
argv.push("--prompt-template", dirs.commands);
|
|
46
|
+
return argv;
|
|
47
|
+
}
|
|
48
|
+
function pluginsToArgv(plugins) {
|
|
49
|
+
const argv = [];
|
|
50
|
+
for (const plugin of plugins) {
|
|
51
|
+
for (const path of plugin.skillPaths)
|
|
52
|
+
argv.push("--skill", path);
|
|
53
|
+
for (const path of plugin.commandPaths)
|
|
54
|
+
argv.push("--prompt-template", path);
|
|
55
|
+
}
|
|
56
|
+
return argv;
|
|
57
|
+
}
|
|
58
|
+
/** Pure policy: what to wire, and whether a trust prompt is required first. */
|
|
59
|
+
export function decideClaudeCompat(probe) {
|
|
60
|
+
const projectArgv = [...dirsToArgv(probe.project), ...pluginsToArgv(probe.projectPlugins)];
|
|
61
|
+
const projectAgentDirs = probe.projectPlugins.flatMap((p) => p.agentDirs);
|
|
62
|
+
const projectRulesDirs = probe.project.rules ? [probe.project.rules] : [];
|
|
63
|
+
const hasProjectContent = projectArgv.length > 0 || projectAgentDirs.length > 0 || projectRulesDirs.length > 0;
|
|
64
|
+
return {
|
|
65
|
+
userArgv: [...dirsToArgv(probe.user), ...pluginsToArgv(probe.userPlugins)],
|
|
66
|
+
projectArgv,
|
|
67
|
+
userAgentDirs: probe.userPlugins.flatMap((p) => p.agentDirs),
|
|
68
|
+
projectAgentDirs,
|
|
69
|
+
userRulesDirs: probe.user.rules ? [probe.user.rules] : [],
|
|
70
|
+
projectRulesDirs,
|
|
71
|
+
needsPrompt: probe.interactive && hasProjectContent && probe.projectTrust === null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function isDirectory(path) {
|
|
75
|
+
try {
|
|
76
|
+
return statSync(path).isDirectory();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** The `.claude` asset dirs under `root` that actually exist. */
|
|
83
|
+
export function probeClaudeDirs(root) {
|
|
84
|
+
const skills = join(root, ".claude", "skills");
|
|
85
|
+
const commands = join(root, ".claude", "commands");
|
|
86
|
+
const rules = join(root, ".claude", "rules");
|
|
87
|
+
return {
|
|
88
|
+
skills: isDirectory(skills) ? skills : null,
|
|
89
|
+
commands: isDirectory(commands) ? commands : null,
|
|
90
|
+
rules: isDirectory(rules) ? rules : null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// ── pi trust.json interop ───────────────────────────────────────────────────
|
|
94
|
+
/** Match pi's normalization closely enough for key lookups: resolve + realpath. */
|
|
95
|
+
function normalizeCwd(path) {
|
|
96
|
+
const resolved = resolve(path);
|
|
97
|
+
try {
|
|
98
|
+
return realpathSync(resolved);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function trustPath(agentDir) {
|
|
105
|
+
return join(agentDir, "trust.json");
|
|
106
|
+
}
|
|
107
|
+
function readTrustFile(path) {
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
110
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
111
|
+
return {};
|
|
112
|
+
const data = {};
|
|
113
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
114
|
+
if (value === true || value === false || value === null)
|
|
115
|
+
data[key] = value;
|
|
116
|
+
}
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The nearest recorded decision for `cwd` or an ancestor, else null. Read-only
|
|
125
|
+
* and fail-soft: a missing or corrupt trust file just means "not decided".
|
|
126
|
+
*/
|
|
127
|
+
export function readTrustDecision(agentDir, cwd) {
|
|
128
|
+
const data = readTrustFile(trustPath(agentDir));
|
|
129
|
+
let current = normalizeCwd(cwd);
|
|
130
|
+
while (true) {
|
|
131
|
+
const decision = data[current];
|
|
132
|
+
if (decision === true || decision === false)
|
|
133
|
+
return decision;
|
|
134
|
+
const parent = dirname(current);
|
|
135
|
+
if (parent === current)
|
|
136
|
+
return null;
|
|
137
|
+
current = parent;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Persist a decision into pi's trust.json, holding pi's mkdir-based lock
|
|
142
|
+
* (`trust.json.lock`) so a concurrently-running session can't lose a write.
|
|
143
|
+
* Fail-soft: if the lock never frees, skip the write — worst case the user is
|
|
144
|
+
* asked again next launch.
|
|
145
|
+
*/
|
|
146
|
+
export function writeTrustDecision(agentDir, cwd, decision) {
|
|
147
|
+
const path = trustPath(agentDir);
|
|
148
|
+
const lock = `${path}.lock`;
|
|
149
|
+
try {
|
|
150
|
+
mkdirSync(agentDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Fail-soft: an uncreatable agent dir must not discard the user's answer
|
|
154
|
+
// for this launch — the caller still applies it in-process.
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let locked = false;
|
|
158
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
159
|
+
try {
|
|
160
|
+
mkdirSync(lock);
|
|
161
|
+
locked = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
const start = Date.now();
|
|
166
|
+
while (Date.now() - start < 20) {
|
|
167
|
+
// Brief synchronous wait between lock attempts.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!locked)
|
|
172
|
+
return;
|
|
173
|
+
try {
|
|
174
|
+
const data = readTrustFile(path);
|
|
175
|
+
data[normalizeCwd(cwd)] = decision;
|
|
176
|
+
const sorted = {};
|
|
177
|
+
for (const key of Object.keys(data).sort())
|
|
178
|
+
sorted[key] = data[key];
|
|
179
|
+
const tmp = `${path}.tmp`;
|
|
180
|
+
writeFileSync(tmp, `${JSON.stringify(sorted, null, 2)}\n`, "utf8");
|
|
181
|
+
renameSync(tmp, path);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Fail-soft: an unwritable trust file only costs a repeat prompt.
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
try {
|
|
188
|
+
rmdirSync(lock);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Already gone.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export function compatDisabled(env) {
|
|
196
|
+
const value = env[CLAUDE_COMPAT_DISABLE_ENV];
|
|
197
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The extra pi argv + child env for this launch: user-scope dirs and plugins
|
|
201
|
+
* unconditionally, project-sourced dirs/plugins/rules once trusted, asking
|
|
202
|
+
* (and persisting) the trust decision when needed. Everything is fail-soft —
|
|
203
|
+
* compat can only add flags/env, never break a launch.
|
|
204
|
+
*/
|
|
205
|
+
export async function claudeCompatArgs(deps) {
|
|
206
|
+
const env = deps.env ?? process.env;
|
|
207
|
+
if (compatDisabled(env))
|
|
208
|
+
return { argv: [], env: {} };
|
|
209
|
+
const home = deps.homeDir ?? homedir();
|
|
210
|
+
const projectTrust = readTrustDecision(deps.agentDir, deps.cwd);
|
|
211
|
+
let plugins;
|
|
212
|
+
try {
|
|
213
|
+
plugins = discoverClaudePlugins({ cwd: deps.cwd, homeDir: home });
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
plugins = { user: [], project: [] };
|
|
217
|
+
}
|
|
218
|
+
const decision = decideClaudeCompat({
|
|
219
|
+
project: probeClaudeDirs(deps.cwd),
|
|
220
|
+
user: probeClaudeDirs(home),
|
|
221
|
+
projectPlugins: plugins.project,
|
|
222
|
+
userPlugins: plugins.user,
|
|
223
|
+
projectTrust,
|
|
224
|
+
interactive: deps.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
|
|
225
|
+
});
|
|
226
|
+
let trusted = projectTrust;
|
|
227
|
+
if (decision.needsPrompt && deps.confirm) {
|
|
228
|
+
try {
|
|
229
|
+
trusted = await deps.confirm("This folder has Claude Code assets (.claude skills, commands, rules, or plugins). Trust this folder and load them?");
|
|
230
|
+
writeTrustDecision(deps.agentDir, deps.cwd, trusted);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
trusted = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const argv = [...decision.userArgv];
|
|
237
|
+
if (trusted === true)
|
|
238
|
+
argv.push(...decision.projectArgv);
|
|
239
|
+
const agentDirs = [
|
|
240
|
+
...decision.userAgentDirs,
|
|
241
|
+
...(trusted === true ? decision.projectAgentDirs : []),
|
|
242
|
+
];
|
|
243
|
+
// User rules first, project rules last: the extension appends in order, so
|
|
244
|
+
// project rules end up closest to the task — matching Claude Code priority.
|
|
245
|
+
const rulesDirs = [
|
|
246
|
+
...decision.userRulesDirs,
|
|
247
|
+
...(trusted === true ? decision.projectRulesDirs : []),
|
|
248
|
+
];
|
|
249
|
+
const extraEnv = {};
|
|
250
|
+
if (agentDirs.length > 0)
|
|
251
|
+
extraEnv[CLAUDE_AGENT_DIRS_ENV] = agentDirs.join(delimiter);
|
|
252
|
+
if (rulesDirs.length > 0)
|
|
253
|
+
extraEnv[CLAUDE_RULES_DIRS_ENV] = rulesDirs.join(delimiter);
|
|
254
|
+
return { argv, env: extraEnv };
|
|
255
|
+
}
|
|
256
|
+
/** Whether pi's trust file exists yet (used only for messaging). */
|
|
257
|
+
export function trustFileExists(agentDir) {
|
|
258
|
+
return existsSync(trustPath(agentDir));
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=claudeCompat.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code plugin + marketplace discovery (local content only).
|
|
3
|
+
*
|
|
4
|
+
* Finds the plugin content a Claude Code user already has on disk and exposes
|
|
5
|
+
* it as asset paths the launcher can feed through the existing compat seams:
|
|
6
|
+
* skills → `--skill`, commands → `--prompt-template`, agents → the subagent
|
|
7
|
+
* discovery env var. Two sources:
|
|
8
|
+
*
|
|
9
|
+
* 1. Installed plugins: `~/.claude/plugins/installed_plugins.json` (version-2
|
|
10
|
+
* ledger of `name@marketplace` → cache checkouts), enabled through
|
|
11
|
+
* `enabledPlugins` in the user's `~/.claude/settings.json` (user scope) or
|
|
12
|
+
* the repo's `.claude/settings{,.local}.json` (project scope). When a
|
|
13
|
+
* ledger entry is gone, the marketplace checkout recorded in
|
|
14
|
+
* `known_marketplaces.json` is tried as a fallback.
|
|
15
|
+
* 2. The repo's own `.claude-plugin/marketplace.json` (the internal-
|
|
16
|
+
* marketplace pattern): every plugin it defines with a locally-present
|
|
17
|
+
* relative-path source loads on the project side, unless explicitly
|
|
18
|
+
* disabled via `enabledPlugins`.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NOT here: network installation of any kind, plugin hooks, MCP
|
|
21
|
+
* servers, LSP servers, themes, output styles. Discovery is read-only.
|
|
22
|
+
*
|
|
23
|
+
* Everything is fail-soft: malformed JSON, missing dirs, or hostile path
|
|
24
|
+
* entries degrade to "that plugin absent" — never a failed launch. Path
|
|
25
|
+
* entries from marketplace/plugin manifests are containment-checked (realpath
|
|
26
|
+
* inside the expected root) so a malicious `source` or component override
|
|
27
|
+
* cannot reach outside its repo/plugin.
|
|
28
|
+
*/
|
|
29
|
+
export interface PluginAssets {
|
|
30
|
+
/** The plugin's name (marketplace-entry name — the `enabledPlugins` key half). */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Absolute plugin root directory. */
|
|
33
|
+
root: string;
|
|
34
|
+
/** Dirs (or a lone root SKILL.md file) for pi `--skill`. */
|
|
35
|
+
skillPaths: string[];
|
|
36
|
+
/** Dirs or files for pi `--prompt-template`. */
|
|
37
|
+
commandPaths: string[];
|
|
38
|
+
/** Dirs of Claude Code-format agent markdown for subagent discovery. */
|
|
39
|
+
agentDirs: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface DiscoveredClaudePlugins {
|
|
42
|
+
/** Enabled via the user's own `~/.claude` config; loads without ceremony. */
|
|
43
|
+
user: PluginAssets[];
|
|
44
|
+
/** Sourced from repo config; wire only once the folder is trusted. */
|
|
45
|
+
project: PluginAssets[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve `candidate` (a relative path from untrusted JSON) against `root` and
|
|
49
|
+
* return its realpath only when it exists AND stays inside `root` after
|
|
50
|
+
* symlink resolution. Absolute candidates and any `../` escape are rejected.
|
|
51
|
+
*/
|
|
52
|
+
export declare function containedExistingPath(root: string, candidate: string): string | null;
|
|
53
|
+
export interface InstalledPluginEntry {
|
|
54
|
+
installPath: string;
|
|
55
|
+
projectPath?: string;
|
|
56
|
+
}
|
|
57
|
+
/** `~/.claude/plugins/installed_plugins.json` → `name@marketplace` → entries. */
|
|
58
|
+
export declare function readInstalledPlugins(homeDir: string): Map<string, InstalledPluginEntry[]>;
|
|
59
|
+
export type EnableScope = "user" | "project";
|
|
60
|
+
export interface EnableState {
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
scope: EnableScope;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `enabledPlugins` merged across scopes, later (more specific) winning:
|
|
66
|
+
* user `~/.claude/settings.json` < project `.claude/settings.json` <
|
|
67
|
+
* project `.claude/settings.local.json`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readEnabledPlugins(cwd: string, homeDir: string): Map<string, EnableState>;
|
|
70
|
+
/** `~/.claude/plugins/known_marketplaces.json` → marketplace name → checkout dir. */
|
|
71
|
+
export declare function readKnownMarketplaces(homeDir: string): Map<string, string>;
|
|
72
|
+
export interface MarketplaceEntry {
|
|
73
|
+
name: string;
|
|
74
|
+
source: unknown;
|
|
75
|
+
}
|
|
76
|
+
export interface Marketplace {
|
|
77
|
+
name: string;
|
|
78
|
+
/** Marketplace root: the dir containing `.claude-plugin/`. */
|
|
79
|
+
root: string;
|
|
80
|
+
/** `metadata.pluginRoot`, prepended to bare relative sources. */
|
|
81
|
+
pluginRoot: string | null;
|
|
82
|
+
plugins: MarketplaceEntry[];
|
|
83
|
+
}
|
|
84
|
+
/** Parse `<root>/.claude-plugin/marketplace.json`; null when absent/unusable. */
|
|
85
|
+
export declare function readMarketplace(root: string): Marketplace | null;
|
|
86
|
+
/**
|
|
87
|
+
* The local directory a marketplace entry's source points at, when it is a
|
|
88
|
+
* relative-path source that exists inside the marketplace root. Object
|
|
89
|
+
* sources (github/url/npm/...) are network installs → null here.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveLocalPluginRoot(mp: Marketplace, entry: MarketplaceEntry): string | null;
|
|
92
|
+
/**
|
|
93
|
+
* The bridgeable assets inside one plugin checkout, per Claude Code's
|
|
94
|
+
* component rules: `skills/` (manifest `skills` ADDS dirs), `commands/`
|
|
95
|
+
* (manifest REPLACES), `agents/` (manifest REPLACES; dirs only — pi's
|
|
96
|
+
* discovery reads whole dirs), root `SKILL.md` fallback. Null when the plugin
|
|
97
|
+
* has nothing we can bridge.
|
|
98
|
+
*/
|
|
99
|
+
export declare function pluginAssets(root: string, name: string): PluginAssets | null;
|
|
100
|
+
export interface DiscoverPluginsDeps {
|
|
101
|
+
cwd: string;
|
|
102
|
+
homeDir: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* All locally-present Claude Code plugin content relevant to `cwd`, split by
|
|
106
|
+
* the trust posture the launcher must apply. Never throws.
|
|
107
|
+
*/
|
|
108
|
+
export declare function discoverClaudePlugins(deps: DiscoverPluginsDeps): DiscoveredClaudePlugins;
|
|
109
|
+
//# sourceMappingURL=claudePlugins.d.ts.map
|