@yagni-app/code-staging 1.2.13-staging.2158.1 → 1.2.13-staging.2171.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.
@@ -35,7 +35,31 @@ export interface PlanFile {
35
35
  repository: string;
36
36
  summary: string;
37
37
  }>;
38
+ /** The repository the change belongs in instead, when it is not this one (ENG-2966). */
39
+ wrongRepository?: {
40
+ repository: string;
41
+ why: string;
42
+ confidence: "high" | "low";
43
+ };
44
+ /** The other repositories that need a change before this one can work (ENG-3033). */
45
+ dependsOn: Array<{
46
+ repository: string;
47
+ what: string;
48
+ }>;
49
+ /**
50
+ * What the plan found that the ticket did not ask for (ENG-2966): kept off
51
+ * the plan, shown at its gate for a person to make a ticket of.
52
+ */
53
+ followUps: Array<{
54
+ title: string;
55
+ why: string;
56
+ repository?: string;
57
+ }>;
38
58
  }
59
+ /** The plan's `wrongRepository`, or null when it names no `owner/name`. Confidence is high only when said. */
60
+ export declare function readWrongRepository(value: unknown): PlanFile["wrongRepository"] | null;
61
+ /** The plan's `dependsOn`, one entry per distinct repository, in the order written. */
62
+ export declare function readDependsOn(value: unknown): PlanFile["dependsOn"];
39
63
  /** An `owner/name` repository the plan names, or null when it is not one. */
40
64
  export declare function repositoryName(value: unknown): string | null;
41
65
  /**
@@ -44,6 +68,12 @@ export declare function repositoryName(value: unknown): string | null;
44
68
  * for the same repository (case-insensitive) is dropped too.
45
69
  */
46
70
  export declare function readRepositoriesNeeded(value: unknown): PlanFile["repositoriesNeeded"];
71
+ /**
72
+ * The plan's `followUps`, one per distinct title in the order written. An
73
+ * entry with no title is dropped; a repository that is not `owner/name` is
74
+ * left off its entry.
75
+ */
76
+ export declare function readFollowUps(value: unknown): PlanFile["followUps"];
47
77
  /** The plan's ```plan-json block as an object, or null when it has none that parses. */
48
78
  export declare function extractPlanJson(text: string): Record<string, unknown> | null;
49
79
  /** The plan for a person to read: the text without its machine-readable block. */
@@ -19,6 +19,34 @@ const MAX_TEXT = 500;
19
19
  * same number as `MAX_SPLIT_CHILDREN` in @yagni/shared, kept here because this package
20
20
  * does not depend on it. */
21
21
  const MAX_REPOSITORIES_NEEDED = 12;
22
+ /** The plan's `wrongRepository`, or null when it names no `owner/name`. Confidence is high only when said. */
23
+ export function readWrongRepository(value) {
24
+ if (!isRecord(value))
25
+ return null;
26
+ const repository = repositoryName(value.repository);
27
+ if (!repository)
28
+ return null;
29
+ return {
30
+ repository,
31
+ why: text(value.why) ?? "",
32
+ confidence: value.confidence === "high" ? "high" : "low",
33
+ };
34
+ }
35
+ /** The plan's `dependsOn`, one entry per distinct repository, in the order written. */
36
+ export function readDependsOn(value) {
37
+ const out = [];
38
+ const seen = new Set();
39
+ for (const entry of list(value)) {
40
+ const repository = isRecord(entry) ? repositoryName(entry.repository) : null;
41
+ if (!repository || seen.has(repository.toLowerCase()))
42
+ continue;
43
+ seen.add(repository.toLowerCase());
44
+ out.push({ repository, what: (isRecord(entry) && text(entry.what)) || "" });
45
+ if (out.length === MAX_REPOSITORIES_NEEDED)
46
+ break;
47
+ }
48
+ return out;
49
+ }
22
50
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23
51
  /** An `owner/name` repository the plan names, or null when it is not one. */
24
52
  export function repositoryName(value) {
@@ -49,6 +77,30 @@ export function readRepositoriesNeeded(value) {
49
77
  }
50
78
  return out;
51
79
  }
