@yagni-app/code-staging 1.1.1-staging.1355.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/permission/approvedPrefixes.d.ts +3 -1
- package/dist/extension/permission/approvedPrefixes.js +140 -21
- package/dist/extension/permission/gate.js +59 -19
- 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/sandbox/bash.js +4 -4
- package/dist/extension/sandbox/config.d.ts +13 -2
- package/dist/extension/sandbox/config.js +49 -6
- package/dist/extension/sandbox/panel.d.ts +5 -2
- package/dist/extension/sandbox/panel.js +4 -4
- 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
|