@yagni-app/code 0.2.0 → 0.3.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/dist/cli.d.ts +30 -0
- package/dist/cli.js +135 -3
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/extension/advisor.d.ts +4 -4
- package/dist/extension/advisor.js +6 -7
- package/dist/extension/approvedPrefixes.d.ts +92 -0
- package/dist/extension/approvedPrefixes.js +252 -0
- package/dist/extension/askAdvisorTool.d.ts +2 -2
- package/dist/extension/askAdvisorTool.js +5 -5
- package/dist/extension/askYagniTool.js +49 -0
- package/dist/extension/branding.d.ts +24 -3
- package/dist/extension/branding.js +71 -10
- package/dist/extension/chipEditor.d.ts +30 -9
- package/dist/extension/chipEditor.js +173 -59
- package/dist/extension/claudeRules.d.ts +0 -2
- package/dist/extension/claudeRules.js +0 -8
- package/dist/extension/cmux/dispatcher.d.ts +25 -0
- package/dist/extension/cmux/dispatcher.js +266 -0
- package/dist/extension/cmux/hooks.d.ts +12 -0
- package/dist/extension/cmux/hooks.js +192 -0
- package/dist/extension/cmux/index.d.ts +3 -0
- package/dist/extension/cmux/index.js +155 -0
- package/dist/extension/cmux/naming.d.ts +5 -0
- package/dist/extension/cmux/naming.js +23 -0
- package/dist/extension/cmux/state.d.ts +33 -0
- package/dist/extension/cmux/state.js +142 -0
- package/dist/extension/config.d.ts +32 -1
- package/dist/extension/config.js +36 -4
- package/dist/extension/costHud.d.ts +16 -22
- package/dist/extension/costHud.js +8 -47
- package/dist/extension/crashReport.js +1 -3
- package/dist/extension/execPolicy.d.ts +119 -0
- package/dist/extension/execPolicy.js +805 -0
- package/dist/extension/footer.d.ts +111 -0
- package/dist/extension/footer.js +294 -0
- package/dist/extension/guardian.d.ts +129 -0
- package/dist/extension/guardian.js +213 -0
- package/dist/extension/index.d.ts +15 -4
- package/dist/extension/index.js +250 -24
- package/dist/extension/permission.d.ts +123 -10
- package/dist/extension/permission.js +586 -40
- package/dist/extension/pipeline/childRegistry.d.ts +41 -0
- package/dist/extension/pipeline/childRegistry.js +118 -0
- package/dist/extension/pipeline/finish.js +5 -1
- package/dist/extension/pipeline/goCommand.d.ts +1 -1
- package/dist/extension/pipeline/goCommand.js +35 -6
- package/dist/extension/pipeline/goStatusCommands.d.ts +10 -0
- package/dist/extension/pipeline/goStatusCommands.js +61 -1
- package/dist/extension/pipeline/personas.js +25 -0
- package/dist/extension/pipeline/runRegistry.d.ts +14 -0
- package/dist/extension/pipeline/runRegistry.js +35 -0
- package/dist/extension/pipeline/runner.js +4 -0
- package/dist/extension/pipeline/verify.d.ts +4 -0
- package/dist/extension/pipeline/verify.js +48 -26
- package/dist/extension/redact.d.ts +20 -0
- package/dist/extension/redact.js +64 -0
- package/dist/extension/rerouteNotice.d.ts +3 -12
- package/dist/extension/rerouteNotice.js +36 -15
- package/dist/extension/subagentRender.d.ts +129 -0
- package/dist/extension/subagentRender.js +441 -0
- package/dist/extension/subagents.d.ts +4 -7
- package/dist/extension/subagents.js +103 -33
- package/dist/extension/ticketTools.d.ts +37 -0
- package/dist/extension/ticketTools.js +117 -0
- package/dist/extension/tokenProvider.js +46 -5
- package/dist/launch.d.ts +7 -0
- package/dist/launch.js +24 -12
- package/dist/padding.d.ts +22 -0
- package/dist/padding.js +25 -0
- package/dist/promptEnrichment.d.ts +40 -0
- package/dist/promptEnrichment.js +85 -0
- package/dist/signalForward.d.ts +60 -0
- package/dist/signalForward.js +130 -0
- package/package.json +5 -5
- package/dist/extension/boostCommand.d.ts +0 -144
- package/dist/extension/boostCommand.js +0 -263
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
/**
|
|
4
|
+
* Ticket write-back tools (spec 2026-08-09): file a ticket, move a ticket.
|
|
5
|
+
*
|
|
6
|
+
* Both are EXPLICIT user-intent actions — the guidelines pin the agent to
|
|
7
|
+
* calling them only when the developer asked. Writes execute server-side as
|
|
8
|
+
* the developer's own tracker account (per-user write identity); when no
|
|
9
|
+
* personal connection exists the backend answers 412 `connect_required` and
|
|
10
|
+
* the tool renders the one-time connect prompt instead of failing opaquely.
|
|
11
|
+
*/
|
|
12
|
+
export interface MakeTicketToolOptions {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
getToken: () => string | undefined;
|
|
15
|
+
fetchImpl?: typeof fetch;
|
|
16
|
+
}
|
|
17
|
+
declare const fileTicketParams: Type.TObject<{
|
|
18
|
+
title: Type.TString;
|
|
19
|
+
description: Type.TOptional<Type.TString>;
|
|
20
|
+
tracker: Type.TOptional<Type.TUnion<[Type.TLiteral<"jira">, Type.TLiteral<"linear">]>>;
|
|
21
|
+
target_key: Type.TOptional<Type.TString>;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function makeFileTicketTool(opts: MakeTicketToolOptions): ToolDefinition<typeof fileTicketParams, {
|
|
24
|
+
identifier?: string;
|
|
25
|
+
url?: string | null;
|
|
26
|
+
}>;
|
|
27
|
+
declare const updateTicketStatusParams: Type.TObject<{
|
|
28
|
+
ref: Type.TString;
|
|
29
|
+
status: Type.TString;
|
|
30
|
+
tracker: Type.TOptional<Type.TUnion<[Type.TLiteral<"jira">, Type.TLiteral<"linear">]>>;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function makeUpdateTicketStatusTool(opts: MakeTicketToolOptions): ToolDefinition<typeof updateTicketStatusParams, {
|
|
33
|
+
identifier?: string;
|
|
34
|
+
state?: string;
|
|
35
|
+
}>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=ticketTools.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { friendlyFetchError, METERED_POST_FETCH_POLICY, resilientFetch, } from "./resilientFetch.js";
|
|
3
|
+
/** Render backend problem responses (412/404/422) as agent-relayable text. */
|
|
4
|
+
async function renderProblem(toolName, res) {
|
|
5
|
+
let body = null;
|
|
6
|
+
try {
|
|
7
|
+
body = (await res.clone().json());
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
body = null;
|
|
11
|
+
}
|
|
12
|
+
if (res.status === 412 && body?.error === "connect_required") {
|
|
13
|
+
return [
|
|
14
|
+
`A personal ${body.service ?? "tracker"} connection is needed to write as you.`,
|
|
15
|
+
`Connect here (one time): ${body.connect_url ?? "(ask your admin for the connect link)"}`,
|
|
16
|
+
"Then ask me again and I'll retry.",
|
|
17
|
+
].join("\n");
|
|
18
|
+
}
|
|
19
|
+
if ((res.status === 404 || res.status === 422) && body?.error) {
|
|
20
|
+
const options = body.options?.length
|
|
21
|
+
? `\nAvailable options: ${body.options.join(", ")}`
|
|
22
|
+
: "";
|
|
23
|
+
return `${body.message ?? body.error}${options}`;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(await friendlyFetchError(toolName, res));
|
|
26
|
+
}
|
|
27
|
+
const fileTicketParams = Type.Object({
|
|
28
|
+
title: Type.String(),
|
|
29
|
+
description: Type.Optional(Type.String()),
|
|
30
|
+
tracker: Type.Optional(Type.Union([Type.Literal("jira"), Type.Literal("linear")])),
|
|
31
|
+
target_key: Type.Optional(Type.String()),
|
|
32
|
+
});
|
|
33
|
+
export function makeFileTicketTool(opts) {
|
|
34
|
+
return {
|
|
35
|
+
name: "file_ticket",
|
|
36
|
+
label: "File Ticket",
|
|
37
|
+
description: "File a ticket in the workspace's tracker (Jira or Linear), attributed to the developer's " +
|
|
38
|
+
"own account. ONLY call when the user explicitly asks to file/create a ticket — never " +
|
|
39
|
+
"speculatively, never as a side effect of other work. Pass target_key (Jira project key or " +
|
|
40
|
+
"Linear team key) when the workspace has more than one.",
|
|
41
|
+
promptSnippet: "file_ticket: file a ticket in the workspace tracker as the developer (explicit request only).",
|
|
42
|
+
promptGuidelines: [
|
|
43
|
+
"Call file_ticket ONLY when the user explicitly asks to file, create, or capture a ticket.",
|
|
44
|
+
"If the tool reports a connect prompt or asks for a target/tracker, relay it verbatim and wait for the user.",
|
|
45
|
+
],
|
|
46
|
+
parameters: fileTicketParams,
|
|
47
|
+
async execute(toolCallId, params, signal) {
|
|
48
|
+
const res = await resilientFetch(`${opts.baseUrl}/api/yagni-code/tickets`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"content-type": "application/json",
|
|
52
|
+
authorization: `Bearer ${opts.getToken() ?? ""}`,
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({ ...params, idempotencyKey: toolCallId }),
|
|
55
|
+
}, { fetchImpl: opts.fetchImpl, signal, policy: METERED_POST_FETCH_POLICY });
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const text = await renderProblem("file_ticket", res);
|
|
58
|
+
return { content: [{ type: "text", text }], details: {} };
|
|
59
|
+
}
|
|
60
|
+
const data = (await res.json());
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: `Filed ${data.identifier}${data.url ? `: ${data.url}` : ""}`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
details: { identifier: data.identifier, url: data.url },
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const updateTicketStatusParams = Type.Object({
|
|
74
|
+
ref: Type.String(),
|
|
75
|
+
status: Type.String(),
|
|
76
|
+
tracker: Type.Optional(Type.Union([Type.Literal("jira"), Type.Literal("linear")])),
|
|
77
|
+
});
|
|
78
|
+
export function makeUpdateTicketStatusTool(opts) {
|
|
79
|
+
return {
|
|
80
|
+
name: "update_ticket_status",
|
|
81
|
+
label: "Update Ticket Status",
|
|
82
|
+
description: "Move a ticket to a new status by name (e.g. mark YAG-123 In Progress), attributed to the " +
|
|
83
|
+
"developer's own tracker account. ONLY call on the user's explicit request — never as an " +
|
|
84
|
+
"automatic side effect of starting or finishing work.",
|
|
85
|
+
promptSnippet: "update_ticket_status: move a tracker ticket to a named status as the developer (explicit request only).",
|
|
86
|
+
promptGuidelines: [
|
|
87
|
+
"Call update_ticket_status ONLY when the user explicitly asks to move/mark a ticket's status.",
|
|
88
|
+
"If the requested status is not available, relay the offered options and let the user pick.",
|
|
89
|
+
],
|
|
90
|
+
parameters: updateTicketStatusParams,
|
|
91
|
+
async execute(toolCallId, params, signal) {
|
|
92
|
+
const res = await resilientFetch(`${opts.baseUrl}/api/yagni-code/tickets/transition`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: {
|
|
95
|
+
"content-type": "application/json",
|
|
96
|
+
authorization: `Bearer ${opts.getToken() ?? ""}`,
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({ ...params, idempotencyKey: toolCallId }),
|
|
99
|
+
}, { fetchImpl: opts.fetchImpl, signal, policy: METERED_POST_FETCH_POLICY });
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
const text = await renderProblem("update_ticket_status", res);
|
|
102
|
+
return { content: [{ type: "text", text }], details: {} };
|
|
103
|
+
}
|
|
104
|
+
const data = (await res.json());
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: `${data.identifier} → ${data.state ?? params.status}`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
details: { identifier: data.identifier, state: data.state },
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=ticketTools.js.map
|
|
@@ -130,7 +130,7 @@ export function makeTokenProvider(deps) {
|
|
|
130
130
|
});
|
|
131
131
|
}, delay);
|
|
132
132
|
}
|
|
133
|
-
function applyRotation(rotation) {
|
|
133
|
+
function applyRotation(rotation, skipPersist = false) {
|
|
134
134
|
token = rotation.token;
|
|
135
135
|
if (rotation.expiresAt)
|
|
136
136
|
expiresAt = rotation.expiresAt;
|
|
@@ -139,13 +139,45 @@ export function makeTokenProvider(deps) {
|
|
|
139
139
|
env.YAGNI_TOKEN = rotation.token;
|
|
140
140
|
if (rotation.expiresAt)
|
|
141
141
|
env.YAGNI_TOKEN_EXPIRES_AT = rotation.expiresAt;
|
|
142
|
+
if (!skipPersist) {
|
|
143
|
+
try {
|
|
144
|
+
persistProfile(rotation);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* fail-soft */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
armProactiveTimer();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Read the token from the launcher's profile file on disk. Used as a fallback
|
|
154
|
+
* when the server-side refresh fails (the old token is also invalid for
|
|
155
|
+
* /auth/refresh): an external `yagni login` writes a fresh token to the same
|
|
156
|
+
* file, so re-reading it can recover a session that the server refresh cannot.
|
|
157
|
+
* Returns null when the file is missing, unreadable, or carries the same token
|
|
158
|
+
* already in memory.
|
|
159
|
+
*/
|
|
160
|
+
function readTokenFromDisk() {
|
|
161
|
+
const profilePath = env.YAGNI_PROFILE_PATH?.trim();
|
|
162
|
+
if (!profilePath)
|
|
163
|
+
return null;
|
|
142
164
|
try {
|
|
143
|
-
|
|
165
|
+
const parsed = JSON.parse(readFileSync(profilePath, "utf8"));
|
|
166
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
167
|
+
return null;
|
|
168
|
+
const obj = parsed;
|
|
169
|
+
const diskToken = typeof obj.token === "string" ? obj.token : undefined;
|
|
170
|
+
if (!diskToken || diskToken === token)
|
|
171
|
+
return null;
|
|
172
|
+
return {
|
|
173
|
+
token: diskToken,
|
|
174
|
+
expiresAt: typeof obj.expiresAt === "string" ? obj.expiresAt : undefined,
|
|
175
|
+
workspaceId: typeof obj.workspaceId === "string" ? obj.workspaceId : undefined,
|
|
176
|
+
};
|
|
144
177
|
}
|
|
145
178
|
catch {
|
|
146
|
-
|
|
179
|
+
return null;
|
|
147
180
|
}
|
|
148
|
-
armProactiveTimer();
|
|
149
181
|
}
|
|
150
182
|
async function doRefresh() {
|
|
151
183
|
const current = token;
|
|
@@ -161,8 +193,17 @@ export function makeTokenProvider(deps) {
|
|
|
161
193
|
body: "{}",
|
|
162
194
|
signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS),
|
|
163
195
|
});
|
|
164
|
-
if (!res.ok)
|
|
196
|
+
if (!res.ok) {
|
|
197
|
+
// Server refresh failed (the old token is also invalid for /auth/refresh).
|
|
198
|
+
// Fall back to the profile file on disk: an external `yagni login` may
|
|
199
|
+
// have written a fresh token there that this running session hasn't seen.
|
|
200
|
+
const disk = readTokenFromDisk();
|
|
201
|
+
if (disk) {
|
|
202
|
+
applyRotation(disk, true);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
165
205
|
return false;
|
|
206
|
+
}
|
|
166
207
|
const data = (await res.json());
|
|
167
208
|
if (!data || typeof data.token !== "string" || data.token.length === 0)
|
|
168
209
|
return false;
|
package/dist/launch.d.ts
CHANGED
|
@@ -71,6 +71,13 @@ export interface BuildLaunchOptions {
|
|
|
71
71
|
cliVersion?: string;
|
|
72
72
|
/** Base environment to extend (defaults to process.env at call sites). */
|
|
73
73
|
baseEnv?: NodeJS.ProcessEnv;
|
|
74
|
+
/**
|
|
75
|
+
* Whether this launch freshly collapsed reasoning (seeded `hideThinkingBlock`
|
|
76
|
+
* for the first time). Forwarded as `YAGNI_HIDE_THINKING_SEEDED=1` so the
|
|
77
|
+
* extension can surface a single one-time "reasoning is collapsed" hint.
|
|
78
|
+
* Omitted/false on repeat launches: the hint must not re-nag.
|
|
79
|
+
*/
|
|
80
|
+
hideThinkingSeeded?: boolean;
|
|
74
81
|
/** Clock seam for the token-expiry preflight (defaults to Date.now). */
|
|
75
82
|
now?: () => number;
|
|
76
83
|
/**
|
package/dist/launch.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { agentDirEnvVar } from "./branding.js";
|
|
10
|
+
import { PAD_X_ENV, resolvePadX } from "./padding.js";
|
|
11
|
+
import { ENGINEERING_PRACTICE_SECTION, promptEnrichmentDisabled } from "./promptEnrichment.js";
|
|
10
12
|
/**
|
|
11
13
|
* How close to expiry the token can be before launch warns. A coding session
|
|
12
14
|
* easily outlives a token, so warn early enough that re-logging in before a long
|
|
@@ -95,27 +97,37 @@ export function buildLaunch(creds, passthroughArgs, opts) {
|
|
|
95
97
|
// Match the e2b harness defaults: skip pi's update check + telemetry.
|
|
96
98
|
PI_SKIP_VERSION_CHECK: "1",
|
|
97
99
|
PI_TELEMETRY: "0",
|
|
100
|
+
// One-time marker for the "reasoning is collapsed" hint: only set on the
|
|
101
|
+
// launch that actually seeded the collapse default. Absent on every later
|
|
102
|
+
// launch so the hint never re-nags. (See hideThinkingSeeded in the docs.)
|
|
103
|
+
...(opts.hideThinkingSeeded ? { YAGNI_HIDE_THINKING_SEEDED: "1" } : {}),
|
|
104
|
+
// Forward the chosen horizontal pad so the extension's footer aligns with
|
|
105
|
+
// the editor (which the launcher pads by seeding editorPaddingX). The
|
|
106
|
+
// footer can't read pi's settings, so the value crosses over env.
|
|
107
|
+
[PAD_X_ENV]: String(resolvePadX()),
|
|
98
108
|
};
|
|
99
109
|
// Always load our extension. Default the provider to `yagni` unless the user
|
|
100
110
|
// explicitly chose one (so power users can still point pi elsewhere).
|
|
101
111
|
const userChoseProvider = passthroughArgs.some(arg => arg === "--provider" || arg.startsWith("--provider="));
|
|
102
|
-
// Default the model to the `
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
// can reason better. A user `--model` (e.g. `standard` or `efficient`) still
|
|
107
|
-
// wins. Without this, pi's default-model heuristic could land an interactive
|
|
108
|
-
// session on a weaker tier.
|
|
109
|
-
// Detect if the user explicitly set a model, whether via "--model" as a separate
|
|
110
|
-
// argument or using the equals form "--model=efficient". The previous check only
|
|
111
|
-
// caught the separate form, causing a duplicate "--model balanced" to be added
|
|
112
|
-
// when the equals form was used.
|
|
112
|
+
// Default the model to the `advanced` tier. The model is locked: the
|
|
113
|
+
// catalog is filtered to only `advanced` (see index.ts), so the user
|
|
114
|
+
// cannot switch to a different tier via /model or Ctrl+P. The proxy still
|
|
115
|
+
// resolves the tier to the concrete backing model.
|
|
113
116
|
const userChoseModel = passthroughArgs.some(arg => arg === "--model" || arg.startsWith("--model="));
|
|
117
|
+
// Engineering-practice enrichment (YAG-496): appended to the DRIVER's system
|
|
118
|
+
// prompt only — /go stage children and subagents build their own pi argv, so
|
|
119
|
+
// this flag never reaches them. pi treats the value as literal text (it is
|
|
120
|
+
// never an existing file path) and slots it before <project_context>, so the
|
|
121
|
+
// user's repo instructions still outrank it. The flag is repeatable, so a
|
|
122
|
+
// user-passed --append-system-prompt coexists rather than conflicting; the
|
|
123
|
+
// opt-out is YAGNI_DISABLE_PROMPT_ENRICHMENT.
|
|
124
|
+
const enrichmentOff = promptEnrichmentDisabled(opts.baseEnv ?? process.env);
|
|
114
125
|
const argv = [
|
|
115
126
|
"-e",
|
|
116
127
|
opts.extensionPath,
|
|
117
128
|
...(userChoseProvider ? [] : ["--provider", "yagni"]),
|
|
118
|
-
...(userChoseModel ? [] : ["--model", "
|
|
129
|
+
...(userChoseModel ? [] : ["--model", "advanced"]),
|
|
130
|
+
...(enrichmentOff ? [] : ["--append-system-prompt", ENGINEERING_PRACTICE_SECTION]),
|
|
119
131
|
...(opts.extraAgentArgs ?? []),
|
|
120
132
|
...passthroughArgs,
|
|
121
133
|
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the CLI's horizontal padding, shared by the
|
|
3
|
+
* launcher (which seeds the editor setting) and — via the `YAGNI_PAD_X` env the
|
|
4
|
+
* launcher forwards — pi-extension-yagni's footer.
|
|
5
|
+
*
|
|
6
|
+
* The value must match pi's `outputPad` (the chat/output area's left+right
|
|
7
|
+
* padding, which defaults to 1) so the input editor, the output stream, and
|
|
8
|
+
* the status bar all align on the same column. Keeping it in one module makes
|
|
9
|
+
* the coherence structural rather than eyeballed: change this one number and
|
|
10
|
+
* the editor, footer, and seeded default move together.
|
|
11
|
+
*
|
|
12
|
+
* Separators (the ─ rules above/below the input) intentionally stay full-bleed
|
|
13
|
+
* and are NOT padded — matching Claude Code's frame.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PAD_X = 1;
|
|
16
|
+
/** Left (and right) padding string applied to the editor and footer. */
|
|
17
|
+
export declare const PAD_STR: string;
|
|
18
|
+
/** Env var the launcher sets so the extension's footer can match the editor pad. */
|
|
19
|
+
export declare const PAD_X_ENV = "YAGNI_PAD_X";
|
|
20
|
+
/** Parse a pad width from the env override, falling back to {@link PAD_X}. */
|
|
21
|
+
export declare function resolvePadX(raw?: string | undefined): number;
|
|
22
|
+
//# sourceMappingURL=padding.d.ts.map
|
package/dist/padding.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the CLI's horizontal padding, shared by the
|
|
3
|
+
* launcher (which seeds the editor setting) and — via the `YAGNI_PAD_X` env the
|
|
4
|
+
* launcher forwards — pi-extension-yagni's footer.
|
|
5
|
+
*
|
|
6
|
+
* The value must match pi's `outputPad` (the chat/output area's left+right
|
|
7
|
+
* padding, which defaults to 1) so the input editor, the output stream, and
|
|
8
|
+
* the status bar all align on the same column. Keeping it in one module makes
|
|
9
|
+
* the coherence structural rather than eyeballed: change this one number and
|
|
10
|
+
* the editor, footer, and seeded default move together.
|
|
11
|
+
*
|
|
12
|
+
* Separators (the ─ rules above/below the input) intentionally stay full-bleed
|
|
13
|
+
* and are NOT padded — matching Claude Code's frame.
|
|
14
|
+
*/
|
|
15
|
+
export const PAD_X = 1;
|
|
16
|
+
/** Left (and right) padding string applied to the editor and footer. */
|
|
17
|
+
export const PAD_STR = " ".repeat(PAD_X);
|
|
18
|
+
/** Env var the launcher sets so the extension's footer can match the editor pad. */
|
|
19
|
+
export const PAD_X_ENV = "YAGNI_PAD_X";
|
|
20
|
+
/** Parse a pad width from the env override, falling back to {@link PAD_X}. */
|
|
21
|
+
export function resolvePadX(raw = process.env[PAD_X_ENV]) {
|
|
22
|
+
const n = raw === undefined ? NaN : Number.parseInt(raw, 10);
|
|
23
|
+
return Number.isFinite(n) && n >= 0 && n <= 3 ? n : PAD_X;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=padding.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engineering-practice prompt enrichment (YAG-496).
|
|
3
|
+
*
|
|
4
|
+
* pi's own base system prompt is deliberately thin (~15 lines: identity, tool
|
|
5
|
+
* one-liners, two generic guidelines). The extension adds identity, grounding,
|
|
6
|
+
* and product-tool guidance — but nothing about general engineering behavior:
|
|
7
|
+
* conventions, verification, commit discipline, communication style. This
|
|
8
|
+
* section carries that missing middle, curated from the prompt lineage shared
|
|
9
|
+
* by OpenCode/Claude Code/Kimi CLI and adapted to YAGNI Code's tools and
|
|
10
|
+
* autonomy posture.
|
|
11
|
+
*
|
|
12
|
+
* Delivery mechanism: pi's `--append-system-prompt <text>` flag, injected by
|
|
13
|
+
* the launcher (launch.ts). pi places appended text after its Guidelines and
|
|
14
|
+
* BEFORE the user's <project_context>, so repo instructions still outrank it.
|
|
15
|
+
* Because `/go` stage children and subagents build their own pi argv
|
|
16
|
+
* (pi-extension-yagni's pipeline/invocation.ts and pipeline/runner.ts), a
|
|
17
|
+
* launcher flag is structurally driver-only: child prompts are untouched by
|
|
18
|
+
* construction, not by an `if`. The piContract test pins both properties.
|
|
19
|
+
*
|
|
20
|
+
* Content constraints (enforced by test/promptEnrichment.test.ts):
|
|
21
|
+
* - must not contain the standalone word "pi" — the extension's brand scrub
|
|
22
|
+
* rewrites it to "YAGNI Code" and would mangle the sentence;
|
|
23
|
+
* - must not OPEN with a "- " bullet line — pi's self-referential docs block
|
|
24
|
+
* (which the extension strips with a regex that consumes consecutive
|
|
25
|
+
* bullets) sits directly above this section in the assembled prompt;
|
|
26
|
+
* - no emojis (the section itself forbids them).
|
|
27
|
+
*/
|
|
28
|
+
/** Env switch that skips the enrichment entirely (sibling of
|
|
29
|
+
* YAGNI_DISABLE_BRANDING / YAGNI_DISABLE_CLAUDE_COMPAT, same semantics). */
|
|
30
|
+
export declare const ENRICHMENT_DISABLE_ENV = "YAGNI_DISABLE_PROMPT_ENRICHMENT";
|
|
31
|
+
/** `"1"`/anything truthy disables; unset, empty, and `"0"` keep it on. */
|
|
32
|
+
export declare function promptEnrichmentDisabled(env: NodeJS.ProcessEnv): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The section appended to the driver session's system prompt. Keep it curated,
|
|
35
|
+
* not encyclopedic: every line here spends attention budget on an open-weight
|
|
36
|
+
* model, and the load-bearing instructions (ask_yagni contract, delegation)
|
|
37
|
+
* live elsewhere in the prompt.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ENGINEERING_PRACTICE_SECTION = "Engineering practice:\n\nBias to action: when the user asks you to implement, fix, or change something, use your tools to make the actual edits and run the actual commands \u2014 do not answer with a description of what you would do, or with code for the user to apply themselves. When the user asks HOW to approach something, answer the question first; do not jump into making changes they have not asked for.\n\nConventions:\n- Never assume a library is available, however well known. Before using one, confirm the project already depends on it (its package manifest, or imports in neighboring files).\n- When editing, read the surrounding code and its imports first; match the file's existing style, naming, and patterns rather than introducing your own.\n- When creating a new file or component, study an existing sibling first and follow its structure.\n- Never write code that logs or exposes secrets, keys, or credentials.\n\nVerification:\n- Consider what the code you are changing is supposed to do (from its name, location, and callers) before you change it.\n- Verify changes with the project's own tests when possible. Never assume a test framework or command \u2014 check the README, package scripts, or neighboring tests for the real one.\n- After completing a task, run the project's lint and typecheck commands if you know them; if you cannot find them, ask the user and suggest recording them in AGENTS.md for next time.\n\nVersion control:\n- No unsolicited commits: commit only when the user asked for one or the task at hand clearly calls for it.\n\nGit safety:\n- You may be in a dirty git worktree. Never revert existing changes you did not make unless explicitly asked \u2014 these were made by the user.\n- If there are unrelated changes in files you are touching, read and work with them rather than reverting.\n- If changes appear in unrelated files, ignore them and do not revert.\n- Do not amend a commit unless explicitly asked.\n- If you notice unexpected changes you did not make while working, stop immediately and ask the user.\n- Never use destructive git commands (git reset --hard, git checkout --) unless the user explicitly requests or approves them.\n\nTodo discipline:\n- Track multi-step work with todo_write: keep exactly one item in_progress at a time, mark items completed the moment they are done, and add newly discovered steps as pending.\n- Do not batch-complete items or create single-step plans. Skip planning for trivially small work (~25% of tasks).\n\nMode awareness:\n- In auto mode, proactively run tests, lint, and typecheck after your changes.\n- In review mode, propose verification steps but wait for approval before running them.\n- In plan mode, explore and design only \u2014 the gate holds all writes.\n\nCommunication:\n- Answer directly, without preamble or postamble (\"Here is what I will do next...\", \"Based on the information provided...\"). Match the length of your answer to the question.\n- After making edits, report the outcome briefly; do not restate the diff or explain the code you just wrote unless asked.\n- Do not add code comments that narrate what you changed or why the change is correct; comments are for future readers of the code.\n- Reference code as file_path:line_number so the user can jump to it.\n- Before running a non-trivial command that changes state, say in one line what it does and why.\n- Never guess or fabricate URLs. Only use URLs the user provided or that appear in local files.\n- No emojis unless the user asks for them.";
|
|
40
|
+
//# sourceMappingURL=promptEnrichment.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engineering-practice prompt enrichment (YAG-496).
|
|
3
|
+
*
|
|
4
|
+
* pi's own base system prompt is deliberately thin (~15 lines: identity, tool
|
|
5
|
+
* one-liners, two generic guidelines). The extension adds identity, grounding,
|
|
6
|
+
* and product-tool guidance — but nothing about general engineering behavior:
|
|
7
|
+
* conventions, verification, commit discipline, communication style. This
|
|
8
|
+
* section carries that missing middle, curated from the prompt lineage shared
|
|
9
|
+
* by OpenCode/Claude Code/Kimi CLI and adapted to YAGNI Code's tools and
|
|
10
|
+
* autonomy posture.
|
|
11
|
+
*
|
|
12
|
+
* Delivery mechanism: pi's `--append-system-prompt <text>` flag, injected by
|
|
13
|
+
* the launcher (launch.ts). pi places appended text after its Guidelines and
|
|
14
|
+
* BEFORE the user's <project_context>, so repo instructions still outrank it.
|
|
15
|
+
* Because `/go` stage children and subagents build their own pi argv
|
|
16
|
+
* (pi-extension-yagni's pipeline/invocation.ts and pipeline/runner.ts), a
|
|
17
|
+
* launcher flag is structurally driver-only: child prompts are untouched by
|
|
18
|
+
* construction, not by an `if`. The piContract test pins both properties.
|
|
19
|
+
*
|
|
20
|
+
* Content constraints (enforced by test/promptEnrichment.test.ts):
|
|
21
|
+
* - must not contain the standalone word "pi" — the extension's brand scrub
|
|
22
|
+
* rewrites it to "YAGNI Code" and would mangle the sentence;
|
|
23
|
+
* - must not OPEN with a "- " bullet line — pi's self-referential docs block
|
|
24
|
+
* (which the extension strips with a regex that consumes consecutive
|
|
25
|
+
* bullets) sits directly above this section in the assembled prompt;
|
|
26
|
+
* - no emojis (the section itself forbids them).
|
|
27
|
+
*/
|
|
28
|
+
/** Env switch that skips the enrichment entirely (sibling of
|
|
29
|
+
* YAGNI_DISABLE_BRANDING / YAGNI_DISABLE_CLAUDE_COMPAT, same semantics). */
|
|
30
|
+
export const ENRICHMENT_DISABLE_ENV = "YAGNI_DISABLE_PROMPT_ENRICHMENT";
|
|
31
|
+
/** `"1"`/anything truthy disables; unset, empty, and `"0"` keep it on. */
|
|
32
|
+
export function promptEnrichmentDisabled(env) {
|
|
33
|
+
const value = env[ENRICHMENT_DISABLE_ENV];
|
|
34
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The section appended to the driver session's system prompt. Keep it curated,
|
|
38
|
+
* not encyclopedic: every line here spends attention budget on an open-weight
|
|
39
|
+
* model, and the load-bearing instructions (ask_yagni contract, delegation)
|
|
40
|
+
* live elsewhere in the prompt.
|
|
41
|
+
*/
|
|
42
|
+
export const ENGINEERING_PRACTICE_SECTION = `Engineering practice:
|
|
43
|
+
|
|
44
|
+
Bias to action: when the user asks you to implement, fix, or change something, use your tools to make the actual edits and run the actual commands — do not answer with a description of what you would do, or with code for the user to apply themselves. When the user asks HOW to approach something, answer the question first; do not jump into making changes they have not asked for.
|
|
45
|
+
|
|
46
|
+
Conventions:
|
|
47
|
+
- Never assume a library is available, however well known. Before using one, confirm the project already depends on it (its package manifest, or imports in neighboring files).
|
|
48
|
+
- When editing, read the surrounding code and its imports first; match the file's existing style, naming, and patterns rather than introducing your own.
|
|
49
|
+
- When creating a new file or component, study an existing sibling first and follow its structure.
|
|
50
|
+
- Never write code that logs or exposes secrets, keys, or credentials.
|
|
51
|
+
|
|
52
|
+
Verification:
|
|
53
|
+
- Consider what the code you are changing is supposed to do (from its name, location, and callers) before you change it.
|
|
54
|
+
- Verify changes with the project's own tests when possible. Never assume a test framework or command — check the README, package scripts, or neighboring tests for the real one.
|
|
55
|
+
- After completing a task, run the project's lint and typecheck commands if you know them; if you cannot find them, ask the user and suggest recording them in AGENTS.md for next time.
|
|
56
|
+
|
|
57
|
+
Version control:
|
|
58
|
+
- No unsolicited commits: commit only when the user asked for one or the task at hand clearly calls for it.
|
|
59
|
+
|
|
60
|
+
Git safety:
|
|
61
|
+
- You may be in a dirty git worktree. Never revert existing changes you did not make unless explicitly asked — these were made by the user.
|
|
62
|
+
- If there are unrelated changes in files you are touching, read and work with them rather than reverting.
|
|
63
|
+
- If changes appear in unrelated files, ignore them and do not revert.
|
|
64
|
+
- Do not amend a commit unless explicitly asked.
|
|
65
|
+
- If you notice unexpected changes you did not make while working, stop immediately and ask the user.
|
|
66
|
+
- Never use destructive git commands (git reset --hard, git checkout --) unless the user explicitly requests or approves them.
|
|
67
|
+
|
|
68
|
+
Todo discipline:
|
|
69
|
+
- Track multi-step work with todo_write: keep exactly one item in_progress at a time, mark items completed the moment they are done, and add newly discovered steps as pending.
|
|
70
|
+
- Do not batch-complete items or create single-step plans. Skip planning for trivially small work (~25% of tasks).
|
|
71
|
+
|
|
72
|
+
Mode awareness:
|
|
73
|
+
- In auto mode, proactively run tests, lint, and typecheck after your changes.
|
|
74
|
+
- In review mode, propose verification steps but wait for approval before running them.
|
|
75
|
+
- In plan mode, explore and design only — the gate holds all writes.
|
|
76
|
+
|
|
77
|
+
Communication:
|
|
78
|
+
- Answer directly, without preamble or postamble ("Here is what I will do next...", "Based on the information provided..."). Match the length of your answer to the question.
|
|
79
|
+
- After making edits, report the outcome briefly; do not restate the diff or explain the code you just wrote unless asked.
|
|
80
|
+
- Do not add code comments that narrate what you changed or why the change is correct; comments are for future readers of the code.
|
|
81
|
+
- Reference code as file_path:line_number so the user can jump to it.
|
|
82
|
+
- Before running a non-trivial command that changes state, say in one line what it does and why.
|
|
83
|
+
- Never guess or fabricate URLs. Only use URLs the user provided or that appear in local files.
|
|
84
|
+
- No emojis unless the user asks for them.`;
|
|
85
|
+
//# sourceMappingURL=promptEnrichment.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Termination-signal forwarding: launcher → pi child.
|
|
3
|
+
*
|
|
4
|
+
* In the interactive TUI the tty is in raw mode, so Ctrl+C never raises
|
|
5
|
+
* SIGINT; this path covers `yagni -p`/piped/CI runs and an external
|
|
6
|
+
* `kill <launcher pid>`. Policy:
|
|
7
|
+
*
|
|
8
|
+
* - The FIRST signal asks pi to shut down cleanly. SIGINT is translated to
|
|
9
|
+
* SIGTERM because pi installs a graceful handler for SIGTERM (it kills its
|
|
10
|
+
* tracked children) but none for SIGINT — a raw SIGINT would drop pi on the
|
|
11
|
+
* default disposition with no cleanup.
|
|
12
|
+
* - Any SECOND signal hard-kills pi's whole process TREE. A bare SIGKILL to
|
|
13
|
+
* pi's pid would bypass its exit sweep (SIGKILL runs no handlers) and leave
|
|
14
|
+
* /go workers alive, so the launcher enumerates and kills the descendants
|
|
15
|
+
* itself: `ps` walk on POSIX, `taskkill /T /F` on Windows.
|
|
16
|
+
*/
|
|
17
|
+
/** The minimal child surface the forwarder needs (ChildProcess satisfies it). */
|
|
18
|
+
export interface KillableChild {
|
|
19
|
+
pid?: number | undefined;
|
|
20
|
+
kill(signal: NodeJS.Signals): boolean;
|
|
21
|
+
}
|
|
22
|
+
/** The launcher-terminating signals that are forwarded rather than obeyed. */
|
|
23
|
+
export declare const FORWARDED_SIGNALS: readonly NodeJS.Signals[];
|
|
24
|
+
/**
|
|
25
|
+
* PURE policy for the FIRST signal: which signal the child receives when the
|
|
26
|
+
* launcher gets `received` (SIGINT is translated, the rest pass through).
|
|
27
|
+
* Escalation is not expressed here — a second signal goes through the tree
|
|
28
|
+
* kill, not a forwarded signal.
|
|
29
|
+
*/
|
|
30
|
+
export declare function forwardedSignal(received: NodeJS.Signals): NodeJS.Signals;
|
|
31
|
+
/**
|
|
32
|
+
* PURE: the launcher's own exit code for the child's (code, signal) exit tuple.
|
|
33
|
+
* A signal-terminated child maps to the conventional 128+n (bash parity:
|
|
34
|
+
* SIGTERM→143, SIGKILL→137, SIGINT→130), so a cancelled run never reads as
|
|
35
|
+
* success to scripts or CI.
|
|
36
|
+
*/
|
|
37
|
+
export declare function exitCodeFor(code: number | null, signal: NodeJS.Signals | null): number;
|
|
38
|
+
/**
|
|
39
|
+
* PURE: given `ps -A -o pid=,ppid=` output lines, collect every descendant of
|
|
40
|
+
* the given roots (children, grandchildren, ...), breadth-first. Mirrored in
|
|
41
|
+
* pi-extension-yagni's childRegistry (the packages are intentionally
|
|
42
|
+
* independent); keep the two in sync.
|
|
43
|
+
*/
|
|
44
|
+
export declare function descendantsOf(roots: number[], psLines: string[]): number[];
|
|
45
|
+
/**
|
|
46
|
+
* Synchronously SIGKILL a process AND its descendants (POSIX `ps` walk /
|
|
47
|
+
* Windows `taskkill /T /F`). Throw-proof; fail-soft to a direct kill when no
|
|
48
|
+
* process snapshot is readable.
|
|
49
|
+
*/
|
|
50
|
+
export declare function killTreeSync(pid: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Subscribe the forwarding policy for every signal in {@link FORWARDED_SIGNALS}.
|
|
53
|
+
* `subscribe` and `killTree` default to the real process surfaces and are
|
|
54
|
+
* injectable so tests never install real handlers, raise real signals, or kill
|
|
55
|
+
* real processes. Returns the forward function itself (also for tests).
|
|
56
|
+
* pid guard: a child whose spawn failed has no pid, and `kill()` would resolve
|
|
57
|
+
* that to the whole process group.
|
|
58
|
+
*/
|
|
59
|
+
export declare function installSignalForwarding(child: KillableChild, subscribe?: (sig: NodeJS.Signals, handler: () => void) => void, killTree?: (pid: number) => void): (sig: NodeJS.Signals) => void;
|
|
60
|
+
//# sourceMappingURL=signalForward.d.ts.map
|