@yagni-app/code 0.1.0 → 0.2.0
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 +42 -0
- package/dist/extension/rerouteNotice.js +67 -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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/boost` — the sanctioned session-scoped escalation to Peak (spec §7).
|
|
3
|
+
*
|
|
4
|
+
* The session default is the `balanced` tier. `/boost` flips the
|
|
5
|
+
* DRIVER session's live model to `peak` until `/boost off` or the process
|
|
6
|
+
* exits; `peak` stays directly pickable through pi's own model picker too,
|
|
7
|
+
* this just adds a one-word lever plus an attribution flag.
|
|
8
|
+
*
|
|
9
|
+
* Two halves, same split as `advisor.ts` / `permission.ts`:
|
|
10
|
+
*
|
|
11
|
+
* - `boostOn` / `boostOff` are PURE. They own every transition and the exact
|
|
12
|
+
* notice copy; no pi, no I/O, no env. That is what needs exhaustive tests.
|
|
13
|
+
* - `registerBoostCommand` is the wiring: it applies the pure result's
|
|
14
|
+
* `targetModelId` through pi's real model-switch API, toggles
|
|
15
|
+
* `process.env.YAGNI_BOOST`, and paints the notice + a status-bar chip.
|
|
16
|
+
*
|
|
17
|
+
* The model-switch API (verified against pi 0.83.0's typings, not guessed):
|
|
18
|
+
* `ctx.modelRegistry.find(provider, modelId)` resolves a tier id to a
|
|
19
|
+
* `Model`, and `pi.setModel(model)` (the top-level `ExtensionAPI` method, NOT
|
|
20
|
+
* `ctx.setModel` — `ExtensionCommandContext` does not expose a setter) applies
|
|
21
|
+
* it to the live session, resolving `false` when no API key is configured.
|
|
22
|
+
* The `yagni` provider's catalog entries key `id` on the tier id itself (see
|
|
23
|
+
* `provider.ts`), so `peak` is the pi model id for the peak tier.
|
|
24
|
+
*
|
|
25
|
+
* State lives in the registration closure — module/session-scoped, mirroring
|
|
26
|
+
* `costHud.ts`'s accumulator — because a session's process exit is the only
|
|
27
|
+
* "end" there is; nothing needs to persist across it. `registerBoostCommand`
|
|
28
|
+
* returns a small `{ isBoosted() }` handle onto that same closure so other
|
|
29
|
+
* registrations (currently just `/cost`, see below) can read the live flag
|
|
30
|
+
* without a second source of truth.
|
|
31
|
+
*
|
|
32
|
+
* Picker-drift guard (spec review item 3): pi's OWN model picker (Ctrl+P,
|
|
33
|
+
* `/model`) can change the live model independently of `/boost` in either
|
|
34
|
+
* direction, so `state.active` can go stale relative to `ctx.model.id`. Both
|
|
35
|
+
* halves read the live model and reconcile rather than trusting `state.active`
|
|
36
|
+
* blindly:
|
|
37
|
+
*
|
|
38
|
+
* - `boostOn` while already active but the live model has drifted OFF peak
|
|
39
|
+
* (the user cycled models mid-boost without `/boost off`): re-applies
|
|
40
|
+
* peak instead of a silent "Already boosted." that would leave the
|
|
41
|
+
* driver quietly running a cheaper tier under an attribution header that
|
|
42
|
+
* claims otherwise. The remembered `priorModelId` is left untouched — the
|
|
43
|
+
* tier to restore is still whatever was active before the ORIGINAL boost,
|
|
44
|
+
* not the tier the user happened to drift to.
|
|
45
|
+
* - `boostOff` while active but the live model is no longer peak (same
|
|
46
|
+
* drift, encountered from the other command): the user already left
|
|
47
|
+
* boost manually, so forcing a switch back to the remembered prior tier
|
|
48
|
+
* would clobber a choice they just made on purpose. Instead this clears
|
|
49
|
+
* `/boost`'s own bookkeeping (state, env, chip) without touching the
|
|
50
|
+
* model, and names where they actually landed.
|
|
51
|
+
*
|
|
52
|
+
* KNOWN asymmetry, documented rather than worked around: `YAGNI_BOOST=1`
|
|
53
|
+
* while boosted makes every CHILD process spawned during the boost (a /go
|
|
54
|
+
* run, a subagent, an advisor consult) inherit it, and those children's
|
|
55
|
+
* completions carry `x-yagni-boost` because their provider is registered
|
|
56
|
+
* fresh per child. The DRIVER's own completions do not gain that header
|
|
57
|
+
* retroactively — `buildYagniProvider`'s headers are baked into the
|
|
58
|
+
* `registerProvider` call once, at session start, from the env snapshot at
|
|
59
|
+
* that moment (see `provider.ts` / `attributionHeaders`). Re-registering the
|
|
60
|
+
* provider mid-session to pick up a new header is explicitly NOT the fix
|
|
61
|
+
* here: it would race the in-flight request the switch itself triggers and
|
|
62
|
+
* has no test coverage as a live-swap path.
|
|
63
|
+
*
|
|
64
|
+
* This is exactly why `/cost` cannot rely on server rows alone: the server's
|
|
65
|
+
* per-tier "Boosted (tier) spend" subtotal (`formatServerCostLines`, Task 7)
|
|
66
|
+
* only ever sees rows carrying `x-yagni-boost` — i.e. `/go` children, never
|
|
67
|
+
* the driver's own turns. A session that only chats while boosted would
|
|
68
|
+
* otherwise show no boost line at all. `registerCostCommand`'s `isBoosted`
|
|
69
|
+
* dep (threaded from THIS module's return handle, the same way
|
|
70
|
+
* `advisorSubtotal` is threaded from `advisor.ts`) closes that gap: `/cost`
|
|
71
|
+
* appends a fixed "Boost is on. Driver turns bill at the peak tier." line
|
|
72
|
+
* whenever the live toggle is on, on BOTH the server-authoritative and the
|
|
73
|
+
* local-fallback branch, independent of what server rows happen to show.
|
|
74
|
+
*/
|
|
75
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
76
|
+
/** Session-scoped boost state, held in the registration closure. */
|
|
77
|
+
export interface BoostState {
|
|
78
|
+
active: boolean;
|
|
79
|
+
/** The tier to restore on `/boost off`. Set only while `active`. */
|
|
80
|
+
priorModelId?: string;
|
|
81
|
+
}
|
|
82
|
+
/** The pure outcome of a `/boost` transition. */
|
|
83
|
+
export interface BoostResult {
|
|
84
|
+
state: BoostState;
|
|
85
|
+
/** Exact notice text to surface to the user. */
|
|
86
|
+
notice: string;
|
|
87
|
+
/** The tier id to switch the session to, or undefined for a pure no-op. */
|
|
88
|
+
targetModelId?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Turn boost on. PURE.
|
|
92
|
+
*
|
|
93
|
+
* - Already active AND the live model is still `peak`: genuine no-op,
|
|
94
|
+
* "Already boosted." (no switch — nothing about the prior tier changes).
|
|
95
|
+
* - Already active but the live model has drifted off `peak` (picker-drift
|
|
96
|
+
* guard 3b, see module docblock): re-applies `peak`, keeping the ORIGINAL
|
|
97
|
+
* `priorModelId` rather than adopting the drifted-to tier.
|
|
98
|
+
* - Not active, current model IS already `peak`: activates anyway (so
|
|
99
|
+
* attribution and `/boost off` behave correctly) with a distinct notice,
|
|
100
|
+
* remembering `peak` itself as the tier to restore.
|
|
101
|
+
* - Not active, otherwise: remembers the current tier, targets `peak`.
|
|
102
|
+
*/
|
|
103
|
+
export declare function boostOn(state: BoostState, currentModelId: string | undefined): BoostResult;
|
|
104
|
+
/**
|
|
105
|
+
* Turn boost off. PURE.
|
|
106
|
+
*
|
|
107
|
+
* - Not active: no-op, "Boost is not on."
|
|
108
|
+
* - Active but the live model is no longer `peak` (picker-drift guard 3a,
|
|
109
|
+
* see module docblock): the user already left boost manually. Clears
|
|
110
|
+
* `/boost`'s own bookkeeping WITHOUT a switch — forcing one back to the
|
|
111
|
+
* remembered prior tier would clobber a choice just made on purpose — and
|
|
112
|
+
* names the model they actually landed on.
|
|
113
|
+
* - Active and still on `peak`: restores the remembered prior tier (falling
|
|
114
|
+
* back to the session default when none was recorded, which should not
|
|
115
|
+
* normally happen). When the prior tier is itself `peak` (the
|
|
116
|
+
* already-on-Peak activation path), the resulting switch is a no-op in
|
|
117
|
+
* effect — still applied, harmlessly.
|
|
118
|
+
*/
|
|
119
|
+
export declare function boostOff(state: BoostState, currentModelId: string | undefined): BoostResult;
|
|
120
|
+
/** Injectable seams for `registerBoostCommand`. */
|
|
121
|
+
export interface RegisterBoostCommandDeps {
|
|
122
|
+
/** Environment `YAGNI_BOOST` is toggled on. Defaults to `process.env`. */
|
|
123
|
+
env?: NodeJS.ProcessEnv;
|
|
124
|
+
}
|
|
125
|
+
/** What `registerBoostCommand` hands back so other registrations (`/cost`) can read the live flag. */
|
|
126
|
+
export interface BoostCommandHandle {
|
|
127
|
+
/** Whether the session is currently boosted, read fresh off the same closure `/boost` mutates. */
|
|
128
|
+
isBoosted(): boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Register the `/boost` command.
|
|
132
|
+
*
|
|
133
|
+
* `/boost` (no args) turns boost on; `/boost off` turns it off; any other
|
|
134
|
+
* argument shows a usage notice and changes nothing. Every transition that
|
|
135
|
+
* requires a model switch runs it through pi's real API and is guarded end to
|
|
136
|
+
* end: state and `YAGNI_BOOST` are only ever committed together, AFTER the
|
|
137
|
+
* switch succeeds (or is deliberately skipped — the picker-drift guards), so
|
|
138
|
+
* a half-applied boost (env set but model unchanged, or vice versa) can never
|
|
139
|
+
* happen. A failed ON reports "Boost failed."; a failed OFF reports a
|
|
140
|
+
* DISTINCT message, because the failure mode is materially worse — the
|
|
141
|
+
* driver is left still running (and billing) on Peak, not merely unchanged.
|
|
142
|
+
*/
|
|
143
|
+
export declare function registerBoostCommand(pi: ExtensionAPI, deps?: RegisterBoostCommandDeps): BoostCommandHandle;
|
|
144
|
+
//# sourceMappingURL=boostCommand.d.ts.map
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/boost` — the sanctioned session-scoped escalation to Peak (spec §7).
|
|
3
|
+
*
|
|
4
|
+
* The session default is the `balanced` tier. `/boost` flips the
|
|
5
|
+
* DRIVER session's live model to `peak` until `/boost off` or the process
|
|
6
|
+
* exits; `peak` stays directly pickable through pi's own model picker too,
|
|
7
|
+
* this just adds a one-word lever plus an attribution flag.
|
|
8
|
+
*
|
|
9
|
+
* Two halves, same split as `advisor.ts` / `permission.ts`:
|
|
10
|
+
*
|
|
11
|
+
* - `boostOn` / `boostOff` are PURE. They own every transition and the exact
|
|
12
|
+
* notice copy; no pi, no I/O, no env. That is what needs exhaustive tests.
|
|
13
|
+
* - `registerBoostCommand` is the wiring: it applies the pure result's
|
|
14
|
+
* `targetModelId` through pi's real model-switch API, toggles
|
|
15
|
+
* `process.env.YAGNI_BOOST`, and paints the notice + a status-bar chip.
|
|
16
|
+
*
|
|
17
|
+
* The model-switch API (verified against pi 0.83.0's typings, not guessed):
|
|
18
|
+
* `ctx.modelRegistry.find(provider, modelId)` resolves a tier id to a
|
|
19
|
+
* `Model`, and `pi.setModel(model)` (the top-level `ExtensionAPI` method, NOT
|
|
20
|
+
* `ctx.setModel` — `ExtensionCommandContext` does not expose a setter) applies
|
|
21
|
+
* it to the live session, resolving `false` when no API key is configured.
|
|
22
|
+
* The `yagni` provider's catalog entries key `id` on the tier id itself (see
|
|
23
|
+
* `provider.ts`), so `peak` is the pi model id for the peak tier.
|
|
24
|
+
*
|
|
25
|
+
* State lives in the registration closure — module/session-scoped, mirroring
|
|
26
|
+
* `costHud.ts`'s accumulator — because a session's process exit is the only
|
|
27
|
+
* "end" there is; nothing needs to persist across it. `registerBoostCommand`
|
|
28
|
+
* returns a small `{ isBoosted() }` handle onto that same closure so other
|
|
29
|
+
* registrations (currently just `/cost`, see below) can read the live flag
|
|
30
|
+
* without a second source of truth.
|
|
31
|
+
*
|
|
32
|
+
* Picker-drift guard (spec review item 3): pi's OWN model picker (Ctrl+P,
|
|
33
|
+
* `/model`) can change the live model independently of `/boost` in either
|
|
34
|
+
* direction, so `state.active` can go stale relative to `ctx.model.id`. Both
|
|
35
|
+
* halves read the live model and reconcile rather than trusting `state.active`
|
|
36
|
+
* blindly:
|
|
37
|
+
*
|
|
38
|
+
* - `boostOn` while already active but the live model has drifted OFF peak
|
|
39
|
+
* (the user cycled models mid-boost without `/boost off`): re-applies
|
|
40
|
+
* peak instead of a silent "Already boosted." that would leave the
|
|
41
|
+
* driver quietly running a cheaper tier under an attribution header that
|
|
42
|
+
* claims otherwise. The remembered `priorModelId` is left untouched — the
|
|
43
|
+
* tier to restore is still whatever was active before the ORIGINAL boost,
|
|
44
|
+
* not the tier the user happened to drift to.
|
|
45
|
+
* - `boostOff` while active but the live model is no longer peak (same
|
|
46
|
+
* drift, encountered from the other command): the user already left
|
|
47
|
+
* boost manually, so forcing a switch back to the remembered prior tier
|
|
48
|
+
* would clobber a choice they just made on purpose. Instead this clears
|
|
49
|
+
* `/boost`'s own bookkeeping (state, env, chip) without touching the
|
|
50
|
+
* model, and names where they actually landed.
|
|
51
|
+
*
|
|
52
|
+
* KNOWN asymmetry, documented rather than worked around: `YAGNI_BOOST=1`
|
|
53
|
+
* while boosted makes every CHILD process spawned during the boost (a /go
|
|
54
|
+
* run, a subagent, an advisor consult) inherit it, and those children's
|
|
55
|
+
* completions carry `x-yagni-boost` because their provider is registered
|
|
56
|
+
* fresh per child. The DRIVER's own completions do not gain that header
|
|
57
|
+
* retroactively — `buildYagniProvider`'s headers are baked into the
|
|
58
|
+
* `registerProvider` call once, at session start, from the env snapshot at
|
|
59
|
+
* that moment (see `provider.ts` / `attributionHeaders`). Re-registering the
|
|
60
|
+
* provider mid-session to pick up a new header is explicitly NOT the fix
|
|
61
|
+
* here: it would race the in-flight request the switch itself triggers and
|
|
62
|
+
* has no test coverage as a live-swap path.
|
|
63
|
+
*
|
|
64
|
+
* This is exactly why `/cost` cannot rely on server rows alone: the server's
|
|
65
|
+
* per-tier "Boosted (tier) spend" subtotal (`formatServerCostLines`, Task 7)
|
|
66
|
+
* only ever sees rows carrying `x-yagni-boost` — i.e. `/go` children, never
|
|
67
|
+
* the driver's own turns. A session that only chats while boosted would
|
|
68
|
+
* otherwise show no boost line at all. `registerCostCommand`'s `isBoosted`
|
|
69
|
+
* dep (threaded from THIS module's return handle, the same way
|
|
70
|
+
* `advisorSubtotal` is threaded from `advisor.ts`) closes that gap: `/cost`
|
|
71
|
+
* appends a fixed "Boost is on. Driver turns bill at the peak tier." line
|
|
72
|
+
* whenever the live toggle is on, on BOTH the server-authoritative and the
|
|
73
|
+
* local-fallback branch, independent of what server rows happen to show.
|
|
74
|
+
*/
|
|
75
|
+
/** The pi provider name the YAGNI catalog registers under (see `provider.ts`). */
|
|
76
|
+
const YAGNI_PROVIDER = "yagni";
|
|
77
|
+
/** The tier `/boost` escalates to. */
|
|
78
|
+
const BOOST_TIER = "peak";
|
|
79
|
+
/** The session default tier, restored when no prior tier is known. */
|
|
80
|
+
const DEFAULT_TIER = "balanced";
|
|
81
|
+
/** Tier ids are lowercase; every known tier's display name is just Title Case of the id. */
|
|
82
|
+
function displayTierName(id) {
|
|
83
|
+
return id.length === 0 ? id : id.charAt(0).toUpperCase() + id.slice(1);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Turn boost on. PURE.
|
|
87
|
+
*
|
|
88
|
+
* - Already active AND the live model is still `peak`: genuine no-op,
|
|
89
|
+
* "Already boosted." (no switch — nothing about the prior tier changes).
|
|
90
|
+
* - Already active but the live model has drifted off `peak` (picker-drift
|
|
91
|
+
* guard 3b, see module docblock): re-applies `peak`, keeping the ORIGINAL
|
|
92
|
+
* `priorModelId` rather than adopting the drifted-to tier.
|
|
93
|
+
* - Not active, current model IS already `peak`: activates anyway (so
|
|
94
|
+
* attribution and `/boost off` behave correctly) with a distinct notice,
|
|
95
|
+
* remembering `peak` itself as the tier to restore.
|
|
96
|
+
* - Not active, otherwise: remembers the current tier, targets `peak`.
|
|
97
|
+
*/
|
|
98
|
+
export function boostOn(state, currentModelId) {
|
|
99
|
+
if (state.active) {
|
|
100
|
+
if (currentModelId === BOOST_TIER) {
|
|
101
|
+
return { state, notice: "Already boosted." };
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
state,
|
|
105
|
+
notice: `Re-boosted to Peak. /boost off to return to ${displayTierName(state.priorModelId ?? DEFAULT_TIER)}.`,
|
|
106
|
+
targetModelId: BOOST_TIER,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (currentModelId === BOOST_TIER) {
|
|
110
|
+
return {
|
|
111
|
+
state: { active: true, priorModelId: BOOST_TIER },
|
|
112
|
+
notice: "Already on Peak. /boost off returns to Peak.",
|
|
113
|
+
targetModelId: BOOST_TIER,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
state: { active: true, priorModelId: currentModelId },
|
|
118
|
+
notice: `Boosted to Peak. /boost off to return to ${displayTierName(currentModelId ?? DEFAULT_TIER)}.`,
|
|
119
|
+
targetModelId: BOOST_TIER,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Turn boost off. PURE.
|
|
124
|
+
*
|
|
125
|
+
* - Not active: no-op, "Boost is not on."
|
|
126
|
+
* - Active but the live model is no longer `peak` (picker-drift guard 3a,
|
|
127
|
+
* see module docblock): the user already left boost manually. Clears
|
|
128
|
+
* `/boost`'s own bookkeeping WITHOUT a switch — forcing one back to the
|
|
129
|
+
* remembered prior tier would clobber a choice just made on purpose — and
|
|
130
|
+
* names the model they actually landed on.
|
|
131
|
+
* - Active and still on `peak`: restores the remembered prior tier (falling
|
|
132
|
+
* back to the session default when none was recorded, which should not
|
|
133
|
+
* normally happen). When the prior tier is itself `peak` (the
|
|
134
|
+
* already-on-Peak activation path), the resulting switch is a no-op in
|
|
135
|
+
* effect — still applied, harmlessly.
|
|
136
|
+
*/
|
|
137
|
+
export function boostOff(state, currentModelId) {
|
|
138
|
+
if (!state.active) {
|
|
139
|
+
return { state, notice: "Boost is not on." };
|
|
140
|
+
}
|
|
141
|
+
if (currentModelId !== BOOST_TIER) {
|
|
142
|
+
return {
|
|
143
|
+
state: { active: false, priorModelId: undefined },
|
|
144
|
+
notice: `Boost off. Leaving the model on ${displayTierName(currentModelId ?? DEFAULT_TIER)}.`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const priorId = state.priorModelId ?? DEFAULT_TIER;
|
|
148
|
+
return {
|
|
149
|
+
state: { active: false, priorModelId: undefined },
|
|
150
|
+
notice: `Back to ${displayTierName(priorId)}.`,
|
|
151
|
+
targetModelId: priorId,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Resolve `tierId` to a live `Model` and apply it via `pi.setModel`.
|
|
156
|
+
*
|
|
157
|
+
* On a registry lookup miss, `allowFallback` (true only on the OFF path —
|
|
158
|
+
* MINOR 5) retries once against the session default tier rather than
|
|
159
|
+
* stranding the user on Peak over one missing catalog entry. Returns the
|
|
160
|
+
* tier id that was ACTUALLY applied, so the caller can tell whether the
|
|
161
|
+
* fallback fired and adjust the notice to name it honestly.
|
|
162
|
+
*
|
|
163
|
+
* @throws when neither the requested tier nor (if allowed) the fallback
|
|
164
|
+
* resolves, or when `pi.setModel` itself throws or resolves `false`.
|
|
165
|
+
*/
|
|
166
|
+
async function applyTier(pi, ctx, tierId, allowFallback) {
|
|
167
|
+
let model = ctx.modelRegistry?.find(YAGNI_PROVIDER, tierId);
|
|
168
|
+
let applied = tierId;
|
|
169
|
+
if (!model && allowFallback && tierId !== DEFAULT_TIER) {
|
|
170
|
+
model = ctx.modelRegistry?.find(YAGNI_PROVIDER, DEFAULT_TIER);
|
|
171
|
+
applied = DEFAULT_TIER;
|
|
172
|
+
}
|
|
173
|
+
if (!model) {
|
|
174
|
+
throw new Error(`Unknown YAGNI tier "${tierId}".`);
|
|
175
|
+
}
|
|
176
|
+
const ok = await pi.setModel(model);
|
|
177
|
+
if (!ok) {
|
|
178
|
+
throw new Error("setModel reported no API key configured.");
|
|
179
|
+
}
|
|
180
|
+
return applied;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Register the `/boost` command.
|
|
184
|
+
*
|
|
185
|
+
* `/boost` (no args) turns boost on; `/boost off` turns it off; any other
|
|
186
|
+
* argument shows a usage notice and changes nothing. Every transition that
|
|
187
|
+
* requires a model switch runs it through pi's real API and is guarded end to
|
|
188
|
+
* end: state and `YAGNI_BOOST` are only ever committed together, AFTER the
|
|
189
|
+
* switch succeeds (or is deliberately skipped — the picker-drift guards), so
|
|
190
|
+
* a half-applied boost (env set but model unchanged, or vice versa) can never
|
|
191
|
+
* happen. A failed ON reports "Boost failed."; a failed OFF reports a
|
|
192
|
+
* DISTINCT message, because the failure mode is materially worse — the
|
|
193
|
+
* driver is left still running (and billing) on Peak, not merely unchanged.
|
|
194
|
+
*/
|
|
195
|
+
export function registerBoostCommand(pi, deps = {}) {
|
|
196
|
+
const env = deps.env ?? process.env;
|
|
197
|
+
let state = { active: false };
|
|
198
|
+
const paintStatus = (ctx, active) => {
|
|
199
|
+
try {
|
|
200
|
+
if (ctx.hasUI)
|
|
201
|
+
ctx.ui.setStatus?.("yagni-boost", active ? "⚡ Peak" : undefined);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// The chip is chrome; never let it break /boost.
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
pi.registerCommand("boost", {
|
|
208
|
+
description: "Escalate this session's model to Peak until /boost off or the session ends. /boost off returns to the prior tier.",
|
|
209
|
+
handler: async (args, ctx) => {
|
|
210
|
+
const notify = (message, type) => {
|
|
211
|
+
if (ctx.hasUI)
|
|
212
|
+
ctx.ui.notify(message, type);
|
|
213
|
+
};
|
|
214
|
+
// MINOR 4: case-insensitive "off" match, mirroring /mode's arg handling.
|
|
215
|
+
const arg = args.trim().toLowerCase();
|
|
216
|
+
if (arg !== "" && arg !== "off") {
|
|
217
|
+
notify("Usage: /boost (turn on) or /boost off.", "warning");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const isOff = arg === "off";
|
|
221
|
+
const wasActive = state.active;
|
|
222
|
+
const result = isOff ? boostOff(state, ctx.model?.id) : boostOn(state, ctx.model?.id);
|
|
223
|
+
try {
|
|
224
|
+
let notice = result.notice;
|
|
225
|
+
if (result.targetModelId) {
|
|
226
|
+
const applied = await applyTier(pi, ctx, result.targetModelId, isOff);
|
|
227
|
+
if (applied !== result.targetModelId) {
|
|
228
|
+
// MINOR 5: the remembered/target tier no longer resolves in the
|
|
229
|
+
// catalog; we landed on the session default instead of
|
|
230
|
+
// stranding the user on Peak. Name the fallback so the notice
|
|
231
|
+
// stays honest about what actually happened.
|
|
232
|
+
notice = `Back to ${displayTierName(applied)} (could not restore ${displayTierName(result.targetModelId)}).`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
state = result.state;
|
|
236
|
+
// MINOR 6: only touch the env on a real active/inactive flip. Both
|
|
237
|
+
// pure no-op paths ("Already boosted.", "Boost is not on.") return
|
|
238
|
+
// `state` unchanged, so this never fires for them — an inherited
|
|
239
|
+
// YAGNI_BOOST from outside this session is left exactly as found.
|
|
240
|
+
if (state.active !== wasActive) {
|
|
241
|
+
if (state.active) {
|
|
242
|
+
env.YAGNI_BOOST = "1";
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
delete env.YAGNI_BOOST;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
notify(notice, "info");
|
|
249
|
+
paintStatus(ctx, state.active);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// IMPORTANT 1: a failed OFF is materially worse than a failed ON —
|
|
253
|
+
// the driver is STILL on Peak, still billing at Peak rates, which a
|
|
254
|
+
// generic "Boost failed." does not convey. Neither `state` nor
|
|
255
|
+
// `YAGNI_BOOST` was touched above (the throw lands before both), so
|
|
256
|
+
// the chip is also left exactly as it was: still lit.
|
|
257
|
+
notify(isOff ? "Could not leave boost. Still on Peak; try /boost off again." : "Boost failed.", "error");
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
return { isBoosted: () => state.active };
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=boostCommand.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAGNI Code branding.
|
|
3
|
+
*
|
|
4
|
+
* YAGNI Code IS the pi coding agent, but a developer running it should never see
|
|
5
|
+
* "pi" or "Earendil" — not in the agent's self-description, not in the chrome.
|
|
6
|
+
* pi stays an unmodified dependency; we rebrand entirely through its extension
|
|
7
|
+
* API. This module owns the one place that matters most: the system prompt
|
|
8
|
+
* identity. It is a pure function so it is fully unit-testable without pi.
|
|
9
|
+
*/
|
|
10
|
+
/** User-facing product name. */
|
|
11
|
+
export declare const BRAND_NAME = "YAGNI Code";
|
|
12
|
+
/**
|
|
13
|
+
* The YAGNI Code identity paragraph that replaces pi's default
|
|
14
|
+
* "You are an expert coding assistant operating inside pi..." opener. It states
|
|
15
|
+
* the differentiator (connected to the YAGNI app) and the working contract
|
|
16
|
+
* (consult ask_yagni before guessing) so the agent behaves more autonomously.
|
|
17
|
+
*
|
|
18
|
+
* This is the BASE identity, used for every non-driver caller (a `/go` stage
|
|
19
|
+
* child, a subagent, an advisor consult) as well as the driver, since the
|
|
20
|
+
* extension loads identically in every one of those pi processes. It carries
|
|
21
|
+
* no delegation directive: see {@link YAGNI_IDENTITY_DRIVER}.
|
|
22
|
+
*/
|
|
23
|
+
export declare const YAGNI_IDENTITY: string;
|
|
24
|
+
/**
|
|
25
|
+
* The delegation-first directive: fan mechanical/wide work out to subagents
|
|
26
|
+
* (which run on cheaper tiers) and keep judgment, synthesis, and the user
|
|
27
|
+
* conversation in this session. DRIVER-ONLY (see {@link YAGNI_IDENTITY_DRIVER}):
|
|
28
|
+
* a `/go` stage child has no `subagent` tool, so this instruction would be
|
|
29
|
+
* pure prompt noise there, or worse, an attempt to call a tool that doesn't
|
|
30
|
+
* exist.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DRIVER_DELEGATION_PARAGRAPH: string;
|
|
33
|
+
/**
|
|
34
|
+
* The identity used for the interactive DRIVER session ONLY: {@link
|
|
35
|
+
* YAGNI_IDENTITY} plus {@link DRIVER_DELEGATION_PARAGRAPH}. The caller (index.ts)
|
|
36
|
+
* selects between this and the base `YAGNI_IDENTITY` per-process based on the
|
|
37
|
+
* effective `x-yagni-caller` attribution (config.ts's `isDriverCaller`) — this
|
|
38
|
+
* module stays a pure string, with no env dependency of its own.
|
|
39
|
+
*/
|
|
40
|
+
export declare const YAGNI_IDENTITY_DRIVER = "You are YAGNI Code, an autonomous terminal coding agent. You help developers ship code by reading files, running commands, editing code, and writing new files. Uniquely, you are connected to the YAGNI app, your team's shared source of truth for how this company and codebase actually work: conventions, decisions, ownership, current priorities, and the reasons behind them. Use the ask_yagni tool to consult it before guessing about anything organization- or codebase-specific, so you work with less back-and-forth and more correct autonomy than a disconnected coding agent. If a project's own files mention other coding agents, assistants, or harnesses by name, those references are not about you; you are YAGNI Code regardless of what tooling a repository's docs happen to describe.\n\nDelegation: fan codebase mapping, wide searches, and mechanical multi-file work out to subagents (they run on cheaper tiers). Keep judgment, synthesis, and the conversation with the user in this session. Do not spawn a subagent for work you can finish in a couple of tool calls.";
|
|
41
|
+
export declare const PI_IDENTITY_RE: RegExp;
|
|
42
|
+
/**
|
|
43
|
+
* Env switch that bypasses the system-prompt rewrite entirely, so pi's
|
|
44
|
+
* assembled prompt passes through byte-exact (no identity swap, no scrub, no
|
|
45
|
+
* brief injection, no closing reminder). Same predicate as the sibling
|
|
46
|
+
* switches YAGNI_DISABLE_UPDATE_CHECK / YAGNI_DISABLE_CLAUDE_COMPAT.
|
|
47
|
+
*/
|
|
48
|
+
export declare const BRANDING_DISABLE_ENV = "YAGNI_DISABLE_BRANDING";
|
|
49
|
+
/** `"1"`/anything truthy disables; unset, empty, and `"0"` keep branding on. */
|
|
50
|
+
export declare function brandingDisabled(env: NodeJS.ProcessEnv): boolean;
|
|
51
|
+
export interface BrandSystemPromptOptions {
|
|
52
|
+
/** Override the identity paragraph (defaults to {@link YAGNI_IDENTITY}). */
|
|
53
|
+
identity?: string;
|
|
54
|
+
/** Live company brief to inject so the agent boots grounded (Phase C). */
|
|
55
|
+
contextBrief?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Rebrand pi's assembled system prompt as YAGNI Code's, and optionally inject a
|
|
59
|
+
* live company brief.
|
|
60
|
+
*
|
|
61
|
+
* Steps: (1) drop pi's self-referential documentation block, (2) replace the
|
|
62
|
+
* identity sentence (with a loose fallback, else prepend), (3) scrub residual
|
|
63
|
+
* brand tokens outside user content, (4) append the company brief + a proactive
|
|
64
|
+
* ask_yagni directive. Idempotent: re-branding keeps a single identity.
|
|
65
|
+
*/
|
|
66
|
+
export declare function brandSystemPrompt(original: string, opts?: BrandSystemPromptOptions): string;
|
|
67
|
+
/** The minimal theme surface the masthead needs (a subset of pi's Theme). */
|
|
68
|
+
export interface MastheadTheme {
|
|
69
|
+
bold(s: string): string;
|
|
70
|
+
fg(color: string, s: string): string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build the YAGNI Code startup masthead string, rendered into a header that
|
|
74
|
+
* REPLACES pi's built-in startup banner (which otherwise shows "pi v<version>"
|
|
75
|
+
* and a "Pi can explain its own features…" onboarding line). Kept here as a
|
|
76
|
+
* pure string builder so its content is unit-testable without a terminal.
|
|
77
|
+
*/
|
|
78
|
+
export declare function buildMastheadString(theme: MastheadTheme): string;
|
|
79
|
+
//# sourceMappingURL=branding.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAGNI Code branding.
|
|
3
|
+
*
|
|
4
|
+
* YAGNI Code IS the pi coding agent, but a developer running it should never see
|
|
5
|
+
* "pi" or "Earendil" — not in the agent's self-description, not in the chrome.
|
|
6
|
+
* pi stays an unmodified dependency; we rebrand entirely through its extension
|
|
7
|
+
* API. This module owns the one place that matters most: the system prompt
|
|
8
|
+
* identity. It is a pure function so it is fully unit-testable without pi.
|
|
9
|
+
*/
|
|
10
|
+
/** User-facing product name. */
|
|
11
|
+
export const BRAND_NAME = "YAGNI Code";
|
|
12
|
+
/**
|
|
13
|
+
* The YAGNI Code identity paragraph that replaces pi's default
|
|
14
|
+
* "You are an expert coding assistant operating inside pi..." opener. It states
|
|
15
|
+
* the differentiator (connected to the YAGNI app) and the working contract
|
|
16
|
+
* (consult ask_yagni before guessing) so the agent behaves more autonomously.
|
|
17
|
+
*
|
|
18
|
+
* This is the BASE identity, used for every non-driver caller (a `/go` stage
|
|
19
|
+
* child, a subagent, an advisor consult) as well as the driver, since the
|
|
20
|
+
* extension loads identically in every one of those pi processes. It carries
|
|
21
|
+
* no delegation directive: see {@link YAGNI_IDENTITY_DRIVER}.
|
|
22
|
+
*/
|
|
23
|
+
export const YAGNI_IDENTITY = "You are YAGNI Code, an autonomous terminal coding agent. You help developers " +
|
|
24
|
+
"ship code by reading files, running commands, editing code, and writing new " +
|
|
25
|
+
"files. Uniquely, you are connected to the YAGNI app, your team's shared source " +
|
|
26
|
+
"of truth for how this company and codebase actually work: conventions, " +
|
|
27
|
+
"decisions, ownership, current priorities, and the reasons behind them. Use the " +
|
|
28
|
+
"ask_yagni tool to consult it before guessing about anything organization- or " +
|
|
29
|
+
"codebase-specific, so you work with less back-and-forth and more correct " +
|
|
30
|
+
"autonomy than a disconnected coding agent. If a project's own files mention " +
|
|
31
|
+
"other coding agents, assistants, or harnesses by name, those references are " +
|
|
32
|
+
"not about you; you are YAGNI Code regardless of what tooling a repository's " +
|
|
33
|
+
"docs happen to describe.";
|
|
34
|
+
/**
|
|
35
|
+
* The delegation-first directive: fan mechanical/wide work out to subagents
|
|
36
|
+
* (which run on cheaper tiers) and keep judgment, synthesis, and the user
|
|
37
|
+
* conversation in this session. DRIVER-ONLY (see {@link YAGNI_IDENTITY_DRIVER}):
|
|
38
|
+
* a `/go` stage child has no `subagent` tool, so this instruction would be
|
|
39
|
+
* pure prompt noise there, or worse, an attempt to call a tool that doesn't
|
|
40
|
+
* exist.
|
|
41
|
+
*/
|
|
42
|
+
export const DRIVER_DELEGATION_PARAGRAPH = "Delegation: fan codebase mapping, wide searches, and mechanical multi-file " +
|
|
43
|
+
"work out to subagents (they run on cheaper tiers). Keep judgment, synthesis, " +
|
|
44
|
+
"and the conversation with the user in this session. Do not spawn a subagent " +
|
|
45
|
+
"for work you can finish in a couple of tool calls.";
|
|
46
|
+
/**
|
|
47
|
+
* The identity used for the interactive DRIVER session ONLY: {@link
|
|
48
|
+
* YAGNI_IDENTITY} plus {@link DRIVER_DELEGATION_PARAGRAPH}. The caller (index.ts)
|
|
49
|
+
* selects between this and the base `YAGNI_IDENTITY` per-process based on the
|
|
50
|
+
* effective `x-yagni-caller` attribution (config.ts's `isDriverCaller`) — this
|
|
51
|
+
* module stays a pure string, with no env dependency of its own.
|
|
52
|
+
*/
|
|
53
|
+
export const YAGNI_IDENTITY_DRIVER = `${YAGNI_IDENTITY}\n\n${DRIVER_DELEGATION_PARAGRAPH}`;
|
|
54
|
+
// pi 0.83.0's exact identity sentence (dist/core/system-prompt.js). Exported as
|
|
55
|
+
// the identity anchor the CLI's pi-contract tripwire test reads back from pi's
|
|
56
|
+
// built system prompt, so a pi bump that reworded the opener (silently defeating
|
|
57
|
+
// our rebrand) fails CI loudly. Export-only: no behavior change here.
|
|
58
|
+
export const PI_IDENTITY_RE = /You are an expert coding assistant operating inside pi, a coding agent harness\.\s*You help users by reading files, executing commands, editing code, and writing new files\./;
|
|
59
|
+
// Looser fallback if pi rewords its opener in a future version.
|
|
60
|
+
const PI_IDENTITY_LOOSE_RE = /You are [^\n.]*operating inside pi[^\n.]*\.(?:\s*You help[^\n.]*\.)?/i;
|
|
61
|
+
// pi's self-referential "Pi documentation (...)" header plus its bullet list.
|
|
62
|
+
// The block is a header line followed by consecutive "- " bullets; matching the
|
|
63
|
+
// bullets explicitly keeps us safe regardless of what section pi appends next.
|
|
64
|
+
const PI_DOCS_BLOCK_RE = /\n*Pi documentation \(read only[^\n]*(?:\n- [^\n]*)*/gi;
|
|
65
|
+
/** Neutralize residual pi/Earendil brand tokens in pi-owned text. */
|
|
66
|
+
function scrubPiHarness(text) {
|
|
67
|
+
return text
|
|
68
|
+
.replace(/\bEarendil(?:\s+Works)?\b/gi, "YAGNI")
|
|
69
|
+
// Whole-word "pi" only, so "api", "pip", "pipeline" are untouched.
|
|
70
|
+
.replace(/\bpi\b/gi, BRAND_NAME);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Scrub pi-brand tokens everywhere EXCEPT inside the user's <project_context>.
|
|
74
|
+
* The user's own AGENTS.md may legitimately mention "pi-extension-yagni"; a
|
|
75
|
+
* blanket scrub would corrupt their instructions. pi wraps project files in
|
|
76
|
+
* <project_context>…</project_context>, so we leave those segments verbatim.
|
|
77
|
+
*/
|
|
78
|
+
function scrubOutsideProjectContext(s) {
|
|
79
|
+
const parts = s.split(/(<project_context>[\s\S]*?<\/project_context>)/);
|
|
80
|
+
return parts
|
|
81
|
+
.map((p) => (p.startsWith("<project_context>") ? p : scrubPiHarness(p)))
|
|
82
|
+
.join("");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Env switch that bypasses the system-prompt rewrite entirely, so pi's
|
|
86
|
+
* assembled prompt passes through byte-exact (no identity swap, no scrub, no
|
|
87
|
+
* brief injection, no closing reminder). Same predicate as the sibling
|
|
88
|
+
* switches YAGNI_DISABLE_UPDATE_CHECK / YAGNI_DISABLE_CLAUDE_COMPAT.
|
|
89
|
+
*/
|
|
90
|
+
export const BRANDING_DISABLE_ENV = "YAGNI_DISABLE_BRANDING";
|
|
91
|
+
/** `"1"`/anything truthy disables; unset, empty, and `"0"` keep branding on. */
|
|
92
|
+
export function brandingDisabled(env) {
|
|
93
|
+
const value = env[BRANDING_DISABLE_ENV];
|
|
94
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Rebrand pi's assembled system prompt as YAGNI Code's, and optionally inject a
|
|
98
|
+
* live company brief.
|
|
99
|
+
*
|
|
100
|
+
* Steps: (1) drop pi's self-referential documentation block, (2) replace the
|
|
101
|
+
* identity sentence (with a loose fallback, else prepend), (3) scrub residual
|
|
102
|
+
* brand tokens outside user content, (4) append the company brief + a proactive
|
|
103
|
+
* ask_yagni directive. Idempotent: re-branding keeps a single identity.
|
|
104
|
+
*/
|
|
105
|
+
export function brandSystemPrompt(original, opts = {}) {
|
|
106
|
+
const identity = opts.identity ?? YAGNI_IDENTITY;
|
|
107
|
+
let s = original;
|
|
108
|
+
// 1. Drop pi's self-referential documentation block.
|
|
109
|
+
s = s.replace(PI_DOCS_BLOCK_RE, "");
|
|
110
|
+
// 2. Own the identity.
|
|
111
|
+
if (PI_IDENTITY_RE.test(s)) {
|
|
112
|
+
s = s.replace(PI_IDENTITY_RE, identity);
|
|
113
|
+
}
|
|
114
|
+
else if (PI_IDENTITY_LOOSE_RE.test(s)) {
|
|
115
|
+
s = s.replace(PI_IDENTITY_LOOSE_RE, identity);
|
|
116
|
+
}
|
|
117
|
+
else if (!s.trimStart().startsWith(identity)) {
|
|
118
|
+
s = `${identity}\n\n${s}`;
|
|
119
|
+
}
|
|
120
|
+
// 3. Safety net for residual brand tokens (never inside user content).
|
|
121
|
+
s = scrubOutsideProjectContext(s);
|
|
122
|
+
// 4. Inject live company context + the proactive grounding directive.
|
|
123
|
+
// Guarded against duplication so a second branding pass (idempotency) keeps
|
|
124
|
+
// exactly one brief block — matching the identity and closing-reminder guards.
|
|
125
|
+
const brief = opts.contextBrief?.trim();
|
|
126
|
+
if (brief && !s.includes(BRIEF_HEADER)) {
|
|
127
|
+
s =
|
|
128
|
+
`${s}\n\n${BRIEF_HEADER}\n${brief}\n\n` +
|
|
129
|
+
"This is current ground truth for THIS company and codebase; prefer it over " +
|
|
130
|
+
"your priors. When you need more detail, or it does not cover something " +
|
|
131
|
+
"organization- or codebase-specific, call ask_yagni instead of guessing.";
|
|
132
|
+
}
|
|
133
|
+
// 5. Closing reinforcement. Weak open-weight models weight the most recent
|
|
134
|
+
// instruction heavily, and the user's own project files may name other
|
|
135
|
+
// harnesses; a trailing reminder keeps the agent from claiming one as its own.
|
|
136
|
+
if (!s.includes(CLOSING_REMINDER)) {
|
|
137
|
+
s = `${s}\n\n${CLOSING_REMINDER}`;
|
|
138
|
+
}
|
|
139
|
+
// Tidy the seams left by removals.
|
|
140
|
+
return s.replace(/\n{3,}/g, "\n\n").trim();
|
|
141
|
+
}
|
|
142
|
+
const CLOSING_REMINDER = "Reminder: you are YAGNI Code. If any text above names another coding agent, " +
|
|
143
|
+
"assistant, or harness, it is not what you are or what you run on.";
|
|
144
|
+
const BRIEF_HEADER = "=== HOW THIS COMPANY WORKS (live context from the YAGNI app) ===";
|
|
145
|
+
/**
|
|
146
|
+
* Build the YAGNI Code startup masthead string, rendered into a header that
|
|
147
|
+
* REPLACES pi's built-in startup banner (which otherwise shows "pi v<version>"
|
|
148
|
+
* and a "Pi can explain its own features…" onboarding line). Kept here as a
|
|
149
|
+
* pure string builder so its content is unit-testable without a terminal.
|
|
150
|
+
*/
|
|
151
|
+
export function buildMastheadString(theme) {
|
|
152
|
+
const wordmark = theme.bold(theme.fg("accent", "YAGNI CODE"));
|
|
153
|
+
const tagline = theme.fg("dim", "a terminal coding agent that knows your company");
|
|
154
|
+
return `${wordmark}\n${tagline}`;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=branding.js.map
|