@yagni-app/code-staging 1.1.1-staging.1347.1 → 1.1.1-staging.1355.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 +3 -0
- package/dist/bin.d.ts +24 -0
- package/dist/bin.js +73 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +11 -3
- package/dist/crashReport.d.ts +7 -0
- package/dist/crashReport.js +10 -1
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.js +21 -0
- package/dist/extension/branding.d.ts +1 -1
- package/dist/extension/branding.js +1 -1
- package/dist/extension/crashReport.d.ts +7 -0
- package/dist/extension/crashReport.js +11 -2
- package/dist/extension/index.js +10 -7
- package/dist/extension/permission/gate.d.ts +1 -1
- package/dist/extension/permission/gate.js +1 -1
- package/dist/extension/silentTurnReminder.js +1 -0
- package/dist/extension/todos.d.ts +97 -15
- package/dist/extension/todos.js +240 -57
- package/dist/extension/workingLine.d.ts +7 -0
- package/dist/extension/workingLine.js +7 -1
- package/dist/nodeVersion.d.ts +53 -0
- package/dist/nodeVersion.js +79 -0
- package/dist/promptEnrichment.d.ts +1 -1
- package/dist/promptEnrichment.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -23,6 +23,9 @@ correct, autonomous work than a coding agent that starts blank.
|
|
|
23
23
|
npm install -g @yagni-app/code
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Requires Node.js 22.19 or newer (`node --version`); an older Node stops at
|
|
27
|
+
launch with an upgrade message instead of crashing mid-session.
|
|
28
|
+
|
|
26
29
|
The command it installs is `yagni`:
|
|
27
30
|
|
|
28
31
|
```bash
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* yagni — the published bin entry.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately tiny: the only static imports are the dependency-free Node
|
|
6
|
+
* version gate and the (equally dependency-free) distribution record that
|
|
7
|
+
* names the installed channel. Everything else (`cli.js` and the module graph
|
|
8
|
+
* behind it) is loaded dynamically AFTER the check passes, so an old Node
|
|
9
|
+
* prints one clear message and exits instead of blowing up inside a
|
|
10
|
+
* dependency — or failing to parse one. `cli.js` stays directly runnable
|
|
11
|
+
* (`node dist/cli.js`) for the e2e lanes; its own entrypoint guard is false
|
|
12
|
+
* when this shim is argv[1], so the shim calls `runAsEntrypoint()` explicitly.
|
|
13
|
+
*
|
|
14
|
+
* Both failure paths set `process.exitCode` and return instead of calling
|
|
15
|
+
* `process.exit()`: stderr on a pipe is asynchronous on Windows, and an
|
|
16
|
+
* immediate exit can truncate the very diagnostic this shim exists to print.
|
|
17
|
+
* Letting the event loop drain flushes it.
|
|
18
|
+
*
|
|
19
|
+
* Behavioral coverage lives in `test/bin.test.ts`, which spawns this file
|
|
20
|
+
* under a faked-old `process.versions.node` and against a stub launcher that
|
|
21
|
+
* fails to load.
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=bin.d.ts.map
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* yagni — the published bin entry.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately tiny: the only static imports are the dependency-free Node
|
|
6
|
+
* version gate and the (equally dependency-free) distribution record that
|
|
7
|
+
* names the installed channel. Everything else (`cli.js` and the module graph
|
|
8
|
+
* behind it) is loaded dynamically AFTER the check passes, so an old Node
|
|
9
|
+
* prints one clear message and exits instead of blowing up inside a
|
|
10
|
+
* dependency — or failing to parse one. `cli.js` stays directly runnable
|
|
11
|
+
* (`node dist/cli.js`) for the e2e lanes; its own entrypoint guard is false
|
|
12
|
+
* when this shim is argv[1], so the shim calls `runAsEntrypoint()` explicitly.
|
|
13
|
+
*
|
|
14
|
+
* Both failure paths set `process.exitCode` and return instead of calling
|
|
15
|
+
* `process.exit()`: stderr on a pipe is asynchronous on Windows, and an
|
|
16
|
+
* immediate exit can truncate the very diagnostic this shim exists to print.
|
|
17
|
+
* Letting the event loop drain flushes it.
|
|
18
|
+
*
|
|
19
|
+
* Behavioral coverage lives in `test/bin.test.ts`, which spawns this file
|
|
20
|
+
* under a faked-old `process.versions.node` and against a stub launcher that
|
|
21
|
+
* fails to load.
|
|
22
|
+
*/
|
|
23
|
+
import { DISTRIBUTION } from "./distribution.js";
|
|
24
|
+
import { nodeCheckSkipped, nodeVersionProblem } from "./nodeVersion.js";
|
|
25
|
+
function debugEnabled() {
|
|
26
|
+
const value = process.env.YAGNI_DEBUG;
|
|
27
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
28
|
+
}
|
|
29
|
+
async function main() {
|
|
30
|
+
const problem = nodeCheckSkipped() ? null : nodeVersionProblem(process.versions.node, DISTRIBUTION);
|
|
31
|
+
if (problem !== null) {
|
|
32
|
+
process.stderr.write(`${problem}\n`);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// A launcher that fails to LOAD (corrupt or partial install, a dependency
|
|
37
|
+
// missing from node_modules) would otherwise die as a raw unhandled
|
|
38
|
+
// rejection before the launcher's crash handlers exist — the same opaque
|
|
39
|
+
// failure this shim is here to prevent. No crash report is possible at this
|
|
40
|
+
// point (the reporter is part of what failed to load), so say what to do
|
|
41
|
+
// instead. The message carries the error class and message; YAGNI_DEBUG=1
|
|
42
|
+
// adds the full stack (resolution chain, parse location) for a support thread.
|
|
43
|
+
let launcher;
|
|
44
|
+
try {
|
|
45
|
+
launcher = await import("./cli.js");
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const name = err instanceof Error && err.name ? err.name : "Error";
|
|
49
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50
|
+
const lines = [
|
|
51
|
+
`${DISTRIBUTION.displayName} failed to load: ${name}: ${message}`,
|
|
52
|
+
"",
|
|
53
|
+
"The install looks incomplete or corrupt. Reinstall it:",
|
|
54
|
+
"",
|
|
55
|
+
` npm install -g ${DISTRIBUTION.packageName}`,
|
|
56
|
+
"",
|
|
57
|
+
];
|
|
58
|
+
if (debugEnabled()) {
|
|
59
|
+
const stack = err instanceof Error && err.stack ? err.stack : "(no stack)";
|
|
60
|
+
const cause = err instanceof Error && err.cause !== undefined ? `\ncause: ${String(err.cause)}` : "";
|
|
61
|
+
lines.push(`${stack}${cause}`, "");
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
lines.push("(YAGNI_DEBUG=1 prints the full stack.)", "");
|
|
65
|
+
}
|
|
66
|
+
process.stderr.write(lines.join("\n"));
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
launcher.runAsEntrypoint();
|
|
71
|
+
}
|
|
72
|
+
await main();
|
|
73
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/cli.d.ts
CHANGED
|
@@ -166,5 +166,11 @@ export declare function main(argv: string[]): Promise<number>;
|
|
|
166
166
|
* test import (argv[1] points at the test runner) does not.
|
|
167
167
|
*/
|
|
168
168
|
export declare function isEntrypoint(argv1: string | undefined, moduleUrl: string): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Run the launcher as the process entrypoint. Called by the published bin
|
|
171
|
+
* shim (`bin.js`, after the Node version gate) and by the guard below when
|
|
172
|
+
* this module is executed directly (`node dist/cli.js`, the e2e lanes).
|
|
173
|
+
*/
|
|
174
|
+
export declare function runAsEntrypoint(): void;
|
|
169
175
|
export {};
|
|
170
176
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.js
CHANGED
|
@@ -889,9 +889,12 @@ export function isEntrypoint(argv1, moduleUrl) {
|
|
|
889
889
|
};
|
|
890
890
|
return resolve(argv1) === resolve(fileURLToPath(moduleUrl));
|
|
891
891
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
892
|
+
/**
|
|
893
|
+
* Run the launcher as the process entrypoint. Called by the published bin
|
|
894
|
+
* shim (`bin.js`, after the Node version gate) and by the guard below when
|
|
895
|
+
* this module is executed directly (`node dist/cli.js`, the e2e lanes).
|
|
896
|
+
*/
|
|
897
|
+
export function runAsEntrypoint() {
|
|
895
898
|
// Crash reporting for the LAUNCHER process only (pi runs as a child and the
|
|
896
899
|
// extension covers the session side). Fire-and-forget, sanitized, bounded;
|
|
897
900
|
// YAGNI_DISABLE_CRASH_REPORTS=1 turns it off. Registered before main() so a
|
|
@@ -904,4 +907,9 @@ if (isEntrypoint(process.argv[1], import.meta.url)) {
|
|
|
904
907
|
process.exit(1);
|
|
905
908
|
});
|
|
906
909
|
}
|
|
910
|
+
// Only auto-run when invoked as the CLI entry, so tests can import this module
|
|
911
|
+
// (e.g. to exercise wantsHelp) without spawning the agent.
|
|
912
|
+
if (isEntrypoint(process.argv[1], import.meta.url)) {
|
|
913
|
+
runAsEntrypoint();
|
|
914
|
+
}
|
|
907
915
|
//# sourceMappingURL=cli.js.map
|
package/dist/crashReport.d.ts
CHANGED
|
@@ -77,6 +77,13 @@ export interface SanitizedCrash {
|
|
|
77
77
|
* payloads…) are never touched.
|
|
78
78
|
*/
|
|
79
79
|
export declare function sanitizeCrashError(err: unknown, opts?: SanitizeCrashOptions): SanitizedCrash;
|
|
80
|
+
/**
|
|
81
|
+
* OS, arch AND the Node version. The runtime is a first-class crash cause
|
|
82
|
+
* (an old Node dies inside undici on `zlib.createZstdDecompress`), and it
|
|
83
|
+
* rides the existing `platform` field so the backend and its Sentry tag need
|
|
84
|
+
* no change. Mirrored in `pi-extension-yagni/src/crashReport.ts`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function platformLabel(): string;
|
|
80
87
|
export type CrashClient = "cli" | "desktop" | "desktop-driver";
|
|
81
88
|
export interface CrashReportInput {
|
|
82
89
|
client: CrashClient;
|
package/dist/crashReport.js
CHANGED
|
@@ -177,6 +177,15 @@ export function sanitizeCrashError(err, opts = {}) {
|
|
|
177
177
|
...(cappedStack !== undefined ? { stack: cappedStack } : {}),
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* OS, arch AND the Node version. The runtime is a first-class crash cause
|
|
182
|
+
* (an old Node dies inside undici on `zlib.createZstdDecompress`), and it
|
|
183
|
+
* rides the existing `platform` field so the backend and its Sentry tag need
|
|
184
|
+
* no change. Mirrored in `pi-extension-yagni/src/crashReport.ts`.
|
|
185
|
+
*/
|
|
186
|
+
export function platformLabel() {
|
|
187
|
+
return `${process.platform} ${process.arch} node ${process.version}`;
|
|
188
|
+
}
|
|
180
189
|
/**
|
|
181
190
|
* Sanitize + POST one crash report from pre-extracted fields. Resolves on
|
|
182
191
|
* every outcome — timeout, network error, non-2xx, disabled — and never
|
|
@@ -199,7 +208,7 @@ export async function sendCrashReport(input) {
|
|
|
199
208
|
const payload = {
|
|
200
209
|
client: input.client,
|
|
201
210
|
clientVersion: input.clientVersion,
|
|
202
|
-
platform:
|
|
211
|
+
platform: platformLabel(),
|
|
203
212
|
errorClass: sanitizeCrashText(input.errorClass, opts).slice(0, MAX_CRASH_ERROR_CLASS),
|
|
204
213
|
message: sanitizeCrashText(input.message, opts).slice(0, MAX_CRASH_MESSAGE),
|
|
205
214
|
...(stack !== undefined ? { stack } : {}),
|
package/dist/doctor.d.ts
CHANGED
|
@@ -50,6 +50,13 @@ export type BackendProbe = {
|
|
|
50
50
|
} | {
|
|
51
51
|
kind: "network";
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* The Node floor, first in the list because every other check is moot
|
|
55
|
+
* without it: an old Node dies inside pi's HTTP client mid-request (Sentry
|
|
56
|
+
* YAGNI-BACKEND-4S: `zlib.createZstdDecompress is not a function`). The bin
|
|
57
|
+
* shim refuses to launch below the floor; doctor explains it in the same terms.
|
|
58
|
+
*/
|
|
59
|
+
export declare function checkNodeVersion(version: string): CheckResult;
|
|
53
60
|
export declare function checkPiEngine(probe: PiEngineProbe): CheckResult;
|
|
54
61
|
export declare function checkExtension(probe: ExtensionProbe): CheckResult;
|
|
55
62
|
export declare function checkProfileToken(profile: Pick<Profile, "name" | "token">): CheckResult;
|
|
@@ -115,6 +122,8 @@ export declare function buildDoctorReport(checks: CheckResult[]): DoctorReport;
|
|
|
115
122
|
export declare function formatDoctorReport(report: DoctorReport): string;
|
|
116
123
|
export interface DoctorDeps {
|
|
117
124
|
now?: () => number;
|
|
125
|
+
/** Running Node version (defaults to process.versions.node). */
|
|
126
|
+
nodeVersion?: string;
|
|
118
127
|
probePiEngine?: () => PiEngineProbe;
|
|
119
128
|
probeExtension?: () => ExtensionProbe;
|
|
120
129
|
readActiveProfile?: () => Promise<Profile>;
|
package/dist/doctor.js
CHANGED
|
@@ -21,7 +21,27 @@ import { otelChildEnv, resolveOtelLaunchWithWorkspace } from "./otel.js";
|
|
|
21
21
|
import { resolveExtensionPath, resolvePiCliPath, resolvePiPackageDir, resolveTelemetryProbePath } from "./paths.js";
|
|
22
22
|
import { readActiveProfile } from "./profiles.js";
|
|
23
23
|
import { resolveMcpConfigPath } from "./mcpCommand.js";
|
|
24
|
+
import { MIN_NODE_VERSION, nodeVersionSatisfies } from "./nodeVersion.js";
|
|
24
25
|
// ── Pure check builders ─────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* The Node floor, first in the list because every other check is moot
|
|
28
|
+
* without it: an old Node dies inside pi's HTTP client mid-request (Sentry
|
|
29
|
+
* YAGNI-BACKEND-4S: `zlib.createZstdDecompress is not a function`). The bin
|
|
30
|
+
* shim refuses to launch below the floor; doctor explains it in the same terms.
|
|
31
|
+
*/
|
|
32
|
+
export function checkNodeVersion(version) {
|
|
33
|
+
const shown = version.startsWith("v") ? version : `v${version}`;
|
|
34
|
+
if (!nodeVersionSatisfies(version, MIN_NODE_VERSION)) {
|
|
35
|
+
return {
|
|
36
|
+
name: "node",
|
|
37
|
+
status: "fail",
|
|
38
|
+
detail: `${shown} is older than the ${MIN_NODE_VERSION} floor`,
|
|
39
|
+
hint: "upgrade Node.js (https://nodejs.org or `nvm install 22`); older Node crashes mid-request on missing zlib APIs",
|
|
40
|
+
required: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { name: "node", status: "ok", detail: `${shown} (needs ${MIN_NODE_VERSION}+)`, required: true };
|
|
44
|
+
}
|
|
25
45
|
export function checkPiEngine(probe) {
|
|
26
46
|
if (!probe.binPath || !probe.binExists) {
|
|
27
47
|
return {
|
|
@@ -512,6 +532,7 @@ export async function gatherChecks(deps = {}) {
|
|
|
512
532
|
const probeBash = deps.probeBash ?? (() => bashOnWindowsDefault());
|
|
513
533
|
const probeLatestVersion = deps.probeLatestVersion ?? (() => fetchLatestVersion());
|
|
514
534
|
const checks = [];
|
|
535
|
+
checks.push(checkNodeVersion(deps.nodeVersion ?? process.versions.node));
|
|
515
536
|
checks.push(checkPiEngine(probePiEngine()));
|
|
516
537
|
checks.push(checkExtension(probeExtension()));
|
|
517
538
|
// win32 only, and skipped means NOT SHOWN: on macOS/Linux there is nothing
|
|
@@ -99,7 +99,7 @@ export declare const COMMUNICATION_CONTRACT: string;
|
|
|
99
99
|
*/
|
|
100
100
|
export declare const WRITE_FINDINGS_DOWN: string;
|
|
101
101
|
/** The driver identity while /ultra is on: base identity + the diamond directive. */
|
|
102
|
-
export declare const YAGNI_IDENTITY_ULTRA = "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 (ultra mode): the user has switched this session to ultra mode \u2014 aggressive multi-agent orchestration. Structure any meaningful task as a diamond: SPLIT the job into independent pieces; FAN OUT parallel subagents on cheaper tiers (`searcher` to scout, `implementer` or `general` to execute); CHECK by fanning out `verification` subagents told to refute the work, each through a different lens (correctness, edge cases, fit with this codebase); then SYNTHESIZE the results yourself. Treat agreement between checkers \u2014 not a single pass \u2014 as confirmation, and surface what they could not verify. Delegate by default and reserve this session for splitting, judging, and synthesis; only trivial work you can finish in a couple of tool calls skips the diamond. Subagents cannot touch your
|
|
102
|
+
export declare const YAGNI_IDENTITY_ULTRA = "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 (ultra mode): the user has switched this session to ultra mode \u2014 aggressive multi-agent orchestration. Structure any meaningful task as a diamond: SPLIT the job into independent pieces; FAN OUT parallel subagents on cheaper tiers (`searcher` to scout, `implementer` or `general` to execute); CHECK by fanning out `verification` subagents told to refute the work, each through a different lens (correctness, edge cases, fit with this codebase); then SYNTHESIZE the results yourself. Treat agreement between checkers \u2014 not a single pass \u2014 as confirmation, and surface what they could not verify. Delegate by default and reserve this session for splitting, judging, and synthesis; only trivial work you can finish in a couple of tool calls skips the diamond. Subagents cannot touch your TodoWrite checklist, so keep it current yourself: update it when you split the job and again as each fanned-out piece lands, not only at the end.";
|
|
103
103
|
export declare const PI_IDENTITY_RE: RegExp;
|
|
104
104
|
/**
|
|
105
105
|
* Env switch that bypasses the system-prompt rewrite entirely, so pi's
|
|
@@ -65,7 +65,7 @@ export const ULTRA_DELEGATION_PARAGRAPH = "Delegation (ultra mode): the user has
|
|
|
65
65
|
"surface what they could not verify. Delegate by default and reserve this " +
|
|
66
66
|
"session for splitting, judging, and synthesis; only trivial work you can " +
|
|
67
67
|
"finish in a couple of tool calls skips the diamond. Subagents cannot touch " +
|
|
68
|
-
"your
|
|
68
|
+
"your TodoWrite checklist, so keep it current yourself: update it when you " +
|
|
69
69
|
"split the job and again as each fanned-out piece lands, not only at the end.";
|
|
70
70
|
/**
|
|
71
71
|
* The identity used for the interactive DRIVER session ONLY: {@link
|
|
@@ -72,6 +72,13 @@ export interface CrashReporterOpts {
|
|
|
72
72
|
timeoutMs?: number;
|
|
73
73
|
}
|
|
74
74
|
export type CrashReporter = (error: unknown, context?: string, repoRoot?: string) => Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* OS, arch AND the Node version: the runtime is a first-class crash cause (an
|
|
77
|
+
* old Node dies inside undici on `zlib.createZstdDecompress`), and it rides
|
|
78
|
+
* the existing `platform` field so the backend and its Sentry tag need no
|
|
79
|
+
* change. Mirrored in `yagni-code-cli/src/crashReport.ts`.
|
|
80
|
+
*/
|
|
81
|
+
export declare function platformLabel(): string;
|
|
75
82
|
/**
|
|
76
83
|
* Build the fail-soft reporter. The extension runs inside pi's process, so
|
|
77
84
|
* the client label follows the surface: `desktop` under the desktop shell
|
|
@@ -145,6 +145,15 @@ export function sanitizeCrashError(err, opts = {}) {
|
|
|
145
145
|
...(cappedStack !== undefined ? { stack: cappedStack } : {}),
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* OS, arch AND the Node version: the runtime is a first-class crash cause (an
|
|
150
|
+
* old Node dies inside undici on `zlib.createZstdDecompress`), and it rides
|
|
151
|
+
* the existing `platform` field so the backend and its Sentry tag need no
|
|
152
|
+
* change. Mirrored in `yagni-code-cli/src/crashReport.ts`.
|
|
153
|
+
*/
|
|
154
|
+
export function platformLabel() {
|
|
155
|
+
return `${process.platform} ${process.arch} node ${process.version}`;
|
|
156
|
+
}
|
|
148
157
|
/**
|
|
149
158
|
* Build the fail-soft reporter. The extension runs inside pi's process, so
|
|
150
159
|
* the client label follows the surface: `desktop` under the desktop shell
|
|
@@ -163,7 +172,7 @@ export function makeCrashReporter(opts) {
|
|
|
163
172
|
const payload = {
|
|
164
173
|
client: isDesktopSurface() ? "desktop" : "cli",
|
|
165
174
|
clientVersion: env.YAGNI_CODE_VERSION?.trim() || "unknown",
|
|
166
|
-
platform:
|
|
175
|
+
platform: platformLabel(),
|
|
167
176
|
...sanitized,
|
|
168
177
|
...(context !== undefined ? { context } : {}),
|
|
169
178
|
timestamp: new Date().toISOString(),
|
|
@@ -234,7 +243,7 @@ export function reportFatalCrash(error, opts, context) {
|
|
|
234
243
|
const payload = {
|
|
235
244
|
client: isDesktopSurface() ? "desktop" : "cli",
|
|
236
245
|
clientVersion: env.YAGNI_CODE_VERSION?.trim() || "unknown",
|
|
237
|
-
platform:
|
|
246
|
+
platform: platformLabel(),
|
|
238
247
|
...sanitized,
|
|
239
248
|
...(context !== undefined ? { context } : {}),
|
|
240
249
|
timestamp: new Date().toISOString(),
|
package/dist/extension/index.js
CHANGED
|
@@ -208,11 +208,18 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
208
208
|
// no-position record suggestions reach the model) and record_decision
|
|
209
209
|
// (flywheel-attributed records send dedupe: true). Run 7.
|
|
210
210
|
const flywheelState = makeFlywheelState();
|
|
211
|
+
// The session todo checklist: TodoWrite tool (Claude-parity surface), the
|
|
212
|
+
// above-editor widget, and /todos. Branch-replayed, so forks and resumes
|
|
213
|
+
// show the list as it stood at that point. Registered BEFORE the working
|
|
214
|
+
// line so the board's in_progress activeForm can drive the spinner verb.
|
|
215
|
+
const todosHandle = registerTodos(pi);
|
|
211
216
|
// The composed streaming status line ("Shaping… (12m 54s · ↓ 47.5k tokens)").
|
|
212
217
|
// Registered before the subagent/advisor tools: they publish their live
|
|
213
218
|
// progress through this handle so the elapsed/token suffix survives their
|
|
214
|
-
// overrides.
|
|
215
|
-
|
|
219
|
+
// overrides. The verb mirrors the todo board's live step when one is
|
|
220
|
+
// in_progress (Claude's currentTodo?.activeForm pattern). TUI-gated
|
|
221
|
+
// internally (agent_start checks ctx.mode).
|
|
222
|
+
const workingLine = registerWorkingLine(pi, { todoVerb: todosHandle.activeVerb });
|
|
216
223
|
pi.registerTool(makeAskYagniTool({ ...toolOpts, flywheel: flywheelState, getRepo: () => sessionRepo }));
|
|
217
224
|
// WebFetch (YAG-578): read an arbitrary URL as clean markdown + a
|
|
218
225
|
// standard-tier extraction, replacing the bash + curl + python dance.
|
|
@@ -253,10 +260,6 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
253
260
|
// ask_yagni answers the same question next time instead of interrupting a human.
|
|
254
261
|
pi.registerTool(makeRecordDecisionTool({ ...toolOpts, flywheel: flywheelState }));
|
|
255
262
|
}
|
|
256
|
-
// The visible checklist for multi-step work: the todo_write tool, its
|
|
257
|
-
// above-editor widget, and /todos. Branch-replayed, so forks and resumes
|
|
258
|
-
// show the list as it stood at that point.
|
|
259
|
-
registerTodos(pi);
|
|
260
263
|
// YAG-574: the silent-turn reminder, driver-only. A child/subagent/advisor
|
|
261
264
|
// process has no direct user to answer, so it is never nudged (same gating
|
|
262
265
|
// as the delegation identity in branding.ts); eval mode is untouched so its
|
|
@@ -1480,7 +1483,7 @@ export { blindStages, reportOnlyStages, makeGroundedVsBlindEval, formatCompariso
|
|
|
1480
1483
|
export { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
|
|
1481
1484
|
// The general subagent tool: Claude Code-format agent discovery + fan-out.
|
|
1482
1485
|
export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkdown, buildSubagentStage, formatAgentList, mapModelTier, SUBAGENT_TOOL_NAME, GENERAL_AGENT_NAME, MAX_PARALLEL_SUBAGENTS, MAX_PARALLEL_SUBAGENTS_ULTRA, DEFAULT_SUBAGENT_TOOLS, } from "./subagents.js";
|
|
1483
|
-
// The session todo checklist:
|
|
1486
|
+
// The session todo checklist: TodoWrite tool, widget renderer, /todos.
|
|
1484
1487
|
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, formatTodoReminder, shouldRemindTodos, todoSummary, TODO_TOOL_NAME, TODO_REMINDER_TURNS, MAX_TODOS, } from "./todos.js";
|
|
1485
1488
|
// P3 + W4: the permission gate seam (decideGate is pure; policy injectable) plus
|
|
1486
1489
|
// the session bless-with-remember capture hook.
|
|
@@ -309,7 +309,7 @@ export interface RegisterPermissionDeps {
|
|
|
309
309
|
export declare const MODE_CONTEXT_TYPE = "yagni-mode-context";
|
|
310
310
|
/** Legacy alias — the original plan-mode tag, kept for backward compat. */
|
|
311
311
|
export declare const PLAN_CONTEXT_TYPE = "yagni-mode-context";
|
|
312
|
-
export declare const PLAN_CONTEXT_MESSAGE = "[PLAN MODE ACTIVE]\nYou are in plan mode: explore and design, change nothing.\n- Read-only bash commands (ls, grep, git status, gh pr view, etc.) run freely to help you explore.\n- Ambiguous bash commands are reviewed by the Guardian; if non-mutating they run, if potentially mutating you will be asked.\n- write, edit, file_ticket, and update_ticket_status are held by the permission gate; do not attempt them.\n- Read, search, and ask_yagni freely to ground the plan in how this company works.\n- Produce a concrete numbered plan of the steps you would take, with the files involved.\n- End by asking the user to review the plan; they run /mode auto (or /mode review) to execute it.\n- Once executing, track the plan's steps with
|
|
312
|
+
export declare const PLAN_CONTEXT_MESSAGE = "[PLAN MODE ACTIVE]\nYou are in plan mode: explore and design, change nothing.\n- Read-only bash commands (ls, grep, git status, gh pr view, etc.) run freely to help you explore.\n- Ambiguous bash commands are reviewed by the Guardian; if non-mutating they run, if potentially mutating you will be asked.\n- write, edit, file_ticket, and update_ticket_status are held by the permission gate; do not attempt them.\n- Read, search, and ask_yagni freely to ground the plan in how this company works.\n- Produce a concrete numbered plan of the steps you would take, with the files involved.\n- End by asking the user to review the plan; they run /mode auto (or /mode review) to execute it.\n- Once executing, track the plan's steps with TodoWrite.";
|
|
313
313
|
/** Build the mode-awareness context message for the current permission mode. */
|
|
314
314
|
export declare function buildModeContextMessage(mode: PermissionMode): string;
|
|
315
315
|
/**
|
|
@@ -215,7 +215,7 @@ You are in plan mode: explore and design, change nothing.
|
|
|
215
215
|
- Read, search, and ask_yagni freely to ground the plan in how this company works.
|
|
216
216
|
- Produce a concrete numbered plan of the steps you would take, with the files involved.
|
|
217
217
|
- End by asking the user to review the plan; they run /mode auto (or /mode review) to execute it.
|
|
218
|
-
- Once executing, track the plan's steps with
|
|
218
|
+
- Once executing, track the plan's steps with TodoWrite.`;
|
|
219
219
|
const AUTO_CONTEXT_MESSAGE = `${AUTO_MARKER}
|
|
220
220
|
You are in auto mode. Coding commands run directly.
|
|
221
221
|
- Proactively verify your work: run tests, lint, and typecheck after changes.
|
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The session todo list — the visible checklist for multi-step work.
|
|
3
3
|
*
|
|
4
|
-
* A `
|
|
4
|
+
* A `TodoWrite` tool the model calls with the FULL list every time (replace,
|
|
5
5
|
* not patch: replacement is idempotent under retries and always renders a
|
|
6
6
|
* coherent board), a persistent above-editor widget while steps remain open,
|
|
7
7
|
* and a `/todos` command to pull the list on demand.
|
|
8
8
|
*
|
|
9
|
+
* The model-facing surface (name, item schema, description, result echo)
|
|
10
|
+
* matches Claude Code's shipped TodoWrite v1 exactly: the tick-immediately /
|
|
11
|
+
* don't-batch habits are trained behavior attached to that shape, and the
|
|
12
|
+
* custom `todo_write` surface was getting weak attachment (lists created at
|
|
13
|
+
* round start, entire rounds ground through unticked, one batch
|
|
14
|
+
* complete-at-end — the frozen-board report). Claude's TodoV2 per-task tools
|
|
15
|
+
* are a feature-flagged experiment there and are deliberately NOT adopted.
|
|
16
|
+
*
|
|
17
|
+
* The UI mirrors Claude Code too: TodoWrite leaves ZERO transcript rows
|
|
18
|
+
* (their renderToolUseMessage() => null; here, zero-line renderCall /
|
|
19
|
+
* renderResult renderers under renderShell "self" — pi removes the row
|
|
20
|
+
* entirely), and the board lives in the above-editor widget. The widget
|
|
21
|
+
* borrows Claude's TaskList details: the "N tasks (X done, Y in progress,
|
|
22
|
+
* Z open)" header, ✔/◼/◻ glyphs with a bold active row, activeForm as the
|
|
23
|
+
* live verb, and prioritized truncation (recently-completed tasks linger
|
|
24
|
+
* ~30s so a fresh tick is still visible, then older completions fall behind
|
|
25
|
+
* open work) with a "… +N in progress, M completed" overflow summary. One
|
|
26
|
+
* deliberate deviation: the list is always pinned above the editor, not
|
|
27
|
+
* behind Claude's ctrl+t toggle.
|
|
28
|
+
*
|
|
9
29
|
* State follows pi's branching model the same way the session does: the
|
|
10
|
-
* canonical list is the LAST
|
|
30
|
+
* canonical list is the LAST todo tool result on the current branch,
|
|
11
31
|
* reconstructed on session_start/session_tree, so forking or rewinding a
|
|
12
32
|
* session automatically shows the list as it stood at that point. The
|
|
13
33
|
* in-memory copy is just a cache of that.
|
|
14
34
|
*/
|
|
15
35
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
36
|
+
import { type Component } from "@earendil-works/pi-tui";
|
|
16
37
|
import { Type } from "typebox";
|
|
17
|
-
export declare const TODO_TOOL_NAME = "
|
|
38
|
+
export declare const TODO_TOOL_NAME = "TodoWrite";
|
|
39
|
+
/** The pre-parity tool name. Recorded sessions carry it in tool results. */
|
|
40
|
+
export declare const TODO_LEGACY_TOOL_NAME = "todo_write";
|
|
18
41
|
export declare const MAX_TODOS = 50;
|
|
19
42
|
export declare const MAX_TODO_TEXT = 300;
|
|
20
43
|
/**
|
|
21
44
|
* Staleness-reminder throttle (both counters must trip): a reminder is
|
|
22
|
-
* eligible only after this many assistant turns since the last
|
|
45
|
+
* eligible only after this many assistant turns since the last TodoWrite AND
|
|
23
46
|
* this many since the last reminder. The two-counter shape (staleness gate +
|
|
24
47
|
* anti-spam gate) mirrors what Claude Code ships for its own todo tool; the
|
|
25
48
|
* driver model routinely stops updating the board mid-grind (the frozen
|
|
@@ -27,6 +50,11 @@ export declare const MAX_TODO_TEXT = 300;
|
|
|
27
50
|
* survive a long run.
|
|
28
51
|
*/
|
|
29
52
|
export declare const TODO_REMINDER_TURNS = 10;
|
|
53
|
+
/**
|
|
54
|
+
* How long a freshly-completed task lingers at the top of the widget before
|
|
55
|
+
* dropping behind open work — Claude Code's TaskList RECENT_COMPLETED_TTL.
|
|
56
|
+
*/
|
|
57
|
+
export declare const TODO_COMPLETED_LINGER_MS = 30000;
|
|
30
58
|
/**
|
|
31
59
|
* The desktop's structured state record rides its own widget key, like the
|
|
32
60
|
* `/go` run state: one JSON line the app parses and renders itself, never
|
|
@@ -37,12 +65,17 @@ export declare const TODO_REMINDER_TURNS = 10;
|
|
|
37
65
|
export declare const TODO_STATE_KEY = "yagni-todos:state";
|
|
38
66
|
export type TodoStatus = "pending" | "in_progress" | "completed";
|
|
39
67
|
export interface TodoItem {
|
|
40
|
-
|
|
68
|
+
/** Imperative form: what needs to be done ("Run tests"). */
|
|
69
|
+
content: string;
|
|
70
|
+
/** Present continuous form, shown while in_progress ("Running tests"). */
|
|
71
|
+
activeForm: string;
|
|
41
72
|
status: TodoStatus;
|
|
42
73
|
}
|
|
43
74
|
/**
|
|
44
75
|
* Validate a full replacement list. Strict: this is model input rendered
|
|
45
76
|
* straight into the terminal. An empty list is valid (it clears the board).
|
|
77
|
+
* Accepts both the current shape ({content, activeForm}) and the legacy
|
|
78
|
+
* {text} shape so old sessions replay cleanly.
|
|
46
79
|
*/
|
|
47
80
|
export declare function normalizeTodos(raw: unknown): {
|
|
48
81
|
ok: true;
|
|
@@ -58,17 +91,46 @@ export declare function todoSummary(todos: TodoItem[]): {
|
|
|
58
91
|
};
|
|
59
92
|
/** Plain-text checklist (tool results, /todos in headless contexts). */
|
|
60
93
|
export declare function formatTodoList(todos: TodoItem[]): string;
|
|
94
|
+
/** One count for the whole board — header and overflow always agree. */
|
|
95
|
+
export declare function todoCounts(todos: TodoItem[]): {
|
|
96
|
+
total: number;
|
|
97
|
+
done: number;
|
|
98
|
+
inProgress: number;
|
|
99
|
+
open: number;
|
|
100
|
+
};
|
|
101
|
+
/** Claude Code's widget header: `3 tasks (1 done, 1 in progress, 6 open)`. */
|
|
102
|
+
export declare function formatTodoHeader(todos: TodoItem[]): string;
|
|
103
|
+
/**
|
|
104
|
+
* The every-write result echo — Claude Code's exact reinforcer. The tick
|
|
105
|
+
* habit decays over a long run; this lands on every TodoWrite result
|
|
106
|
+
* (including the create-at-start write, right before the model enters its
|
|
107
|
+
* grind) so the board stays current in the model's attention.
|
|
108
|
+
*/
|
|
109
|
+
export declare const TODO_RESULT_ECHO: string;
|
|
61
110
|
/** The slice of pi's Theme the widget styles with (matches the feed's pattern). */
|
|
62
111
|
export interface TodoTheme {
|
|
63
112
|
fg(color: string, s: string): string;
|
|
113
|
+
bold?(s: string): string;
|
|
64
114
|
strikethrough?(s: string): string;
|
|
65
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Claude Code's task truncation priority: recently-completed first (a fresh
|
|
118
|
+
* tick lingers {@link TODO_COMPLETED_LINGER_MS} so the user sees it land),
|
|
119
|
+
* then in_progress, then pending, then older completed — everything else
|
|
120
|
+
* falls behind the cap and is summarized.
|
|
121
|
+
*/
|
|
122
|
+
export declare function prioritizeTodos(todos: TodoItem[], completedAt: (content: string) => number | undefined, nowMs: number): TodoItem[];
|
|
123
|
+
/** Claude Code's overflow summary: `… +2 in progress, 3 completed`. */
|
|
124
|
+
export declare function formatTodoOverflow(hidden: TodoItem[]): string | null;
|
|
66
125
|
/**
|
|
67
126
|
* The above-editor checklist. Empty (paint nothing) when there is no list or
|
|
68
|
-
* every step is completed — a finished board should leave the screen.
|
|
127
|
+
* every step is completed — a finished board should leave the screen. Rows
|
|
128
|
+
* past the cap are truncated with Claude's prioritization + overflow summary
|
|
69
129
|
* so header + items + overflow stays under pi's 10-line widget truncation.
|
|
130
|
+
* The in-progress row shows the active form in bold (the live "what am I
|
|
131
|
+
* doing" signal); pending and completed rows show the imperative content.
|
|
70
132
|
*/
|
|
71
|
-
export declare function renderTodoWidget(todos: TodoItem[], theme: TodoTheme): string[];
|
|
133
|
+
export declare function renderTodoWidget(todos: TodoItem[], theme: TodoTheme, completedAtCache?: Map<string, number>, nowMs?: number): string[];
|
|
72
134
|
/** The desktop state record: exactly one JSON line under TODO_STATE_KEY. */
|
|
73
135
|
export declare function todoStateLine(todos: TodoItem[]): string;
|
|
74
136
|
/**
|
|
@@ -85,22 +147,23 @@ export declare function shouldRemindTodos(input: {
|
|
|
85
147
|
* PURE: the hedged reminder block appended to a tool result when the board has
|
|
86
148
|
* gone stale. Carries the CURRENT list so the model can reconcile without a
|
|
87
149
|
* read, and explicitly licenses ignoring it, so an accurate board costs one
|
|
88
|
-
* glance rather than a spurious
|
|
150
|
+
* glance rather than a spurious TodoWrite.
|
|
89
151
|
*/
|
|
90
152
|
export declare function formatTodoReminder(todos: TodoItem[]): string;
|
|
91
|
-
/** Replay the branch: the last
|
|
153
|
+
/** Replay the branch: the last todo-tool result is the canonical list. */
|
|
92
154
|
export declare function reconstructTodos(entries: unknown[]): TodoItem[];
|
|
93
155
|
type TodoParams = {
|
|
94
156
|
todos: Array<{
|
|
95
|
-
|
|
157
|
+
content: string;
|
|
158
|
+
activeForm: string;
|
|
96
159
|
status: string;
|
|
97
160
|
}>;
|
|
98
161
|
};
|
|
99
162
|
/**
|
|
100
|
-
* Build the
|
|
163
|
+
* Build the TodoWrite tool definition around a shared store. Separated from
|
|
101
164
|
* registration so tests can drive execute directly.
|
|
102
165
|
*/
|
|
103
|
-
export declare function makeTodoTool(get: () => TodoItem[], set: (todos: TodoItem[]) => void): {
|
|
166
|
+
export declare function makeTodoTool(get: () => TodoItem[], set: (todos: TodoItem[]) => void, completedAt?: Map<string, number>): {
|
|
104
167
|
name: string;
|
|
105
168
|
label: string;
|
|
106
169
|
description: string;
|
|
@@ -108,10 +171,21 @@ export declare function makeTodoTool(get: () => TodoItem[], set: (todos: TodoIte
|
|
|
108
171
|
promptGuidelines: string[];
|
|
109
172
|
parameters: Type.TObject<{
|
|
110
173
|
todos: Type.TArray<Type.TObject<{
|
|
111
|
-
|
|
174
|
+
content: Type.TString;
|
|
175
|
+
activeForm: Type.TString;
|
|
112
176
|
status: Type.TUnion<[Type.TLiteral<"pending">, Type.TLiteral<"in_progress">, Type.TLiteral<"completed">]>;
|
|
113
177
|
}>>;
|
|
114
178
|
}>;
|
|
179
|
+
renderShell: "self";
|
|
180
|
+
renderCall: () => Component;
|
|
181
|
+
renderResult: (result: {
|
|
182
|
+
content?: Array<{
|
|
183
|
+
type: string;
|
|
184
|
+
text?: string;
|
|
185
|
+
}>;
|
|
186
|
+
}, options: {
|
|
187
|
+
isError?: boolean;
|
|
188
|
+
}, theme: TodoTheme) => Component;
|
|
115
189
|
execute(_toolCallId: string, params: TodoParams, _signal?: AbortSignal, _onUpdate?: unknown, ctx?: ExtensionContext): Promise<{
|
|
116
190
|
content: {
|
|
117
191
|
type: "text";
|
|
@@ -132,7 +206,15 @@ export declare function makeTodoTool(get: () => TodoItem[], set: (todos: TodoIte
|
|
|
132
206
|
isError?: undefined;
|
|
133
207
|
}>;
|
|
134
208
|
};
|
|
135
|
-
/**
|
|
136
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Wire the tool, the branch-replay events, the staleness reminder, the
|
|
211
|
+
* widget refresh loop, and /todos.
|
|
212
|
+
*/
|
|
213
|
+
/** What registerTodos hands back: the live spinner-verb override. */
|
|
214
|
+
export interface TodosHandle {
|
|
215
|
+
/** The in_progress item's activeForm, or undefined with no active step. */
|
|
216
|
+
activeVerb(): string | undefined;
|
|
217
|
+
}
|
|
218
|
+
export declare function registerTodos(pi: ExtensionAPI): TodosHandle;
|
|
137
219
|
export {};
|
|
138
220
|
//# sourceMappingURL=todos.d.ts.map
|