@rmrdeveloper/sideroom-pi 7.0.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,9 +9,17 @@ export const CUSTOM_LABEL = 'Write a custom answer';
9
9
  export const UI_UNAVAILABLE =
10
10
  'Error: UI not available (running in non-interactive mode)';
11
11
 
12
+ const MAX_QUESTIONS = 4;
13
+ const MAX_OPTIONS = 4;
14
+ const MAX_TAB_LABEL_LENGTH = 16;
15
+ const MAX_OPTION_LABEL_LENGTH = 60;
16
+
12
17
  const QuestionOptionSchema = Type.Object({
13
18
  value: Type.String({ description: 'The value returned when selected' }),
14
- label: Type.String({ description: 'Display label for the option' }),
19
+ label: Type.String({
20
+ maxLength: MAX_OPTION_LABEL_LENGTH,
21
+ description: `Display label for the option, with at most ${String(MAX_OPTION_LABEL_LENGTH)} characters`,
22
+ }),
15
23
  description: Type.Optional(
16
24
  Type.String({ description: 'Optional description shown below the label' }),
17
25
  ),
@@ -21,14 +29,15 @@ const QuestionSchema = Type.Object({
21
29
  id: Type.String({ description: 'Unique identifier for this question' }),
22
30
  label: Type.Optional(
23
31
  Type.String({
24
- description:
25
- "Short contextual label for the tab bar, e.g. 'Scope' (defaults to Q1, Q2)",
32
+ maxLength: MAX_TAB_LABEL_LENGTH,
33
+ description: `Short contextual label of at most ${String(MAX_TAB_LABEL_LENGTH)} characters for the tab bar, e.g. 'Scope' (defaults to Q1, Q2)`,
26
34
  }),
27
35
  ),
28
36
  prompt: Type.String({ description: 'The full question text to display' }),
29
37
  options: Type.Array(QuestionOptionSchema, {
30
38
  minItems: 2,
31
- description: 'At least two mutually exclusive options',
39
+ maxItems: MAX_OPTIONS,
40
+ description: `Two to ${String(MAX_OPTIONS)} mutually exclusive options`,
32
41
  }),
33
42
  recommendationIndex: Type.Integer({
34
43
  minimum: 0,
@@ -39,7 +48,8 @@ const QuestionSchema = Type.Object({
39
48
  export const AskParamsSchema = Type.Object({
40
49
  questions: Type.Array(QuestionSchema, {
41
50
  minItems: 1,
42
- description: 'Questions to ask the user in this batch',
51
+ maxItems: MAX_QUESTIONS,
52
+ description: `One to ${String(MAX_QUESTIONS)} questions to ask the user in this batch`,
43
53
  }),
44
54
  });
45
55
 
@@ -270,23 +280,58 @@ function schemaErrorMessage(value: unknown): string {
270
280
  if (error === undefined) {
271
281
  return 'Error: Invalid sideroom_ask parameters';
272
282
  }
273
- if (error.keyword === 'minItems' && error.instancePath === '/questions') {
274
- return 'Error: No questions provided';
283
+ if (error.instancePath === '/questions') {
284
+ if (error.keyword === 'minItems') {
285
+ return 'Error: No questions provided';
286
+ }
287
+ if (error.keyword === 'maxItems') {
288
+ return `Error: A questionnaire batch may include at most ${String(MAX_QUESTIONS)} questions`;
289
+ }
275
290
  }
291
+
292
+ const questions = asQuestions(value);
276
293
  const optionsMatch = /\/questions\/(\d+)\/options$/.exec(error.instancePath);
277
- if (error.keyword === 'minItems' && optionsMatch !== null) {
294
+ if (optionsMatch !== null) {
278
295
  const questionIndex = Number(optionsMatch[1]);
279
- const questions = asQuestions(value);
280
- const question = questions[questionIndex];
281
- const id =
282
- question !== undefined && typeof question.id === 'string'
283
- ? question.id
284
- : String(questionIndex);
285
- return `Error: Question '${id}' must include at least two options`;
296
+ const id = questionId(questions, questionIndex);
297
+ if (error.keyword === 'minItems') {
298
+ return `Error: Question '${id}' must include at least two options`;
299
+ }
300
+ if (error.keyword === 'maxItems') {
301
+ return `Error: Question '${id}' may include at most ${String(MAX_OPTIONS)} options`;
302
+ }
286
303
  }
304
+
305
+ const tabLabelMatch = /\/questions\/(\d+)\/label$/.exec(error.instancePath);
306
+ if (error.keyword === 'maxLength' && tabLabelMatch !== null) {
307
+ const questionIndex = Number(tabLabelMatch[1]);
308
+ const id = questionId(questions, questionIndex);
309
+ return `Error: Question '${id}' label may contain at most ${String(MAX_TAB_LABEL_LENGTH)} characters`;
310
+ }
311
+
312
+ const optionLabelMatch = /\/questions\/(\d+)\/options\/(\d+)\/label$/.exec(
313
+ error.instancePath,
314
+ );
315
+ if (error.keyword === 'maxLength' && optionLabelMatch !== null) {
316
+ const questionIndex = Number(optionLabelMatch[1]);
317
+ const optionIndex = Number(optionLabelMatch[2]);
318
+ const id = questionId(questions, questionIndex);
319
+ return `Error: Option ${String(optionIndex + 1)} for question '${id}' may contain at most ${String(MAX_OPTION_LABEL_LENGTH)} characters`;
320
+ }
321
+
287
322
  return `Error: Invalid sideroom_ask parameters`;
288
323
  }
289
324
 
325
+ function questionId(
326
+ questions: readonly Record<string, unknown>[],
327
+ index: number,
328
+ ): string {
329
+ const question = questions[index];
330
+ return question !== undefined && typeof question.id === 'string'
331
+ ? question.id
332
+ : String(index);
333
+ }
334
+
290
335
  function asQuestions(value: unknown): readonly Record<string, unknown>[] {
291
336
  if (typeof value !== 'object' || value === null) {
292
337
  return [];
@@ -0,0 +1,136 @@
1
+ import { dirname, join, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import {
4
+ type ExtensionAPI,
5
+ isReadToolResult,
6
+ isToolCallEventType,
7
+ } from '@earendil-works/pi-coding-agent';
8
+
9
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
10
+ export const GUIDELINE_SKILL_PATH = join(
11
+ PACKAGE_ROOT,
12
+ 'skills/sideroom-guidelines/SKILL.md',
13
+ );
14
+ const LANGUAGE_GUIDE_DIRECTORY = join(
15
+ PACKAGE_ROOT,
16
+ 'skills/sideroom-guidelines/references/languages',
17
+ );
18
+ const LANGUAGE_GUIDE_BY_EXTENSION: Readonly<Record<string, string>> = {
19
+ '.go': join(LANGUAGE_GUIDE_DIRECTORY, 'go.md'),
20
+ '.java': join(LANGUAGE_GUIDE_DIRECTORY, 'java.md'),
21
+ '.php': join(LANGUAGE_GUIDE_DIRECTORY, 'php-laravel.md'),
22
+ '.py': join(LANGUAGE_GUIDE_DIRECTORY, 'python.md'),
23
+ '.rs': join(LANGUAGE_GUIDE_DIRECTORY, 'rust.md'),
24
+ '.ts': join(LANGUAGE_GUIDE_DIRECTORY, 'typescript.md'),
25
+ '.tsx': join(LANGUAGE_GUIDE_DIRECTORY, 'typescript.md'),
26
+ };
27
+
28
+ interface ReadInput {
29
+ readonly path: string;
30
+ readonly offset?: number;
31
+ readonly limit?: number;
32
+ }
33
+
34
+ export interface GuidelineReadState {
35
+ skillRead: boolean;
36
+ readonly languageGuidesRead: Set<string>;
37
+ }
38
+
39
+ export function createGuidelineReadState(): GuidelineReadState {
40
+ return { skillRead: false, languageGuidesRead: new Set() };
41
+ }
42
+
43
+ export function resetGuidelineReadState(state: GuidelineReadState): void {
44
+ state.skillRead = false;
45
+ state.languageGuidesRead.clear();
46
+ }
47
+
48
+ export function registerGuidelineGuard(
49
+ pi: ExtensionAPI,
50
+ state: GuidelineReadState,
51
+ ): void {
52
+ pi.on('tool_result', (event) => {
53
+ if (!isReadToolResult(event) || event.isError) {
54
+ return;
55
+ }
56
+
57
+ // SAFETY: Pi validates built-in read arguments before emitting tool_result.
58
+ const input = event.input as unknown as ReadInput;
59
+ if (input.offset !== undefined || input.limit !== undefined) {
60
+ return;
61
+ }
62
+ recordGuidelineRead(state, input.path);
63
+ });
64
+
65
+ pi.on('tool_call', (event) => {
66
+ if (
67
+ !isToolCallEventType('edit', event) &&
68
+ !isToolCallEventType('write', event)
69
+ ) {
70
+ return;
71
+ }
72
+
73
+ const reason = mutationBlockReason(state, event.input.path);
74
+ if (reason === undefined) {
75
+ return;
76
+ }
77
+ return { block: true, reason };
78
+ });
79
+ }
80
+
81
+ export function recordGuidelineRead(
82
+ state: GuidelineReadState,
83
+ path: string | undefined,
84
+ ): void {
85
+ if (path === undefined) {
86
+ return;
87
+ }
88
+
89
+ const normalizedPath = normalizePath(path);
90
+ if (normalizedPath === normalizePath(GUIDELINE_SKILL_PATH)) {
91
+ state.skillRead = true;
92
+ return;
93
+ }
94
+
95
+ for (const guidePath of new Set(Object.values(LANGUAGE_GUIDE_BY_EXTENSION))) {
96
+ if (normalizedPath === normalizePath(guidePath)) {
97
+ state.languageGuidesRead.add(guidePath);
98
+ return;
99
+ }
100
+ }
101
+ }
102
+
103
+ export function mutationBlockReason(
104
+ state: GuidelineReadState,
105
+ targetPath: string,
106
+ ): string | undefined {
107
+ const requiredPaths: string[] = [];
108
+ if (!state.skillRead) {
109
+ requiredPaths.push(GUIDELINE_SKILL_PATH);
110
+ }
111
+
112
+ const guidePath = languageGuideFor(targetPath);
113
+ if (guidePath !== undefined && !state.languageGuidesRead.has(guidePath)) {
114
+ requiredPaths.push(guidePath);
115
+ }
116
+
117
+ if (requiredPaths.length === 0) {
118
+ return undefined;
119
+ }
120
+
121
+ return `Blocked ${targetPath}: read ${requiredPaths.join(' and ')} with the read tool in this agent run, then retry the edit.`;
122
+ }
123
+
124
+ function languageGuideFor(path: string): string | undefined {
125
+ const normalizedPath = normalizePath(path).toLowerCase();
126
+ const extension = Object.keys(LANGUAGE_GUIDE_BY_EXTENSION).find((candidate) =>
127
+ normalizedPath.endsWith(candidate),
128
+ );
129
+ return extension === undefined
130
+ ? undefined
131
+ : LANGUAGE_GUIDE_BY_EXTENSION[extension];
132
+ }
133
+
134
+ function normalizePath(path: string): string {
135
+ return path.replace(/^@/, '').replaceAll('\\', '/');
136
+ }
@@ -1,9 +1,18 @@
1
1
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+ import {
3
+ createGuidelineReadState,
4
+ registerGuidelineGuard,
5
+ resetGuidelineReadState,
6
+ } from './guard.ts';
2
7
  import { appendGuidelinesReminder } from './prompt.ts';
3
8
 
4
9
  // Pi loads extensions/*/index.ts through export default.
5
10
  export default function registerGuidelines(pi: ExtensionAPI): void {
11
+ const readState = createGuidelineReadState();
12
+ registerGuidelineGuard(pi, readState);
13
+
6
14
  pi.on('before_agent_start', (event) => {
15
+ resetGuidelineReadState(readState);
7
16
  const systemPrompt = appendGuidelinesReminder(event.systemPrompt);
8
17
  if (systemPrompt === event.systemPrompt) {
9
18
  return;
@@ -2,11 +2,12 @@ export const GUIDELINES_REMINDER_HEADING =
2
2
  'Sideroom coding guidelines (always-on reminder):';
3
3
 
4
4
  export const GUIDELINES_REMINDER = `${GUIDELINES_REMINDER_HEADING}
5
- Before write or edit, read the sideroom-guidelines skill.
6
- Apply its Do/Don't table. If the path is .java, .php, .ts/.tsx, .py, .go, or .rs, also read that language file under the skill's references/languages/.
7
- Do not load other language files.`;
5
+ Before the first write or edit in each agent run, use the read tool without offset or limit on the absolute SKILL.md path shown for sideroom-guidelines in Available Skills.
6
+ For .java, .php, .ts/.tsx, .py, .go, or .rs targets, also fully read exactly the matching guide under that skill's references/languages/ directory.
7
+ The extension blocks write/edit until those exact full-file reads succeed. Do not bypass the gate with bash or another mutation tool.
8
+ Before finishing, review changed code against the loaded guide and run the relevant formatter, linter, type checks, and tests.`;
8
9
 
9
- export const LANGUAGE_DELTA_FILES = [
10
+ export const LANGUAGE_GUIDE_FILES = [
10
11
  'go.md',
11
12
  'java.md',
12
13
  'php-laravel.md',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmrdeveloper/sideroom-pi",
3
- "version": "7.0.1",
3
+ "version": "8.0.0",
4
4
  "description": "A global Pi package with a questionnaire, work board, edited-file view, and coding guidelines for parent agents.",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: sideroom-domain-modeling
3
+ description: "Trigger: ubiquitous language, domain term, CONTEXT.md, ADR, vague word, naming conflict. Sharpen the project's domain language while designing: challenge vague terms, split overloaded ones, and write the settled vocabulary into the repo."
4
+ license: MIT
5
+ metadata:
6
+ author: RMRdeveloper
7
+ version: "1.0"
8
+ ---
9
+
10
+ # Sideroom domain modeling
11
+
12
+ ## Activation Contract
13
+
14
+ Load this skill when the _words_ are the problem: two people mean different
15
+ things by one term, one term is doing several jobs, or a statement about the
16
+ domain needs to survive longer than the conversation. This is the active
17
+ discipline — changing the model — not the passive habit of reading
18
+ `CONTEXT.md` to borrow a word. That read needs no skill; it is one line.
19
+
20
+ It runs underneath `sideroom-grill` through a grilling session, and it also
21
+ runs on its own when you want the discipline without the interview.
22
+
23
+ ## Hard Rules
24
+
25
+ - Interrupt. When a term conflicts with the glossary, stop and ask which of
26
+ the two things is meant instead of picking one and moving on. When a vague
27
+ word appears where a precise one exists, force the precise word.
28
+ - Stress-test relationships with a concrete scenario until the boundaries
29
+ are exact.
30
+ - Cross-check statements against the code. When the code and the sentence
31
+ disagree, quote the code back and settle which one is right out loud,
32
+ before changing either.
33
+ - A resolved term lands in `CONTEXT.md` the moment it resolves, inline, in
34
+ the middle of the conversation. One or two sentences saying what the thing
35
+ **is**, plus rejected synonyms under `_Avoid_`.
36
+ - `CONTEXT.md` is a glossary and nothing else. Never persist implementation
37
+ details, spec prose, scratch notes, or general programming concepts there.
38
+ Refuse the write and say why.
39
+ - An ADR under `docs/adr/` is offered, never assumed, and only when all
40
+ three gates pass at once: hard to reverse, surprising without context, and
41
+ the result of a real trade-off. When refusing, say which test failed.
42
+ - Create files lazily. Nothing exists until the first term or decision
43
+ crystallises. In a repo with a `CONTEXT-MAP.md` at the root, terms go into
44
+ the per-context `CONTEXT.md` the map points at instead.
45
+ - Keep the glossary lean. It gets shorter as often as it gets longer: split
46
+ an overloaded term, drop a dead one, and when the file has absorbed detail
47
+ that was never glossary material, offer to condense it.
48
+
49
+ ## Decision Gates
50
+
51
+ | Situation | Move |
52
+ | --- | --- |
53
+ | Two meanings for one term | Pick the canonical term, list the other under `_Avoid_` |
54
+ | One term doing several jobs | Split it (e.g. `Account` into `Customer` and `User`) |
55
+ | A vague term became canonical | Write it to `CONTEXT.md` now |
56
+ | A hard-to-reverse choice clearing all three ADR gates | Offer an ADR under `docs/adr/` |
57
+ | The module's shape is the problem, not its words | This skill does not apply; that is module design |
58
+ | The whole plan needs interrogating before building | `sideroom-grill`, which drives this skill underneath |
59
+ | A term only needs looking up, not changing | Read `CONTEXT.md`. It is a file |
60
+
61
+ ## Execution Steps
62
+
63
+ 1. Read `CONTEXT.md` (and the ADRs, if any) before touching the model.
64
+ 2. As terms settle, write each one inline with its `_Avoid_` list.
65
+ 3. As hard choices surface, offer an ADR or refuse one with the failed
66
+ test named.
67
+ 4. Before finishing, re-read the touched entries: each defines what a thing
68
+ is, in one or two sentences, with no implementation detail.
69
+
70
+ ## Output Contract
71
+
72
+ The repo holds the shared vocabulary and the hard decisions, each entry
73
+ short enough to re-read. The glossary never becomes a running spec.
74
+
75
+ ## References
76
+
77
+ - Driven by `sideroom-grill` during grilling sessions.
78
+ - Entry rules reused by `sideroom-domain-scaffold` for code-first
79
+ scaffolding without interviews.
80
+ - Based on the `domain-modeling` discipline described at
81
+ `https://www.aihero.dev/skills-domain-modeling`.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: sideroom-domain-scaffold
3
+ description: "Trigger: document my repo, scaffold CONTEXT.md, brownfield glossary, survey domain. Read the central domain code as source of truth and build or complete CONTEXT.md without interviewing anyone; conflicting terms are collected and grilled once, at the end."
4
+ license: MIT
5
+ metadata:
6
+ author: RMRdeveloper
7
+ version: "1.0"
8
+ ---
9
+
10
+ # Sideroom domain scaffold
11
+
12
+ ## Activation Contract
13
+
14
+ Load this skill on explicit request ("document my repo", "scaffold the
15
+ glossary") over a codebase whose domain language is missing or thin. The
16
+ code is the source of truth. Nobody is interviewed while the modeling runs.
17
+
18
+ ## Hard Rules
19
+
20
+ - Zero interviews during the scan. Every irreconcilable conflict goes onto
21
+ a conflict list; modeling continues. The happy path is never interrupted.
22
+ - Scope is the central business domain: its modules, types, enums, and
23
+ statuses. Ignore infrastructure, tooling, and utilities. Domain tests are
24
+ witnesses of the language, never sources of new terms.
25
+ - Reconcile by prevalence and centrality: the dominant, core-module usage
26
+ wins. Record each such call as an assumption for later review.
27
+ - Merge, never rebuild. An existing `CONTEXT.md` is curated truth: add and
28
+ sharpen only. A contradiction between the code and an existing entry is
29
+ not overwritten; it joins the conflict list.
30
+ - Entry format, glossary purity, lazy file creation, and multi-context
31
+ routing all follow `sideroom-domain-modeling`. This skill owns the scan;
32
+ that one owns the writing.
33
+ - No ADRs. Trade-offs cannot be validated without humans; hard choices found
34
+ in the code are reported, not recorded.
35
+ - Grill once, at the end. When modeling is complete, launch
36
+ `sideroom-grill` scoped exactly to the conflict list so a human bounds
37
+ each confused term correctly. With an empty list there is no grill: close
38
+ with the glossary and the assumptions.
39
+
40
+ ## Decision Gates
41
+
42
+ | Situation | Move |
43
+ | --- | --- |
44
+ | Same term, two meanings, no dominant use | Conflict list; grilled at the end |
45
+ | Code contradicts an existing glossary entry | Conflict list; the entry stands until grilled |
46
+ | Term belongs to infra, tooling, or utils | Skip it; out of scope |
47
+ | Whole plan needs interrogating, not just terms | `sideroom-grill` instead of this skill |
48
+
49
+ ## Execution Steps
50
+
51
+ 1. Read `CONTEXT.md` (and `CONTEXT-MAP.md`, if any) before touching the
52
+ model.
53
+ 2. Survey the central domain code and collect candidate terms.
54
+ 3. Group synonyms, split overloaded terms, reconcile by prevalence.
55
+ 4. Write each settled term inline, merged with what exists.
56
+ 5. Collect conflicts and assumptions without stopping.
57
+ 6. Close: report the glossary and the assumptions, then either launch the
58
+ end-of-run `sideroom-grill` on the conflict list or state that the model
59
+ came out clean.
60
+
61
+ ## Output Contract
62
+
63
+ A merged `CONTEXT.md` the code already agrees with, a reviewable list of
64
+ assumptions, and — only when the code disagreed with itself — one bounded
65
+ grilling at the end. No interview ever interrupts the scan.
66
+
67
+ ## References
68
+
69
+ - Entry rules come from `sideroom-domain-modeling`.
70
+ - Conflicts are handed to `sideroom-grill`, once, at the end of the run.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: sideroom-grill
3
+ description: "Trigger: fuzzy plan, design review, grilling, CONTEXT.md, ADR, domain language. Interview the user in rounds until you share one understanding, then write the vocabulary and the hard decisions into the repo."
4
+ license: MIT
5
+ metadata:
6
+ author: RMRdeveloper
7
+ version: "1.0"
8
+ ---
9
+
10
+ # Sideroom grill
11
+
12
+ ## Activation Contract
13
+
14
+ Load this skill at the start of a change, in a repo, when the plan is still
15
+ fuzzy and the words for the thing are not settled yet. Single-session scope:
16
+ if the effort is too big to hold in one session, say so and stop.
17
+
18
+ ## Hard Rules
19
+
20
+ - Interview in rounds. One `sideroom_ask` call is one round; wait for its
21
+ answers before the next round.
22
+ - Every question carries a recommendation (`recommendationIndex`). Write
23
+ prompts and option copy in the language the user is speaking; keep ids and
24
+ option values in English.
25
+ - Never ask the user what the codebase can answer. Read the code first; ask
26
+ only what the code cannot settle.
27
+ - An *Out of scope* answer closes that thread. Do not re-ask it.
28
+ - Everything a round settles is recorded through `sideroom-domain-modeling`:
29
+ inline glossary entries, the ADR three-gate offer, lazy file creation.
30
+ This skill owns the interview; that one owns the writing.
31
+ - Everything that is neither glossary nor ADR lives in the conversation
32
+ only. Do not clear the session after grilling; hand it to a spec or
33
+ straight to implementation instead.
34
+
35
+ ## Execution Steps
36
+
37
+ 1. Read the code around the change. Settle from the codebase everything the
38
+ codebase settles.
39
+ 2. Run one `sideroom_ask` round on what remains genuinely open: terms first,
40
+ then decisions.
41
+ 3. After each round, write resolved terms to `CONTEXT.md` and qualifying
42
+ decisions to `docs/adr/`.
43
+ 4. Repeat until no open term or hard decision remains, or the user cancels.
44
+ 5. Close by stating the settled glossary, the ADRs written (if any), and the
45
+ suggested next step: a spec for anything non-trivial, implementation for
46
+ what is small enough to build now.
47
+
48
+ ## Output Contract
49
+
50
+ The repo holds the shared vocabulary and the hard decisions; the
51
+ conversation holds everything else. The user can re-read `CONTEXT.md` and
52
+ the ADRs and find exactly what was agreed, nothing softened.
53
+
54
+ ## References
55
+
56
+ - Drives `sideroom-domain-modeling` underneath: vocabulary sharpening
57
+ during the rounds follows that skill.
58
+ - Closes `sideroom-domain-scaffold` runs: terms a scan could not reconcile
59
+ are grilled here, once, at the end.
60
+ - Based on the `grill-with-docs` discipline described at
61
+ `https://www.aihero.dev/skills-grill-with-docs`.
@@ -11,18 +11,20 @@ metadata:
11
11
 
12
12
  ## Activation Contract
13
13
 
14
- Load this skill before `write` or `edit`. Load at most one file from `references/languages/` for the path being changed.
14
+ Load this whole skill with `read`, without `offset` or `limit`, before the first `write` or `edit` in each agent run. Use the absolute skill path shown in Available Skills. For each supported target language, also fully load its exact guide from `references/languages/`.
15
15
 
16
16
  ## Hard Rules
17
17
 
18
- - Follow the Do/Don't table. Language files override syntax and framework idiom only.
19
- - Do not load language files you are not editing this turn.
18
+ - Treat every rule in the loaded language guide as mandatory for changed code.
19
+ - Do not write or edit until exact full-file reads of the required packaged paths succeed; partial reads and same-named files do not count.
20
+ - Do not bypass the gate with Bash, a subprocess, or another mutation tool.
21
+ - Do not load language guides unrelated to files being changed.
20
22
  - Fail fast, keep control flow flat, and validate once at the boundary.
21
23
 
22
24
  ## Decision Gates
23
25
 
24
26
  | Path | Read |
25
- |------|------|
27
+ | ------ | ------ |
26
28
  | `.java` | `references/languages/java.md` |
27
29
  | `.php` | `references/languages/php-laravel.md` |
28
30
  | `.ts` `.tsx` | `references/languages/typescript.md` |
@@ -33,9 +35,11 @@ Load this skill before `write` or `edit`. Load at most one file from `references
33
35
 
34
36
  ## Execution Steps
35
37
 
36
- 1. Apply the table to the change.
37
- 2. Read the matching language file, if any.
38
- 3. Ship the smallest change that satisfies the current requirement.
38
+ 1. Read this complete file from its packaged path before any mutation.
39
+ 2. Fully read the matching packaged language guide, if the target has a supported extension.
40
+ 3. Implement the smallest change that satisfies the current requirement.
41
+ 4. Review every changed unit against the loaded guide.
42
+ 5. Run the relevant formatter, linter, type checks, and tests before finishing.
39
43
 
40
44
  ## Do / Don't
41
45
 
@@ -65,7 +69,7 @@ Load this skill before `write` or `edit`. Load at most one file from `references
65
69
 
66
70
  ## Output Contract
67
71
 
68
- Changed code follows the table and the loaded language delta. No extra files, layers, or comments unless the change requires them.
72
+ Changed code follows every applicable rule in the loaded complete guide. The final response names the validation run and any check that could not run. No extra files, layers, comments, or bypass mutations unless the requirement needs them.
69
73
 
70
74
  ## References
71
75