@yagni-app/code-staging 1.1.1-staging.1358.1 → 1.1.1-staging.1359.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/dist/extension/askAdvisorTool.d.ts +11 -1
- package/dist/extension/askAdvisorTool.js +49 -19
- package/dist/extension/askUserQuestionTool.d.ts +9 -2
- package/dist/extension/askUserQuestionTool.js +20 -8
- package/dist/extension/branding.d.ts +15 -0
- package/dist/extension/branding.js +30 -1
- package/dist/extension/grounding.d.ts +48 -0
- package/dist/extension/grounding.js +91 -0
- package/dist/extension/index.d.ts +7 -0
- package/dist/extension/index.js +112 -31
- package/dist/extension/initPass.d.ts +7 -0
- package/dist/extension/initPass.js +11 -1
- package/dist/extension/pipeline/goCommand.d.ts +8 -0
- package/dist/extension/pipeline/goCommand.js +7 -1
- package/dist/extension/pipeline/headlessGo.js +8 -1
- package/dist/extension/subagents.d.ts +12 -6
- package/dist/extension/subagents.js +96 -5
- package/package.json +2 -2
|
@@ -40,6 +40,9 @@ import { type RenderTheme, type SubagentTaskProgress } from "./subagentRender.js
|
|
|
40
40
|
* single `read`. No `bash`.
|
|
41
41
|
*/
|
|
42
42
|
export declare const ADVISOR_TOOLS: string[];
|
|
43
|
+
/** The consult's tool list under `grounding: false` — grounding read
|
|
44
|
+
* stripped via the canonical filter (grounding.ts). */
|
|
45
|
+
export declare const ADVISOR_TOOLS_GROUNDING_FREE: string[];
|
|
43
46
|
declare const parameters: Type.TObject<{
|
|
44
47
|
question: Type.TString;
|
|
45
48
|
tried: Type.TOptional<Type.TString>;
|
|
@@ -49,6 +52,13 @@ export interface MakeAskAdvisorToolOptions {
|
|
|
49
52
|
/** Per-session consult accounting. Created once per session in index.ts. */
|
|
50
53
|
state: AdvisorStateHandle;
|
|
51
54
|
limits?: AdvisorLimits;
|
|
55
|
+
/**
|
|
56
|
+
* The boot-time grounding switch: false runs consults on the BLIND advisor
|
|
57
|
+
* persona (the existing ADVISOR_BLIND body in pipeline/personas.ts — the
|
|
58
|
+
* M6 eval lane's machinery) with the grounding-free tool list and metadata
|
|
59
|
+
* that no longer nudges ask_yagni / record_decision. Defaults grounded.
|
|
60
|
+
*/
|
|
61
|
+
grounded?: boolean;
|
|
52
62
|
/** Injectable so tests never spawn a child. */
|
|
53
63
|
runStage?: typeof defaultRunStage;
|
|
54
64
|
/**
|
|
@@ -81,7 +91,7 @@ export declare function buildConsultBrief(params: {
|
|
|
81
91
|
* feed/activity reducers that switch over it. `agent` is what selects the
|
|
82
92
|
* persona, and that is the advisor's own.
|
|
83
93
|
*/
|
|
84
|
-
export declare function advisorStage(): PipelineStage;
|
|
94
|
+
export declare function advisorStage(grounded?: boolean): PipelineStage;
|
|
85
95
|
/** The ask_advisor result details: consult accounting plus the live progress
|
|
86
96
|
* record the subagent renderers paint (one task, the consult itself). */
|
|
87
97
|
export interface AdvisorToolDetails {
|
|
@@ -30,6 +30,7 @@ import { Type } from "typebox";
|
|
|
30
30
|
import { ADVISOR_MODEL_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatConsultCost, } from "./advisor.js";
|
|
31
31
|
import { SPINNER_FRAMES } from "./pipeline/activityFeed.js";
|
|
32
32
|
import { withResilience } from "./pipeline/resilience.js";
|
|
33
|
+
import { stripGroundingTool } from "./grounding.js";
|
|
33
34
|
import { runStage as defaultRunStage } from "./pipeline/runner.js";
|
|
34
35
|
import { DEFAULT_RESILIENCE_POLICY } from "./pipeline/types.js";
|
|
35
36
|
import { logEvent } from "./errorSink.js";
|
|
@@ -40,6 +41,9 @@ import { applyChildEvent, finalizeTask, formatWorkingMessage, newTaskProgress, p
|
|
|
40
41
|
* single `read`. No `bash`.
|
|
41
42
|
*/
|
|
42
43
|
export const ADVISOR_TOOLS = ["read", "grep", "find", "ls", "ask_yagni"];
|
|
44
|
+
/** The consult's tool list under `grounding: false` — grounding read
|
|
45
|
+
* stripped via the canonical filter (grounding.ts). */
|
|
46
|
+
export const ADVISOR_TOOLS_GROUNDING_FREE = stripGroundingTool(ADVISOR_TOOLS);
|
|
43
47
|
const parameters = Type.Object({
|
|
44
48
|
question: Type.String({
|
|
45
49
|
description: "The single judgment call you want a second opinion on. Be specific.",
|
|
@@ -68,12 +72,12 @@ export function buildConsultBrief(params) {
|
|
|
68
72
|
* feed/activity reducers that switch over it. `agent` is what selects the
|
|
69
73
|
* persona, and that is the advisor's own.
|
|
70
74
|
*/
|
|
71
|
-
export function advisorStage() {
|
|
75
|
+
export function advisorStage(grounded = true) {
|
|
72
76
|
return {
|
|
73
77
|
id: "plan",
|
|
74
78
|
agent: "advisor",
|
|
75
79
|
model: ADVISOR_MODEL_TIER,
|
|
76
|
-
tools: ADVISOR_TOOLS,
|
|
80
|
+
tools: grounded ? ADVISOR_TOOLS : ADVISOR_TOOLS_GROUNDING_FREE,
|
|
77
81
|
taskTemplate: "{ticket}",
|
|
78
82
|
};
|
|
79
83
|
}
|
|
@@ -97,17 +101,12 @@ export function renderAdvisorCall(args, theme, _context) {
|
|
|
97
101
|
}
|
|
98
102
|
export function makeAskAdvisorTool(opts) {
|
|
99
103
|
const limits = opts.limits ?? DEFAULT_ADVISOR_LIMITS;
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const runStage = opts.runStage ?? withResilience(defaultRunStage, DEFAULT_RESILIENCE_POLICY);
|
|
107
|
-
return {
|
|
108
|
-
name: "ask_advisor",
|
|
109
|
-
label: "Ask the advisor",
|
|
110
|
-
description: "Escalate ONE hard judgment call to the peak-tier advisor — the strongest " +
|
|
104
|
+
const grounded = opts.grounded !== false;
|
|
105
|
+
// Metadata variants: the description/promptGuidelines reach the model as
|
|
106
|
+
// tool metadata even when the consult runs blind — with grounding off they
|
|
107
|
+
// must not nudge ask_yagni (unregistered) or record_decision (unregistered).
|
|
108
|
+
const description = grounded
|
|
109
|
+
? "Escalate ONE hard judgment call to the peak-tier advisor — the strongest " +
|
|
111
110
|
"model available, which reads the code itself and returns a recommendation. " +
|
|
112
111
|
"Available only on the Advanced tier, and capped per session, so use it for " +
|
|
113
112
|
"calls that are genuinely worth it: an architectural fork with no obvious " +
|
|
@@ -115,15 +114,43 @@ export function makeAskAdvisorTool(opts) {
|
|
|
115
114
|
"a change whose blast radius you are unsure of, or a second opinion before " +
|
|
116
115
|
"committing to an approach you would have to unwind. Do NOT use it for " +
|
|
117
116
|
"lookups (use ask_yagni), for anything you can settle by reading the code, " +
|
|
118
|
-
"or to review work you have already finished. Pass a sharp question plus
|
|
119
|
-
"relevant excerpts — never a conversation transcript."
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
"or to review work you have already finished. Pass a sharp question plus " +
|
|
118
|
+
"the relevant excerpts — never a conversation transcript."
|
|
119
|
+
: "Escalate ONE hard judgment call to the peak-tier advisor — the strongest " +
|
|
120
|
+
"model available, which reads the code itself and returns a recommendation. " +
|
|
121
|
+
"Available only on the Advanced tier, and capped per session, so use it for " +
|
|
122
|
+
"calls that are genuinely worth it: an architectural fork with no obvious " +
|
|
123
|
+
"right answer, a subtle correctness question you cannot settle by reading, " +
|
|
124
|
+
"a change whose blast radius you are unsure of, or a second opinion before " +
|
|
125
|
+
"committing to an approach you would have to unwind. Do NOT use it for " +
|
|
126
|
+
"anything you can settle by reading the code, or to review work you have " +
|
|
127
|
+
"already finished. Pass a sharp question plus the relevant excerpts — never " +
|
|
128
|
+
"a conversation transcript.";
|
|
129
|
+
const promptGuidelines = grounded
|
|
130
|
+
? [
|
|
122
131
|
"Call ask_advisor only for a genuine judgment fork — an architectural choice, a subtle correctness question, or a second opinion before an approach you would have to unwind. Reading the code is cheaper; do that first.",
|
|
123
132
|
"Ask ONE specific question per consult, and include the excerpts that matter. The advisor reads the repo itself, so point it at the right place rather than pasting everything.",
|
|
124
133
|
"Consults are capped per session. Spend them on the calls you would otherwise get wrong.",
|
|
125
134
|
"The advice comes back as plain text: act on it, and call record_decision when it settles a product-intent call so the next agent inherits it.",
|
|
126
|
-
]
|
|
135
|
+
]
|
|
136
|
+
: [
|
|
137
|
+
"Call ask_advisor only for a genuine judgment fork — an architectural choice, a subtle correctness question, or a second opinion before an approach you would have to unwind. Reading the code is cheaper; do that first.",
|
|
138
|
+
"Ask ONE specific question per consult, and include the excerpts that matter. The advisor reads the repo itself, so point it at the right place rather than pasting everything.",
|
|
139
|
+
"Consults are capped per session. Spend them on the calls you would otherwise get wrong.",
|
|
140
|
+
];
|
|
141
|
+
// The default runner rides the /go pipeline's resilience wrapper, exactly
|
|
142
|
+
// like the subagent tool: idle + wall-clock ceilings and transient-only
|
|
143
|
+
// retry, so a stalled consult child aborts honestly instead of hanging the
|
|
144
|
+
// driver's tool call until the user presses Esc. The synthetic stage id is
|
|
145
|
+
// "plan" (read-only tools, no bash), so the wrapper's write-gate never
|
|
146
|
+
// blocks a retry — re-running a consult cannot double-apply anything.
|
|
147
|
+
const runStage = opts.runStage ?? withResilience(defaultRunStage, DEFAULT_RESILIENCE_POLICY);
|
|
148
|
+
return {
|
|
149
|
+
name: "ask_advisor",
|
|
150
|
+
label: "Ask the advisor",
|
|
151
|
+
description,
|
|
152
|
+
promptSnippet: "ask_advisor: escalate one hard judgment call to the peak-tier advisor (Advanced sessions, capped).",
|
|
153
|
+
promptGuidelines,
|
|
127
154
|
parameters,
|
|
128
155
|
// Self-framed: the condensed transcript look has no tinted tool boxes.
|
|
129
156
|
renderShell: "self",
|
|
@@ -172,7 +199,10 @@ export function makeAskAdvisorTool(opts) {
|
|
|
172
199
|
emit();
|
|
173
200
|
let result;
|
|
174
201
|
try {
|
|
175
|
-
result = await runStage(advisorStage(),
|
|
202
|
+
result = await runStage(advisorStage(grounded),
|
|
203
|
+
// `grounded` selects the persona body in the runner's personaBody
|
|
204
|
+
// seam: false = ADVISOR_BLIND (the M6 blind lane's body).
|
|
205
|
+
{ ticket: buildConsultBrief(params), grounded }, {
|
|
176
206
|
cwd: ctx?.cwd ?? process.cwd(),
|
|
177
207
|
...(signal ? { signal } : {}),
|
|
178
208
|
// YAG-471: attribute the consult's completions to the advisor, not
|
|
@@ -48,7 +48,14 @@ type AskDetails = {
|
|
|
48
48
|
questions: Question[];
|
|
49
49
|
answers: Record<string, string>;
|
|
50
50
|
};
|
|
51
|
-
/**
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Build the ask_user_question tool. Registered unconditionally (like
|
|
53
|
+
* ask_advisor): the tool itself is not a grounding surface, but its metadata
|
|
54
|
+
* references the recorded-answer lookup, so `grounded: false` swaps in the
|
|
55
|
+
* reference-free variants.
|
|
56
|
+
*/
|
|
57
|
+
export declare function makeAskUserQuestionTool(opts?: {
|
|
58
|
+
grounded?: boolean;
|
|
59
|
+
}): ToolDefinition<typeof parameters, AskDetails>;
|
|
53
60
|
export {};
|
|
54
61
|
//# sourceMappingURL=askUserQuestionTool.d.ts.map
|
|
@@ -70,6 +70,9 @@ const PROMPT_GUIDELINES = [
|
|
|
70
70
|
"If you recommend a specific option, make it the first one and append '(Recommended)' to its label.",
|
|
71
71
|
"Do not use this tool for feedback like 'does the plan look good?' — ask a real choice with real options.",
|
|
72
72
|
];
|
|
73
|
+
/** The grounding-free guideline list: the recorded-answer clause drops its
|
|
74
|
+
* ask_yagni reference (the tool is unregistered under `grounding: false`). */
|
|
75
|
+
const PROMPT_GUIDELINES_GROUNDING_FREE = PROMPT_GUIDELINES.map((g) => g.replace(" — and the answer is not already recorded (check ask_yagni first)", ""));
|
|
73
76
|
/**
|
|
74
77
|
* Whether any option in the question has a preview. Drives the two-column
|
|
75
78
|
* layout (Claude switches to side-by-side only when previews exist).
|
|
@@ -566,15 +569,19 @@ async function askOne(ctx, q, qIndex) {
|
|
|
566
569
|
logAskQuestion({ event: "resolved", status: "selected", detail: `values:${resolution.values.length}`, qIndex });
|
|
567
570
|
return { answer: resolution.values.join(", "), cancelled: false };
|
|
568
571
|
}
|
|
569
|
-
function buildTool() {
|
|
572
|
+
function buildTool(grounded = true) {
|
|
570
573
|
return {
|
|
571
574
|
name: TOOL_NAME,
|
|
572
575
|
label: "Ask user a question",
|
|
573
|
-
description:
|
|
574
|
-
"
|
|
575
|
-
|
|
576
|
+
description: grounded
|
|
577
|
+
? "Pose a structured multiple-choice question to the user during execution and get back a clean machine-readable answer. " +
|
|
578
|
+
"Use when you genuinely need a human decision (an architectural fork, a product-intent call with no recorded answer, or a choice between approaches)." +
|
|
579
|
+
"Each question has 2-4 mutually exclusive options, each with a description of its tradeoff and an optional preview."
|
|
580
|
+
: "Pose a structured multiple-choice question to the user during execution and get back a clean machine-readable answer. " +
|
|
581
|
+
"Use when you genuinely need a human decision (an architectural fork, a product-intent call, or a choice between approaches)." +
|
|
582
|
+
"Each question has 2-4 mutually exclusive options, each with a description of its tradeoff and an optional preview.",
|
|
576
583
|
promptSnippet: PROMPT_SNIPPET,
|
|
577
|
-
promptGuidelines: PROMPT_GUIDELINES,
|
|
584
|
+
promptGuidelines: grounded ? PROMPT_GUIDELINES : PROMPT_GUIDELINES_GROUNDING_FREE,
|
|
578
585
|
parameters,
|
|
579
586
|
async execute(_toolCallId, params, _signal, onUpdate, ctx) {
|
|
580
587
|
onUpdate?.({ content: [{ type: "text", text: "Asking you…" }], details: { questions: [], answers: {} } });
|
|
@@ -636,8 +643,13 @@ function buildTool() {
|
|
|
636
643
|
},
|
|
637
644
|
};
|
|
638
645
|
}
|
|
639
|
-
/**
|
|
640
|
-
|
|
641
|
-
|
|
646
|
+
/**
|
|
647
|
+
* Build the ask_user_question tool. Registered unconditionally (like
|
|
648
|
+
* ask_advisor): the tool itself is not a grounding surface, but its metadata
|
|
649
|
+
* references the recorded-answer lookup, so `grounded: false` swaps in the
|
|
650
|
+
* reference-free variants.
|
|
651
|
+
*/
|
|
652
|
+
export function makeAskUserQuestionTool(opts = {}) {
|
|
653
|
+
return buildTool(opts.grounded !== false);
|
|
642
654
|
}
|
|
643
655
|
//# sourceMappingURL=askUserQuestionTool.js.map
|
|
@@ -100,6 +100,21 @@ export declare const COMMUNICATION_CONTRACT: string;
|
|
|
100
100
|
export declare const WRITE_FINDINGS_DOWN: string;
|
|
101
101
|
/** The driver identity while /ultra is on: base identity + the diamond directive. */
|
|
102
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
|
+
/**
|
|
104
|
+
* The grounding-FREE identity for `grounding: false` boots: the role and
|
|
105
|
+
* anti-confusion sentences of {@link YAGNI_IDENTITY} with the grounding
|
|
106
|
+
* sentence ("Uniquely, you are connected to the YAGNI app… Use the ask_yagni
|
|
107
|
+
* tool…") removed. The `grounding` settings switch severs the judgment loop
|
|
108
|
+
* in both directions; an identity that still instructs the model to consult
|
|
109
|
+
* ask_yagni (an unregistered tool) would be friction and influence referencing
|
|
110
|
+
* a surface the user turned off. Kept as a SEPARATE constant so the grounded
|
|
111
|
+
* prompt text stays byte-identical (the blind-lane pattern).
|
|
112
|
+
*/
|
|
113
|
+
export declare const YAGNI_IDENTITY_GROUNDING_FREE: string;
|
|
114
|
+
/** The grounding-free driver identity (base + delegation, verbatim). */
|
|
115
|
+
export declare const YAGNI_IDENTITY_DRIVER_GROUNDING_FREE = "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. 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). Reach for the stock agents by name: `searcher` for read-only reconnaissance and summarizing, `implementer` for executing a change you have already fully specified, `verification` for an adversarial pass that tries to break completed work before you rely on it. 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.";
|
|
116
|
+
/** The grounding-free ultra identity (base + diamond, verbatim). */
|
|
117
|
+
export declare const YAGNI_IDENTITY_ULTRA_GROUNDING_FREE = "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. 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
118
|
export declare const PI_IDENTITY_RE: RegExp;
|
|
104
119
|
/**
|
|
105
120
|
* Env switch that bypasses the system-prompt rewrite entirely, so pi's
|
|
@@ -193,12 +193,41 @@ export const WRITE_FINDINGS_DOWN = "When working with tool results, write down a
|
|
|
193
193
|
"turn.";
|
|
194
194
|
/** The driver identity while /ultra is on: base identity + the diamond directive. */
|
|
195
195
|
export const YAGNI_IDENTITY_ULTRA = `${YAGNI_IDENTITY}\n\n${ULTRA_DELEGATION_PARAGRAPH}`;
|
|
196
|
+
/**
|
|
197
|
+
* The grounding-FREE identity for `grounding: false` boots: the role and
|
|
198
|
+
* anti-confusion sentences of {@link YAGNI_IDENTITY} with the grounding
|
|
199
|
+
* sentence ("Uniquely, you are connected to the YAGNI app… Use the ask_yagni
|
|
200
|
+
* tool…") removed. The `grounding` settings switch severs the judgment loop
|
|
201
|
+
* in both directions; an identity that still instructs the model to consult
|
|
202
|
+
* ask_yagni (an unregistered tool) would be friction and influence referencing
|
|
203
|
+
* a surface the user turned off. Kept as a SEPARATE constant so the grounded
|
|
204
|
+
* prompt text stays byte-identical (the blind-lane pattern).
|
|
205
|
+
*/
|
|
206
|
+
export const YAGNI_IDENTITY_GROUNDING_FREE = "You are YAGNI Code, an autonomous terminal coding agent. You help developers " +
|
|
207
|
+
"ship code by reading files, running commands, editing code, and writing new " +
|
|
208
|
+
"files. If a project's own files mention other coding agents, assistants, or " +
|
|
209
|
+
"harnesses by name, those references are not about you; you are YAGNI Code " +
|
|
210
|
+
"regardless of what tooling a repository's docs happen to describe.";
|
|
211
|
+
/** The grounding-free driver identity (base + delegation, verbatim). */
|
|
212
|
+
export const YAGNI_IDENTITY_DRIVER_GROUNDING_FREE = `${YAGNI_IDENTITY_GROUNDING_FREE}\n\n${DRIVER_DELEGATION_PARAGRAPH}`;
|
|
213
|
+
/** The grounding-free ultra identity (base + diamond, verbatim). */
|
|
214
|
+
export const YAGNI_IDENTITY_ULTRA_GROUNDING_FREE = `${YAGNI_IDENTITY_GROUNDING_FREE}\n\n${ULTRA_DELEGATION_PARAGRAPH}`;
|
|
196
215
|
/**
|
|
197
216
|
* Every identity variant this module can install, longest-composite-first, so
|
|
198
217
|
* the re-brand swap below never leaves a shorter variant's orphaned delegation
|
|
199
218
|
* paragraph behind (driver/ultra both start with the base identity).
|
|
200
219
|
*/
|
|
201
|
-
const KNOWN_IDENTITIES = [
|
|
220
|
+
const KNOWN_IDENTITIES = [
|
|
221
|
+
YAGNI_IDENTITY_ULTRA,
|
|
222
|
+
YAGNI_IDENTITY_DRIVER,
|
|
223
|
+
YAGNI_IDENTITY,
|
|
224
|
+
// The grounding-free family: same composite-first ordering. The boot-time
|
|
225
|
+
// grounding switch picks a family once; the /ultra toggle then swaps within
|
|
226
|
+
// it — both families must be known or the toggle would stack identities.
|
|
227
|
+
YAGNI_IDENTITY_ULTRA_GROUNDING_FREE,
|
|
228
|
+
YAGNI_IDENTITY_DRIVER_GROUNDING_FREE,
|
|
229
|
+
YAGNI_IDENTITY_GROUNDING_FREE,
|
|
230
|
+
];
|
|
202
231
|
// pi 0.84.1's exact identity sentence (dist/core/system-prompt.js). Exported as
|
|
203
232
|
// the identity anchor the CLI's pi-contract tripwire test reads back from pi's
|
|
204
233
|
// built system prompt, so a pi bump that reworded the opener (silently defeating
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `grounding` settings switch: the user-tier key in
|
|
3
|
+
* ~/.yagni-code/config.json that severs the recorded-judgment loop in BOTH
|
|
4
|
+
* directions — decisions can neither reach the model (no ask_yagni, no boot
|
|
5
|
+
* brief injection, no ambient recall, blind personas) nor be captured from
|
|
6
|
+
* the session (no record tools, no /decide, no bless capture, no spool
|
|
7
|
+
* replay). The backend is untouched; the extension owns every fetch and
|
|
8
|
+
* every write on this surface.
|
|
9
|
+
*
|
|
10
|
+
* Loader contract (mirrors sandbox/config.ts's read discipline):
|
|
11
|
+
* - USER TIER ONLY: <stateHome>/config.json, top-level `grounding`. The
|
|
12
|
+
* project/local tiers are never consulted — a committed repo config
|
|
13
|
+
* cannot silently re-enable or disable a developer's judgment loop.
|
|
14
|
+
* - default ON: absent key, absent file, malformed JSON, or a non-boolean
|
|
15
|
+
* value all mean grounded (today's behavior). Fail-soft by design: a
|
|
16
|
+
* corrupt config must not wedge the agent's boot.
|
|
17
|
+
* - a malformed/invalid config warns to the error sink (source "grounding")
|
|
18
|
+
* with the error CLASS only — never the parse message, which embeds a
|
|
19
|
+
* snippet of the file's own content.
|
|
20
|
+
*/
|
|
21
|
+
export interface GroundingLoad {
|
|
22
|
+
/** False = the judgment loop is severed for this boot. */
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
/** Present only when a config problem forced the default (fail-soft). */
|
|
25
|
+
warning?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Read the `grounding` key from the user-tier config. Pure-ish (one file
|
|
29
|
+
* read + an optional sink warn); returns the resolved switch plus any
|
|
30
|
+
* fail-soft warning so callers and tests can assert both halves.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The path a config warning names. The warn tier is upload-surfaceable
|
|
34
|
+
* (readSessionTrail keeps warn lines and /feedback binds them), and
|
|
35
|
+
* scrubSecrets redacts credentials, not paths — so the operator's personal
|
|
36
|
+
* default home collapses to ~. An OVERRIDDEN state home (YAGNI_CODE_HOME, an
|
|
37
|
+
* explicit env pin) is not personal — and the warning must name the file
|
|
38
|
+
* that is actually broken — so it stays literal there. PURE, exported for
|
|
39
|
+
* tests.
|
|
40
|
+
*/
|
|
41
|
+
export declare function displayConfigPath(stateHome: string): string;
|
|
42
|
+
export declare function loadGroundingEnabled(stateHome: string, env?: NodeJS.ProcessEnv): GroundingLoad;
|
|
43
|
+
/**
|
|
44
|
+
* The canonical "strip the grounding read tool" filter — one home for the
|
|
45
|
+
* rule so subagent spawns and advisor consults can't drift apart.
|
|
46
|
+
*/
|
|
47
|
+
export declare function stripGroundingTool(tools: readonly string[]): string[];
|
|
48
|
+
//# sourceMappingURL=grounding.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `grounding` settings switch: the user-tier key in
|
|
3
|
+
* ~/.yagni-code/config.json that severs the recorded-judgment loop in BOTH
|
|
4
|
+
* directions — decisions can neither reach the model (no ask_yagni, no boot
|
|
5
|
+
* brief injection, no ambient recall, blind personas) nor be captured from
|
|
6
|
+
* the session (no record tools, no /decide, no bless capture, no spool
|
|
7
|
+
* replay). The backend is untouched; the extension owns every fetch and
|
|
8
|
+
* every write on this surface.
|
|
9
|
+
*
|
|
10
|
+
* Loader contract (mirrors sandbox/config.ts's read discipline):
|
|
11
|
+
* - USER TIER ONLY: <stateHome>/config.json, top-level `grounding`. The
|
|
12
|
+
* project/local tiers are never consulted — a committed repo config
|
|
13
|
+
* cannot silently re-enable or disable a developer's judgment loop.
|
|
14
|
+
* - default ON: absent key, absent file, malformed JSON, or a non-boolean
|
|
15
|
+
* value all mean grounded (today's behavior). Fail-soft by design: a
|
|
16
|
+
* corrupt config must not wedge the agent's boot.
|
|
17
|
+
* - a malformed/invalid config warns to the error sink (source "grounding")
|
|
18
|
+
* with the error CLASS only — never the parse message, which embeds a
|
|
19
|
+
* snippet of the file's own content.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { logEvent } from "./errorSink.js";
|
|
25
|
+
/**
|
|
26
|
+
* Read the `grounding` key from the user-tier config. Pure-ish (one file
|
|
27
|
+
* read + an optional sink warn); returns the resolved switch plus any
|
|
28
|
+
* fail-soft warning so callers and tests can assert both halves.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* The path a config warning names. The warn tier is upload-surfaceable
|
|
32
|
+
* (readSessionTrail keeps warn lines and /feedback binds them), and
|
|
33
|
+
* scrubSecrets redacts credentials, not paths — so the operator's personal
|
|
34
|
+
* default home collapses to ~. An OVERRIDDEN state home (YAGNI_CODE_HOME, an
|
|
35
|
+
* explicit env pin) is not personal — and the warning must name the file
|
|
36
|
+
* that is actually broken — so it stays literal there. PURE, exported for
|
|
37
|
+
* tests.
|
|
38
|
+
*/
|
|
39
|
+
export function displayConfigPath(stateHome) {
|
|
40
|
+
const defaultHome = join(homedir(), ".yagni-code");
|
|
41
|
+
return stateHome === defaultHome ? "~/.yagni-code/config.json" : join(stateHome, "config.json");
|
|
42
|
+
}
|
|
43
|
+
export function loadGroundingEnabled(stateHome, env) {
|
|
44
|
+
const displayPath = displayConfigPath(stateHome);
|
|
45
|
+
const path = join(stateHome, "config.json");
|
|
46
|
+
if (!existsSync(path))
|
|
47
|
+
return { enabled: true };
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const warning = `grounding: could not parse ${displayPath}: ${err instanceof Error ? err.constructor.name : typeof err} — staying grounded`;
|
|
54
|
+
warnConfig(warning, env);
|
|
55
|
+
return { enabled: true, warning };
|
|
56
|
+
}
|
|
57
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
58
|
+
const warning = `grounding: config at ${displayPath} is not an object — staying grounded`;
|
|
59
|
+
warnConfig(warning, env);
|
|
60
|
+
return { enabled: true, warning };
|
|
61
|
+
}
|
|
62
|
+
const value = parsed.grounding;
|
|
63
|
+
if (value === undefined)
|
|
64
|
+
return { enabled: true };
|
|
65
|
+
if (typeof value !== "boolean") {
|
|
66
|
+
const warning = `grounding: "grounding" in ${displayPath} must be a boolean — staying grounded`;
|
|
67
|
+
warnConfig(warning, env);
|
|
68
|
+
return { enabled: true, warning };
|
|
69
|
+
}
|
|
70
|
+
return { enabled: value };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The canonical "strip the grounding read tool" filter — one home for the
|
|
74
|
+
* rule so subagent spawns and advisor consults can't drift apart.
|
|
75
|
+
*/
|
|
76
|
+
export function stripGroundingTool(tools) {
|
|
77
|
+
return tools.filter((t) => t !== "ask_yagni");
|
|
78
|
+
}
|
|
79
|
+
function warnConfig(warning, env) {
|
|
80
|
+
// The warning carries only the file path + the error CLASS — never a parse
|
|
81
|
+
// message (those embed the file's own content). Same posture as the sandbox
|
|
82
|
+
// loader's config warnings.
|
|
83
|
+
logEvent({
|
|
84
|
+
source: "grounding",
|
|
85
|
+
level: "warn",
|
|
86
|
+
event: "config_warning",
|
|
87
|
+
sessionId: env?.YAGNI_SESSION_ID,
|
|
88
|
+
fields: { warning },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=grounding.js.map
|
|
@@ -88,6 +88,13 @@ export interface RegisterYagniDeps {
|
|
|
88
88
|
tokenProvider?: TokenProvider;
|
|
89
89
|
/** The spool flush (R4 write half), injectable so tests never touch disk. */
|
|
90
90
|
flushSpool?: (opts: SpoolClientOpts) => Promise<FlushOutcome>;
|
|
91
|
+
/**
|
|
92
|
+
* The `grounding` settings switch seam. Defaults to the real user-config
|
|
93
|
+
* read (top-level `grounding` in ~/.yagni-code/config.json, default true).
|
|
94
|
+
* Inject `false` in tests to assert the gated wiring; inject nothing and
|
|
95
|
+
* the factory never touches disk beyond that one read.
|
|
96
|
+
*/
|
|
97
|
+
grounding?: boolean;
|
|
91
98
|
/**
|
|
92
99
|
* Non-fatal auth-event reporter (YAG-500 Fix E). Defaults to
|
|
93
100
|
* `makeCrashReporter` gated on `!evalMode`; inject a spy in tests to assert
|
package/dist/extension/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { registerCmuxBridge } from "./cmux/index.js";
|
|
|
15
15
|
import { makeRecordEngineeringContextTool } from "./recordContextTool.js";
|
|
16
16
|
import { makeRecordDecisionTool } from "./recordDecisionTool.js";
|
|
17
17
|
import { makeSuggestNextWorkTool } from "./nextWorkTool.js";
|
|
18
|
-
import { BRAND_NAME, brandSystemPrompt, brandingDisabled, buildMastheadString, YAGNI_IDENTITY_DRIVER, YAGNI_IDENTITY_ULTRA } from "./branding.js";
|
|
18
|
+
import { BRAND_NAME, brandSystemPrompt, brandingDisabled, buildMastheadString, YAGNI_IDENTITY_DRIVER, YAGNI_IDENTITY_DRIVER_GROUNDING_FREE, YAGNI_IDENTITY_GROUNDING_FREE, YAGNI_IDENTITY_ULTRA, YAGNI_IDENTITY_ULTRA_GROUNDING_FREE } from "./branding.js";
|
|
19
19
|
import { claudeRulesSection } from "./claudeRules.js";
|
|
20
20
|
import { ensureScratchpadDir, SCRATCHPAD_TMPDIR_ENV, scratchpadDir as scratchpadDirFor, scratchpadSection } from "./scratchpad.js";
|
|
21
21
|
import { registerCostCommand } from "./costHud.js";
|
|
@@ -47,6 +47,7 @@ import { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
|
47
47
|
import { registerTodos } from "./todos.js";
|
|
48
48
|
import { registerDecisionCommands } from "./decisions.js";
|
|
49
49
|
import { makeDecisionCapture } from "./decisionCapture.js";
|
|
50
|
+
import { loadGroundingEnabled } from "./grounding.js";
|
|
50
51
|
import { registerAmbientRecall } from "./recall.js";
|
|
51
52
|
import { resilientFetch } from "./resilientFetch.js";
|
|
52
53
|
import { installUncaughtExceptionMonitor, makeCrashReporter, runningUnderTest } from "./crashReport.js";
|
|
@@ -146,6 +147,27 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
146
147
|
const fetchCatalog = deps.fetchCatalog ?? defaultFetchCatalog;
|
|
147
148
|
const env = deps.env ?? process.env;
|
|
148
149
|
const evalMode = isEvalMode(env);
|
|
150
|
+
// The grounding settings switch (the judgment-loop kill switch): resolved
|
|
151
|
+
// ONCE at boot from the USER-tier config (default on; a malformed config
|
|
152
|
+
// fails soft to on with a sink warn). Eval mode is its own carve-out and
|
|
153
|
+
// stays orthogonal — a scoping/eval session keeps its measured behavior.
|
|
154
|
+
const groundingLoad = deps.grounding !== undefined
|
|
155
|
+
? { enabled: deps.grounding }
|
|
156
|
+
: loadGroundingEnabled(codeStateHome(null, env), env);
|
|
157
|
+
const grounding = groundingLoad.enabled;
|
|
158
|
+
if (!grounding) {
|
|
159
|
+
logEvent({
|
|
160
|
+
source: "grounding",
|
|
161
|
+
level: "info",
|
|
162
|
+
event: "grounding_disabled",
|
|
163
|
+
sessionId: env.YAGNI_SESSION_ID ?? undefined,
|
|
164
|
+
fields: {},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// The fail-soft warn half (a malformed config silently ignored the user's
|
|
168
|
+
// switch): keep it for the session_start surface below — the local sink
|
|
169
|
+
// alone is invisible to the developer who asked for the switch.
|
|
170
|
+
const groundingConfigWarning = groundingLoad.warning;
|
|
149
171
|
// The refreshing TokenProvider replaces the old boot-time token snapshot:
|
|
150
172
|
// tools read the CURRENT token per call, a proactive unref'd timer rotates
|
|
151
173
|
// it near expiry, and `authedFetch` gives every tool's 401 path a single
|
|
@@ -220,7 +242,12 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
220
242
|
// in_progress (Claude's currentTodo?.activeForm pattern). TUI-gated
|
|
221
243
|
// internally (agent_start checks ctx.mode).
|
|
222
244
|
const workingLine = registerWorkingLine(pi, { todoVerb: todosHandle.activeVerb });
|
|
223
|
-
|
|
245
|
+
// The grounding read gate: with `grounding: false`, the judgment loop is
|
|
246
|
+
// severed in both directions — ask_yagni (the consultation read) is never
|
|
247
|
+
// registered, so no recorded decision reaches the model through it.
|
|
248
|
+
if (grounding) {
|
|
249
|
+
pi.registerTool(makeAskYagniTool({ ...toolOpts, flywheel: flywheelState, getRepo: () => sessionRepo }));
|
|
250
|
+
}
|
|
224
251
|
// WebFetch (YAG-578): read an arbitrary URL as clean markdown + a
|
|
225
252
|
// standard-tier extraction, replacing the bash + curl + python dance.
|
|
226
253
|
pi.registerTool(makeWebFetchTool(toolOpts));
|
|
@@ -241,20 +268,27 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
241
268
|
// tasks, advisor consults, Guardian reviews, /go runs) lands here so the
|
|
242
269
|
// footer's session totals include what the per-child receipts print.
|
|
243
270
|
const childUsage = makeChildUsageState();
|
|
244
|
-
|
|
271
|
+
// The advisor consult itself stays available with grounding off (it is
|
|
272
|
+
// model escalation, not a judgment read); the consult just runs BLIND —
|
|
273
|
+
// the existing grounding-free advisor persona + the ask_yagni-free tool
|
|
274
|
+
// list — so no recorded decision reaches the consult either.
|
|
275
|
+
const askAdvisorTool = makeAskAdvisorTool({ state: advisorState, workingLine, childUsage, grounded: grounding });
|
|
245
276
|
pi.registerTool(askAdvisorTool);
|
|
246
277
|
// /advise runs the SAME tool, sharing the state handle, so a manual consult
|
|
247
278
|
// draws on the same cap rather than opening a side channel around it.
|
|
248
279
|
registerAdviseCommand(pi, askAdvisorTool);
|
|
249
280
|
// Structured human questions: the model poses a closed 2-4-option question
|
|
250
281
|
// and gets a clean machine-readable answer via ctx.ui.custom.
|
|
251
|
-
pi.registerTool(makeAskUserQuestionTool());
|
|
282
|
+
pi.registerTool(makeAskUserQuestionTool({ grounded: grounding }));
|
|
252
283
|
// The differentiated business-grounded tools (loop bricks): review a change
|
|
253
284
|
// for business fit, rank the next work by business priority, and record the
|
|
254
|
-
// engineering rationale back onto the work-item.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
285
|
+
// engineering rationale back onto the work-item. All three read the recorded
|
|
286
|
+
// judgment, so they join the grounding gate.
|
|
287
|
+
if (grounding) {
|
|
288
|
+
pi.registerTool(makeReviewBusinessMatchTool(toolOpts));
|
|
289
|
+
pi.registerTool(makeSuggestNextWorkTool(toolOpts));
|
|
290
|
+
}
|
|
291
|
+
if (!evalMode && grounding) {
|
|
258
292
|
pi.registerTool(makeRecordEngineeringContextTool(toolOpts));
|
|
259
293
|
// The capture half of the judgment loop: bank a product-intent decision so
|
|
260
294
|
// ask_yagni answers the same question next time instead of interrupting a human.
|
|
@@ -282,7 +316,7 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
282
316
|
// driver's delegation identity for the diamond directive (see the
|
|
283
317
|
// before_agent_start handler below) and widens the tool's fan-out ceiling.
|
|
284
318
|
const ultraHolder = createUltraHolder();
|
|
285
|
-
registerSubagents(pi, { isUltra: () => ultraHolder.get(), workingLine, childUsage });
|
|
319
|
+
registerSubagents(pi, { isUltra: () => ultraHolder.get(), workingLine, childUsage, grounded: grounding });
|
|
286
320
|
// /plugin + /reload-plugins: manage Claude Code marketplace plugins from the
|
|
287
321
|
// session, driving the launcher's `yagni plugin …` (YAGNI_CLI_PATH).
|
|
288
322
|
registerPluginPanel(pi, { env: deps.env, runCli: deps.runPluginCli });
|
|
@@ -296,6 +330,11 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
296
330
|
// map → plan → implement → review → fix, each child grounded by inheritance.
|
|
297
331
|
registerGoCommand(pi, {
|
|
298
332
|
childUsage,
|
|
333
|
+
// The grounding switch threads into every /go run: `grounded: false`
|
|
334
|
+
// selects the existing blind personas + blind lens clauses (the M6
|
|
335
|
+
// eval lane's machinery — see pipeline/personas.ts) and strips the
|
|
336
|
+
// grounding tools from the stage whitelists via the orchestrator.
|
|
337
|
+
grounded: grounding,
|
|
299
338
|
// /ultra is one dial for the whole session: the same holder the subagent
|
|
300
339
|
// tool reads widens the implement diamond's parallel ceiling (4 -> 8) for
|
|
301
340
|
// the fan and its fix turns. Read per run, so a toggle lands on the next /go.
|
|
@@ -320,7 +359,10 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
320
359
|
});
|
|
321
360
|
// M6 eval (report-only): /go-compare runs a ticket grounded vs blind and reports
|
|
322
361
|
// the business-fit delta. Never wired to routing.
|
|
323
|
-
|
|
362
|
+
// /go-compare measures grounded-vs-blind; with grounding off both lanes
|
|
363
|
+
// would be blind and its delta would lie. Don't register it.
|
|
364
|
+
if (grounding)
|
|
365
|
+
registerGoCompareCommand(pi);
|
|
324
366
|
// YAG-580: /feedback (alias /bug) + /diagnostics. The capture/upload is the
|
|
325
367
|
// whole point, so it is gated to non-eval mode like every external side
|
|
326
368
|
// effect; the command's deps (doctor report, git state, child transcripts)
|
|
@@ -338,8 +380,8 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
338
380
|
// bless-with-remember capture are the same product-intent write as the record
|
|
339
381
|
// tools, so both are gated together (skipped in eval mode).
|
|
340
382
|
const decisionClientOpts = { baseUrl, getToken: getTokenFn, fetchImpl: authedFetch };
|
|
341
|
-
const decisionCapture = evalMode
|
|
342
|
-
if (!evalMode)
|
|
383
|
+
const decisionCapture = !evalMode && grounding ? makeDecisionCapture(decisionClientOpts) : undefined;
|
|
384
|
+
if (!evalMode && grounding)
|
|
343
385
|
registerDecisionCommands(pi, decisionClientOpts);
|
|
344
386
|
// Local MCP servers (standard client, replaces the backend-proxy /mcp of
|
|
345
387
|
// the YAG-446 era): load user/project/local config, approval-gate project
|
|
@@ -973,18 +1015,25 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
973
1015
|
// M1 ambient judgment recall: after a `read`, append the recorded judgment for
|
|
974
1016
|
// that path to the tool result (result modification is supported on pi's
|
|
975
1017
|
// `tool_result` event — verified against 0.80.2). No-op when the corpus is thin
|
|
976
|
-
// (<3 active decisions from /context.counts) or in eval mode
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1018
|
+
// (<3 active decisions from /context.counts) or in eval mode — and never
|
|
1019
|
+
// registered at all under `grounding: false` (recorded decisions must not
|
|
1020
|
+
// reach the model through any seam).
|
|
1021
|
+
if (grounding) {
|
|
1022
|
+
registerAmbientRecall(pi, {
|
|
1023
|
+
baseUrl,
|
|
1024
|
+
getToken: getTokenFn,
|
|
1025
|
+
fetchImpl: authedFetch,
|
|
1026
|
+
decisionsCount: briefResult?.counts?.decisions ?? 0,
|
|
1027
|
+
evalMode,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
984
1030
|
// ADR-0033: the Team-drafting flow is an explicit command, never a first-run
|
|
985
1031
|
// ceremony. /setup-team drafts the Engineering Team + engineering brief from
|
|
986
|
-
// the repo and banks the brief as one decision on approval
|
|
987
|
-
|
|
1032
|
+
// the repo and banks the brief as one decision on approval — a judgment WRITE,
|
|
1033
|
+
// so it joins the grounding gate.
|
|
1034
|
+
if (grounding) {
|
|
1035
|
+
registerTeamSetupCommand(pi, { baseUrl, getToken: getTokenFn, fetchImpl: authedFetch });
|
|
1036
|
+
}
|
|
988
1037
|
// Onramp Door B (spec §5B/§7, as amended by ADR-0033): on a FRESH workspace
|
|
989
1038
|
// (empty/thin grounding corpus), the first run shows a short welcome + one
|
|
990
1039
|
// next-work offer, with free text first-class — instead of a bare prompt. It
|
|
@@ -1088,12 +1137,21 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
1088
1137
|
? undefined
|
|
1089
1138
|
: {
|
|
1090
1139
|
systemPrompt: brandSystemPrompt(event.systemPrompt, {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1140
|
+
// Grounding gate: the boot fetch still ran (fresh-workspace
|
|
1141
|
+
// detection), but with the switch off its text never reaches the
|
|
1142
|
+
// model — no brief block, no ask_yagni nudge.
|
|
1143
|
+
contextBrief: grounding ? contextBrief : undefined,
|
|
1144
|
+
identity: !grounding
|
|
1145
|
+
? isDriver
|
|
1146
|
+
? ultraHolder.get()
|
|
1147
|
+
? YAGNI_IDENTITY_ULTRA_GROUNDING_FREE
|
|
1148
|
+
: YAGNI_IDENTITY_DRIVER_GROUNDING_FREE
|
|
1149
|
+
: YAGNI_IDENTITY_GROUNDING_FREE
|
|
1150
|
+
: isDriver
|
|
1151
|
+
? ultraHolder.get()
|
|
1152
|
+
? YAGNI_IDENTITY_ULTRA
|
|
1153
|
+
: YAGNI_IDENTITY_DRIVER
|
|
1154
|
+
: undefined,
|
|
1097
1155
|
rulesSection,
|
|
1098
1156
|
scratchpadSection: scratchpadSectionText,
|
|
1099
1157
|
}),
|
|
@@ -1336,12 +1394,33 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
1336
1394
|
catch {
|
|
1337
1395
|
// A notice must never disrupt session start.
|
|
1338
1396
|
}
|
|
1397
|
+
// A malformed user config silently forced the grounding default — the
|
|
1398
|
+
// one surface where the user's kill switch was ignored. Same one-shot
|
|
1399
|
+
// notify posture as the expiry notice; never breaks session start.
|
|
1400
|
+
if (groundingConfigWarning) {
|
|
1401
|
+
try {
|
|
1402
|
+
ctx.ui.notify(groundingConfigWarning, "warning");
|
|
1403
|
+
}
|
|
1404
|
+
catch {
|
|
1405
|
+
// A notice must never disrupt session start — but the sink line is
|
|
1406
|
+
// then the ONLY surviving trace of the ignored switch, so record
|
|
1407
|
+
// that the notify itself failed (content-free: the event name).
|
|
1408
|
+
logEvent({
|
|
1409
|
+
source: "grounding",
|
|
1410
|
+
level: "warn",
|
|
1411
|
+
event: "config_warning_notify_failed",
|
|
1412
|
+
sessionId: env.YAGNI_SESSION_ID ?? undefined,
|
|
1413
|
+
fields: {},
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1339
1417
|
}
|
|
1340
1418
|
// R4 write half: drain any judgment writes spooled by a previous session
|
|
1341
1419
|
// (transport/5xx failures of record_decision / record_engineering_context).
|
|
1342
1420
|
// Fire-and-forget and fail-soft — a flush must never delay or break session
|
|
1343
1421
|
// start; honest notices (replays, expiry drops) surface when it finishes.
|
|
1344
|
-
|
|
1422
|
+
// A replay IS a capture write, so the grounding switch skips it entirely.
|
|
1423
|
+
if (!evalMode && grounding) {
|
|
1345
1424
|
void flushSpoolFn({ baseUrl, getToken: getTokenFn, fetchImpl: authedFetch })
|
|
1346
1425
|
.then((outcome) => {
|
|
1347
1426
|
if (!ctx.hasUI)
|
|
@@ -1361,6 +1440,7 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
1361
1440
|
getToken: getTokenFn,
|
|
1362
1441
|
fetchImpl: authedFetch,
|
|
1363
1442
|
brief: briefResult,
|
|
1443
|
+
grounding,
|
|
1364
1444
|
});
|
|
1365
1445
|
// F2a: mark the workspace init-done on any real run (ran === true — thin
|
|
1366
1446
|
// or not), so it never re-seeds. A skip (not_fresh / non_interactive) is
|
|
@@ -1375,8 +1455,9 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
1375
1455
|
// Run 7: the opt-in repo seeding beat. Fires only on a genuine startup
|
|
1376
1456
|
// when /context reported an explicitly-empty repo-scoped ledger, once per
|
|
1377
1457
|
// repo (marker store). Fully fail-soft — a beat failure never blocks the
|
|
1378
|
-
// session.
|
|
1379
|
-
|
|
1458
|
+
// session. Never offered under `grounding: false` — mining banks decisions
|
|
1459
|
+
// (a judgment write).
|
|
1460
|
+
if (event.reason === "startup" && !evalMode && grounding) {
|
|
1380
1461
|
try {
|
|
1381
1462
|
const offerBeat = deps.offerMiningBeat ?? defaultMaybeOfferMiningBeat;
|
|
1382
1463
|
const markers = deps.mineBeatMarkers ?? fileMineBeatMarkers(codeStateHome(null, env));
|
|
@@ -96,6 +96,13 @@ export interface RunInitPassDeps {
|
|
|
96
96
|
baseUrl: string;
|
|
97
97
|
getToken: () => string | undefined;
|
|
98
98
|
fetchImpl?: typeof fetch;
|
|
99
|
+
/**
|
|
100
|
+
* The boot-time grounding switch: with grounding off the welcome stays (it
|
|
101
|
+
* records nothing) but skips the next-work offer — `suggest_next_work` is
|
|
102
|
+
* unregistered, so the offer would steer to a dead surface — and shows a
|
|
103
|
+
* grounding-free notice. Defaults grounded.
|
|
104
|
+
*/
|
|
105
|
+
grounding?: boolean;
|
|
99
106
|
/**
|
|
100
107
|
* A pre-fetched context brief (the caller usually already fetched it to boot
|
|
101
108
|
* grounded). When `undefined`, the pass fetches it itself. Pass `null`
|
|
@@ -231,6 +231,10 @@ const WELCOME_NOTICE = "First run in this repo. Just type to start working. Ask
|
|
|
231
231
|
"context (tickets, docs, decisions), or run /go <ticket> for a ticket-to-PR run.";
|
|
232
232
|
/** The free-text choice: an untouched editor, ready for whatever they want to build. */
|
|
233
233
|
const FREE_TEXT_LABEL = "Just start typing";
|
|
234
|
+
/** The grounding-free welcome: no company-context advertising (the switch
|
|
235
|
+
* turned that surface off), just the doors that still exist. */
|
|
236
|
+
const WELCOME_NOTICE_GROUNDING_FREE = "First run in this repo. Just type to start working, or run /go <ticket> " +
|
|
237
|
+
"for a ticket-to-PR run.";
|
|
234
238
|
/**
|
|
235
239
|
* Run the first-run welcome (ADR-0033). Guards on fresh-workspace detection
|
|
236
240
|
* (defensive — the caller also guards), shows a short how-to-work-with-YAGNI
|
|
@@ -256,8 +260,14 @@ export async function runInitPass(pi, ctx, deps) {
|
|
|
256
260
|
const ui = (ctx.hasUI ? ctx.ui : undefined);
|
|
257
261
|
if (!ui)
|
|
258
262
|
return { ran: false, reason: "non_interactive" };
|
|
263
|
+
const grounding = deps.grounding !== false;
|
|
259
264
|
const notify = safeNotify(ui);
|
|
260
|
-
notify(WELCOME_NOTICE, "info");
|
|
265
|
+
notify(grounding ? WELCOME_NOTICE : WELCOME_NOTICE_GROUNDING_FREE, "info");
|
|
266
|
+
if (!grounding) {
|
|
267
|
+
// The offer's primary action is `suggest_next_work` — unregistered under
|
|
268
|
+
// the switch. Skip the offer; the editor stays ready for free text.
|
|
269
|
+
return { ran: true, chosenAction: "free_text" };
|
|
270
|
+
}
|
|
261
271
|
// 2. Offer the ONE default next action (spec §7.3: defaults over choices),
|
|
262
272
|
// with free text as a first-class door. Everything here is best-effort:
|
|
263
273
|
// whatever happens, the user ends at a prompt they can just type into.
|
|
@@ -136,6 +136,14 @@ export interface RegisterGoDeps {
|
|
|
136
136
|
* test) simply means "not ultra", which is today's behavior.
|
|
137
137
|
*/
|
|
138
138
|
isUltra?: () => boolean;
|
|
139
|
+
/**
|
|
140
|
+
* The boot-time `grounding` settings switch, threaded into the orchestrator's
|
|
141
|
+
* `grounded` flag: false runs the whole pipeline on the existing blind
|
|
142
|
+
* personas + blind lens clauses (the M6 eval lane's machinery) with the
|
|
143
|
+
* grounding tools stripped from the stage whitelists. Defaults true — an
|
|
144
|
+
* unwired embedder or test keeps today's grounded behavior.
|
|
145
|
+
*/
|
|
146
|
+
grounded?: boolean;
|
|
139
147
|
baseUrl?: string;
|
|
140
148
|
getToken?: () => string | undefined;
|
|
141
149
|
/**
|
|
@@ -432,6 +432,9 @@ export function registerGoCommand(pi, deps = {}) {
|
|
|
432
432
|
// The dial is held, never its value: /ultra can be toggled between runs, so
|
|
433
433
|
// the pipeline calls it when it needs the ceiling rather than reading it here.
|
|
434
434
|
const isUltra = deps.isUltra;
|
|
435
|
+
// The boot-time grounding switch (defaults grounded): false runs the
|
|
436
|
+
// pipeline on the blind personas via the orchestrator's `grounded` flag.
|
|
437
|
+
const goGrounded = deps.grounded !== false;
|
|
435
438
|
// Task 8: no default — see the RegisterGoDeps doc comment for why (only
|
|
436
439
|
// index.ts's real wiring makes sense here; absent, `resolveCostText` falls
|
|
437
440
|
// back to the local client estimate).
|
|
@@ -448,7 +451,9 @@ export function registerGoCommand(pi, deps = {}) {
|
|
|
448
451
|
getToken: deps.getToken ?? defaultGetToken,
|
|
449
452
|
});
|
|
450
453
|
pi.registerCommand("go", {
|
|
451
|
-
description:
|
|
454
|
+
description: goGrounded
|
|
455
|
+
? "Run the grounded multi-agent pipeline (map → plan → implement → review → fix) on a ticket and produce a reviewed change."
|
|
456
|
+
: "Run the multi-agent pipeline (map → plan → implement → review → fix) on a ticket and produce a reviewed change.",
|
|
452
457
|
handler: async (args, ctx) => {
|
|
453
458
|
const notify = (message, type) => {
|
|
454
459
|
if (ctx.hasUI)
|
|
@@ -910,6 +915,7 @@ export function registerGoCommand(pi, deps = {}) {
|
|
|
910
915
|
cwd: runCwd,
|
|
911
916
|
signal: runSignal,
|
|
912
917
|
fanout: fanoutMode,
|
|
918
|
+
grounded: goGrounded,
|
|
913
919
|
...(isUltra ? { isUltra } : {}),
|
|
914
920
|
...(ticketBrief ? { ticketBrief } : {}),
|
|
915
921
|
onProgress: (p) => {
|
|
@@ -52,6 +52,8 @@ import { resolveFanoutMode } from "./fanout.js";
|
|
|
52
52
|
import { makeFanoutBeats } from "./fanoutBeats.js";
|
|
53
53
|
import { normalizeMission } from "./mission.js";
|
|
54
54
|
import { runPipeline as defaultRunPipeline } from "./orchestrator.js";
|
|
55
|
+
import { loadGroundingEnabled } from "../grounding.js";
|
|
56
|
+
import { codeStateHome } from "../stateHome.js";
|
|
55
57
|
import { parseTierCap, TIER_CAP_ENV } from "./tierCap.js";
|
|
56
58
|
/** One-line usage copy, shared by every argument error. */
|
|
57
59
|
export const HEADLESS_GO_USAGE = "Usage: yagni go --headless --ticket-file <path> [--plan-file <path>] [--memo-file <path>] [--run-id <id>] [--cwd <path>] [--json]";
|
|
@@ -202,6 +204,11 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
202
204
|
const readFile = deps.readFile ?? ((path) => readFileSync(path, "utf8"));
|
|
203
205
|
const runPipeline = deps.runPipeline ?? defaultRunPipeline;
|
|
204
206
|
const env = deps.env ?? process.env;
|
|
207
|
+
// The grounding switch's default for a headless run: the user-tier config
|
|
208
|
+
// (the same key the interactive extension reads at boot). An explicit
|
|
209
|
+
// deps.grounded (an eval lane pinning its lane) always wins — the eval's
|
|
210
|
+
// measurement determinism is untouched.
|
|
211
|
+
const grounding = deps.grounded ?? loadGroundingEnabled(codeStateHome(null, env), env).enabled;
|
|
205
212
|
const args = parseHeadlessGoArgs(argv);
|
|
206
213
|
const problem = validateHeadlessGoArgs(args);
|
|
207
214
|
if (problem) {
|
|
@@ -278,7 +285,7 @@ export async function runHeadlessGo(argv, deps = {}) {
|
|
|
278
285
|
...(deps.signal ? { signal: deps.signal } : {}),
|
|
279
286
|
...(deps.budget ? { budget: deps.budget } : {}),
|
|
280
287
|
...(deps.stages ? { stages: deps.stages } : {}),
|
|
281
|
-
|
|
288
|
+
grounded: deps.grounded ?? grounding,
|
|
282
289
|
...(mission ? { mission } : {}),
|
|
283
290
|
onEvent: (ev, tag) => {
|
|
284
291
|
deps.onEvent?.(ev, tag);
|
|
@@ -72,12 +72,9 @@ export interface DiscoverDeps {
|
|
|
72
72
|
*/
|
|
73
73
|
export declare function discoverSubagents(deps: DiscoverDeps): SubagentDef[];
|
|
74
74
|
export declare function formatAgentList(agents: SubagentDef[]): string;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* the runner's personaBody seam.
|
|
79
|
-
*/
|
|
80
|
-
export declare function buildSubagentStage(def: SubagentDef, task: string): {
|
|
75
|
+
export declare function buildSubagentStage(def: SubagentDef, task: string, opts?: {
|
|
76
|
+
grounded?: boolean;
|
|
77
|
+
}): {
|
|
81
78
|
stage: PipelineStage;
|
|
82
79
|
ctx: {
|
|
83
80
|
ticket: string;
|
|
@@ -98,6 +95,13 @@ export interface MakeSubagentToolDeps {
|
|
|
98
95
|
homeDir?: string;
|
|
99
96
|
/** Live ultra-mode probe (/ultra): widens the per-call fan-out ceiling. */
|
|
100
97
|
isUltra?: () => boolean;
|
|
98
|
+
/**
|
|
99
|
+
* The boot-time grounding switch (defaults grounded): false spawns every
|
|
100
|
+
* subagent with the grounding read stripped from its tool list and the
|
|
101
|
+
* grounding-free twin body for BUILTIN agents (user/project bodies ride
|
|
102
|
+
* verbatim — they are the user's own content).
|
|
103
|
+
*/
|
|
104
|
+
grounded?: boolean;
|
|
101
105
|
/**
|
|
102
106
|
* The session working-line manager (workingLine.ts). When present, live
|
|
103
107
|
* progress goes through it (so the elapsed/token suffix survives); absent,
|
|
@@ -157,6 +161,8 @@ export interface RegisterSubagentsDeps {
|
|
|
157
161
|
homeDir?: string;
|
|
158
162
|
/** Live ultra-mode probe (/ultra): widens the per-call fan-out ceiling. */
|
|
159
163
|
isUltra?: () => boolean;
|
|
164
|
+
/** The boot-time grounding switch; false spawns blind subagents. */
|
|
165
|
+
grounded?: boolean;
|
|
160
166
|
/** Session working-line manager; see MakeSubagentToolDeps.workingLine. */
|
|
161
167
|
workingLine?: WorkingLineHandle;
|
|
162
168
|
/** Session child-usage accumulator; see MakeSubagentToolDeps.childUsage. */
|
|
@@ -318,16 +318,98 @@ export function formatAgentList(agents) {
|
|
|
318
318
|
* `{ticket}` verbatim, and the agent's body replaces the /go persona through
|
|
319
319
|
* the runner's personaBody seam.
|
|
320
320
|
*/
|
|
321
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Grounding-free twins of the four builtin bodies: the same capable roles
|
|
323
|
+
* with the "You are grounded in how THIS company works: call ask_yagni…"
|
|
324
|
+
* clause removed (the blind-lane pattern — separate constants, so the
|
|
325
|
+
* grounded prompt text stays byte-identical). Only BUILTIN agents have
|
|
326
|
+
* twins; user/project-defined bodies are the user's own content and ride
|
|
327
|
+
* verbatim.
|
|
328
|
+
*/
|
|
329
|
+
const GENERAL_BODY_GROUNDING_FREE = `You are a capable software-engineering subagent with a fresh context window. Complete the task you are given end to end, autonomously.
|
|
330
|
+
|
|
331
|
+
Never fabricate file paths, contents, or findings. If you cannot find something, say so.
|
|
332
|
+
|
|
333
|
+
Complete the task fully — do not gold-plate, but do not leave it half-done. Your final message is your report back to the driving agent, which has NOT seen what you read or did: make it a concise report of what was done and the key findings, since the caller relays it to the user and it only needs the essentials. Cover what you did, what you found, exact file paths and key excerpts, and anything the driver must know before continuing.`;
|
|
334
|
+
const SEARCHER_BODY_GROUNDING_FREE = `You are a repo scout. Your job is wide, mechanical reconnaissance:
|
|
335
|
+
find files, map structure, trace usages, and summarize what is there. You do
|
|
336
|
+
not write code and you do not run commands; you read and report.
|
|
337
|
+
|
|
338
|
+
Never fabricate file paths, contents, or code. Every path you cite must be
|
|
339
|
+
one you actually read with a tool. If you cannot find something, say "not
|
|
340
|
+
found" — a plausible-sounding invention is worse than no answer because the
|
|
341
|
+
driving agent trusts your report.
|
|
342
|
+
|
|
343
|
+
Complete the task fully — do not gold-plate, but do not leave it half-done.
|
|
344
|
+
|
|
345
|
+
Your final message is your report back to the driving agent, which has NOT
|
|
346
|
+
seen what you read. Make it compressed and complete: exact file paths, the
|
|
347
|
+
key excerpts, and a one-paragraph map of how the pieces relate. Say what you
|
|
348
|
+
did NOT find as plainly as what you found.`;
|
|
349
|
+
const IMPLEMENTER_BODY_GROUNDING_FREE = `You are a mechanical implementer. You execute a
|
|
350
|
+
well-specified change: apply an edit across files, fix a failing test, rename
|
|
351
|
+
carefully, wire a defined seam. The judgment calls were made before you were
|
|
352
|
+
spawned; if the task turns out to require one, STOP and report the fork in
|
|
353
|
+
your final message instead of guessing.
|
|
354
|
+
|
|
355
|
+
Never fabricate file paths or results. Report what you actually did and what
|
|
356
|
+
you actually found.
|
|
357
|
+
|
|
358
|
+
Complete the task fully — do not gold-plate, but do not leave it half-done.
|
|
359
|
+
|
|
360
|
+
Your final message is your report back to the driving agent, which has NOT
|
|
361
|
+
seen what you did. List every file you touched, what changed in each, the
|
|
362
|
+
commands you ran with their outcomes, and anything you deliberately left
|
|
363
|
+
undone.`;
|
|
364
|
+
const VERIFICATION_BODY_GROUNDING_FREE = `You are an adversarial verifier. Another agent
|
|
365
|
+
produced work — a change, a plan, or a claim — and your job is to try to
|
|
366
|
+
BREAK it, not to summarize it. Default to skepticism: hunt for the concrete
|
|
367
|
+
failure scenario (the inputs, state, or sequence that makes it wrong). Bash
|
|
368
|
+
is read-only here (\`git diff\`, \`git log\`, \`git show\`); do NOT modify files
|
|
369
|
+
or run builds.
|
|
370
|
+
|
|
371
|
+
If your task names a lens (correctness, edge cases, codebase fit, security,
|
|
372
|
+
…), judge ONLY through that lens and leave the rest to your sibling
|
|
373
|
+
verifiers.
|
|
374
|
+
|
|
375
|
+
Never fabricate file paths or findings. If you could not verify something,
|
|
376
|
+
say exactly what you tried and why you could not.
|
|
377
|
+
|
|
378
|
+
Complete the task fully — do not gold-plate, but do not leave it half-done.
|
|
379
|
+
|
|
380
|
+
Your final message is your verdict back to the driving agent, which has NOT
|
|
381
|
+
seen what you read. Format:
|
|
382
|
+
## Verdict
|
|
383
|
+
BROKEN or HOLDS, with one sentence why.
|
|
384
|
+
## Findings
|
|
385
|
+
Each real problem: file:line, the concrete failure scenario, severity. No
|
|
386
|
+
style nits.
|
|
387
|
+
## Not verified
|
|
388
|
+
What you could not check, and why.
|
|
389
|
+
|
|
390
|
+
A HOLDS after real digging is valuable; a rubber stamp is not. If you found
|
|
391
|
+
nothing, say exactly what you tried to break and how.`;
|
|
392
|
+
/** Builtin-body twins keyed by agent name; absent = not a builtin. */
|
|
393
|
+
const BUILTIN_BLIND_BODIES = {
|
|
394
|
+
[GENERAL_AGENT_NAME]: GENERAL_BODY_GROUNDING_FREE,
|
|
395
|
+
searcher: SEARCHER_BODY_GROUNDING_FREE,
|
|
396
|
+
implementer: IMPLEMENTER_BODY_GROUNDING_FREE,
|
|
397
|
+
verification: VERIFICATION_BODY_GROUNDING_FREE,
|
|
398
|
+
};
|
|
399
|
+
/** The canonical grounding-strip filter lives in grounding.ts; one home,
|
|
400
|
+
* shared with the advisor consults. */
|
|
401
|
+
import { stripGroundingTool } from "./grounding.js";
|
|
402
|
+
export function buildSubagentStage(def, task, opts = {}) {
|
|
403
|
+
const grounded = opts.grounded !== false;
|
|
322
404
|
return {
|
|
323
405
|
stage: {
|
|
324
406
|
id: "implement",
|
|
325
407
|
agent: def.name,
|
|
326
408
|
model: def.model,
|
|
327
|
-
tools: def.tools ?? DEFAULT_SUBAGENT_TOOLS,
|
|
409
|
+
tools: grounded ? def.tools ?? DEFAULT_SUBAGENT_TOOLS : stripGroundingTool(def.tools ?? DEFAULT_SUBAGENT_TOOLS),
|
|
328
410
|
taskTemplate: "{ticket}",
|
|
329
411
|
},
|
|
330
|
-
ctx: { ticket: task, grounded
|
|
412
|
+
ctx: { ticket: task, grounded },
|
|
331
413
|
};
|
|
332
414
|
}
|
|
333
415
|
const parameters = Type.Object({
|
|
@@ -351,6 +433,15 @@ export function makeSubagentTool(deps = {}) {
|
|
|
351
433
|
// landed a partial edit.
|
|
352
434
|
const run = deps.runStageImpl ?? withResilience(runStage, DEFAULT_RESILIENCE_POLICY);
|
|
353
435
|
const discover = deps.discover ?? discoverSubagents;
|
|
436
|
+
const grounded = deps.grounded !== false;
|
|
437
|
+
// Resolve the effective body for a spawned subagent: the grounding-free
|
|
438
|
+
// twin for builtins under `grounding: false`; the definition's own body
|
|
439
|
+
// everywhere else (user/project content rides verbatim).
|
|
440
|
+
const bodyFor = (def) => {
|
|
441
|
+
if (grounded || def.source !== "builtin")
|
|
442
|
+
return def.body;
|
|
443
|
+
return BUILTIN_BLIND_BODIES[def.name] ?? def.body;
|
|
444
|
+
};
|
|
354
445
|
return {
|
|
355
446
|
name: SUBAGENT_TOOL_NAME,
|
|
356
447
|
label: "Subagent",
|
|
@@ -423,11 +514,11 @@ export function makeSubagentTool(deps = {}) {
|
|
|
423
514
|
try {
|
|
424
515
|
outcomes = await Promise.all(resolved.map(async ({ def, task }, index) => {
|
|
425
516
|
const progress = progresses[index];
|
|
426
|
-
const { stage, ctx: stageCtx } = buildSubagentStage(def, task);
|
|
517
|
+
const { stage, ctx: stageCtx } = buildSubagentStage(def, task, { grounded });
|
|
427
518
|
const result = await run(stage, stageCtx, {
|
|
428
519
|
cwd,
|
|
429
520
|
signal,
|
|
430
|
-
personaBody: () => def
|
|
521
|
+
personaBody: () => bodyFor(def),
|
|
431
522
|
// YAG-471: attribute this child's completions to the specific
|
|
432
523
|
// subagent, not the generic /go stage label the runner would
|
|
433
524
|
// otherwise derive from stage.id ("implement", reused as the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.1.1-staging.
|
|
3
|
+
"version": "1.1.1-staging.1359.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"turndown": "^7.2.4",
|
|
59
59
|
"typebox": "^1.3.15"
|
|
60
60
|
},
|
|
61
|
-
"yagniSourceSha": "
|
|
61
|
+
"yagniSourceSha": "beb4c9e3abfde8628b72e5b434d05ee2835f4bfc"
|
|
62
62
|
}
|