80
+ /** At most this many follow-ups; the host keeps the same number. */
81
+ const MAX_FOLLOW_UPS = 10;
82
+ /**
83
+ * The plan's `followUps`, one per distinct title in the order written. An
84
+ * entry with no title is dropped; a repository that is not `owner/name` is
85
+ * left off its entry.
86
+ */
87
+ export function readFollowUps(value) {
88
+ const out = [];
89
+ const seen = new Set();
90
+ for (const entry of list(value)) {
91
+ if (!isRecord(entry))
92
+ continue;
93
+ const title = text(entry.title, 200);
94
+ if (!title || seen.has(title.toLowerCase()))
95
+ continue;
96
+ seen.add(title.toLowerCase());
97
+ const repository = repositoryName(entry.repository);
98
+ out.push({ title, why: text(entry.why) ?? "", ...(repository ? { repository } : {}) });
99
+ if (out.length === MAX_FOLLOW_UPS)
100
+ break;
101
+ }
102
+ return out;
103
+ }
52
104
  /** The plan's ```plan-json block as an object, or null when it has none that parses. */
53
105
  export function extractPlanJson(text) {
54
106
  let found = null;
@@ -123,8 +175,19 @@ export function buildPlanFile(planText, fallbackPaths) {
123
175
  .filter((path) => path !== null);
124
176
  const paths = [...new Set(named.length > 0 ? named : fallbackPaths())].slice(0, MAX_LIST);
125
177
  const repositoriesNeeded = readRepositoriesNeeded(block?.repositoriesNeeded);
178
+ const wrongRepository = readWrongRepository(block?.wrongRepository);
126
179
  return {
127
- file: { version: 1, questions, decisionsCited, checks, paths, repositoriesNeeded },
180
+ file: {
181
+ version: 1,
182
+ questions,
183
+ decisionsCited,
184
+ checks,
185
+ paths,
186
+ repositoriesNeeded,
187
+ ...(wrongRepository ? { wrongRepository } : {}),
188
+ dependsOn: readDependsOn(block?.dependsOn),
189
+ followUps: readFollowUps(block?.followUps),
190
+ },
128
191
  found: block !== null,
129
192
  };
130
193
  }
@@ -68,6 +68,20 @@ export declare const DECISIONS_READ_TOOL = "yagni_decisions_read_for_repository"
68
68
  * planner left out.
69
69
  */
70
70
  export declare const DOCUMENTATION_REPOSITORY_RULE: string;
71
+ /**
72
+ * Where the change belongs (ENG-2966, ENG-3033): a ticket routed to the wrong
73
+ * repository is said so, not planned around, and work this one cannot do
74
+ * without another repository is named, not left as a Risks bullet.
75
+ */
76
+ export declare const RIGHT_REPOSITORY_RULE: string;
77
+ /**
78
+ * What the planner does with what it finds that the ticket did not ask for
79
+ * (ENG-2966): a nearby bug, a leak, a refactor is a follow-up a person can
80
+ * make a ticket of, never a question, a decision or a step of this plan. And
81
+ * nothing the ticket asked for is put off: work it needs in another
82
+ * repository goes in repositoriesNeeded, so the host offers the split.
83
+ */
84
+ export declare const FOLLOW_UPS_RULE: string;
71
85
  export declare const PLAN_JSON_CONTRACT: string;
72
86
  /**
73
87
  * The one-attempt lane's session (`yagni go --headless --cloud`): plan,
@@ -154,6 +154,33 @@ export const DECISIONS_READ_TOOL = "yagni_decisions_read_for_repository";
154
154
  export const DOCUMENTATION_REPOSITORY_RULE = "When a documentation repository in Other repositories documents this one and the change alters " +
155
155
  "behaviour its pages describe, list it in repositoriesNeeded too, with one line on the pages that " +
156
156
  "change. It is built beside the code, not after it.";
157
+ /**
158
+ * Where the change belongs (ENG-2966, ENG-3033): a ticket routed to the wrong
159
+ * repository is said so, not planned around, and work this one cannot do
160
+ * without another repository is named, not left as a Risks bullet.
161
+ */
162
+ export const RIGHT_REPOSITORY_RULE = '- "wrongRepository" is for a ticket whose change belongs in another repository the Other ' +
163
+ "repositories section lists (the code it asks to change is there, not here). Name that " +
164
+ 'repository, say why in one line, and give "confidence": "high" only when you read the code ' +
165
+ "there or the ticket names it. Do not plan around it in this repository and do not call this " +
166
+ "one the main repository; plan only what this repository needs. Leave it out when the change is here.\n" +
167
+ '- "dependsOn" lists every other repository that needs a change before this one can work, each ' +
168
+ "with one line on what it needs there, whether or not the Other repositories section lists it. " +
169
+ "Such a dependency is never only a Risks bullet. Never list a repository the change only reads.";
170
+ /**
171
+ * What the planner does with what it finds that the ticket did not ask for
172
+ * (ENG-2966): a nearby bug, a leak, a refactor is a follow-up a person can
173
+ * make a ticket of, never a question, a decision or a step of this plan. And
174
+ * nothing the ticket asked for is put off: work it needs in another
175
+ * repository goes in repositoriesNeeded, so the host offers the split.
176
+ */
177
+ export const FOLLOW_UPS_RULE = '- "followUps" lists what you found that the ticket did not ask for: a nearby bug, a security ' +
178
+ "leak, a refactor. Each is a title, one line on why it matters, and its repository when that is " +
179
+ "not this one. A follow-up is never a question, a decision or a step of this plan, however real " +
180
+ "it is: a person makes a ticket of it if they want one. Leave it out when you found nothing.\n" +
181
+ '- Never put off the ticket\'s own work ("for another session", "in a later ticket"). Plan all of ' +
182
+ "what it asked for. Work it needs in another repository goes in repositoriesNeeded, so the ticket " +
183
+ "is split, and is never left out of the plan.";
157
184
  export const PLAN_JSON_CONTRACT = "End your answer with a fenced ```plan-json block and nothing after it:\n" +
158
185
  "```plan-json\n" +
159
186
  "{\n" +
@@ -166,12 +193,16 @@ export const PLAN_JSON_CONTRACT = "End your answer with a fenced ```plan-json bl
166
193
  ' "decisionsCited": [{"id": "<the decision id the read returned>", "why": "one line"}],\n' +
167
194
  ' "checks": [{"name": "lint", "command": "pnpm lint", "cwd": "apps/web"}],\n' +
168
195
  ' "paths": ["apps/web"],\n' +
169
- ' "repositoriesNeeded": [{"repository": "owner/name", "summary": "one line on what changes there"}]\n' +
196
+ ' "repositoriesNeeded": [{"repository": "owner/name", "summary": "one line on what changes there"}],\n' +
197
+ ' "wrongRepository": {"repository": "owner/name", "why": "one line", "confidence": "high|low"},\n' +
198
+ ' "dependsOn": [{"repository": "owner/name", "what": "one line on what it needs there"}],\n' +
199
+ ' "followUps": [{"title": "...", "why": "one line", "repository": "owner/name"}]\n' +
170
200
  "}\n" +
171
201
  "```\n" +
172
202
  "Rules the host enforces, so getting them wrong loses the question:\n" +
173
203
  '- "kind" is "fact" when the answer could be looked up and "decision" when it is a person\'s call. ' +
174
- 'A decision you would make yourself carries your recommendation and is stated, not asked; mark "oneWay": true only when it cannot be undone.\n' +
204
+ 'A decision you would make yourself carries your recommendation and is stated, not asked; mark "oneWay": true only when it cannot be undone. ' +
205
+ "A recommendation whose planChange adds acceptance criteria widens the ticket, so a person is asked it.\n" +
175
206
  '- On a fact, "checked" says in at most 4 short lines what you read or ran for it and what it showed, and what you could not read. ' +
176
207
  "A fact whose every answer plans the same work (no option carries a planChange) is stated as your assumption, not asked.\n" +
177
208
  "- At most two alternatives per question. A planChange only adds or rewords; it never removes.\n" +
@@ -183,7 +214,11 @@ export const PLAN_JSON_CONTRACT = "End your answer with a fenced ```plan-json bl
183
214
  "with one line on what changes there. Only a repository the ticket's Other repositories section lists " +
184
215
  "counts; the host drops any other name. Leave it out for a change inside this repository alone, and " +
185
216
  "never list a repository the change only reads. " +
186
- DOCUMENTATION_REPOSITORY_RULE;
217
+ DOCUMENTATION_REPOSITORY_RULE +
218
+ "\n" +
219
+ RIGHT_REPOSITORY_RULE +
220
+ "\n" +
221
+ FOLLOW_UPS_RULE;
187
222
  /**
188
223
  * The cloud plan stage's task: the input folder the host wrote, read first,
189
224
  * then a plan grounded in the installed checkout, inside the stage's own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "1.2.13-staging.2158.1",
3
+ "version": "1.2.13-staging.2171.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": "b6c406df664ab9bf4977c116c0fe9e30ea4bc631"
61
+ "yagniSourceSha": "4c225e34b3364d25830d9d4d88f071ffedf799f3"
62
62
  }