@shadowclone/cli 0.0.16 → 0.0.18

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.
Files changed (32) hide show
  1. package/PRIVACY.md +1 -1
  2. package/README.md +32 -29
  3. package/dist/_.._/web/client/index.html +30 -45
  4. package/dist/shadowclone.js +1198 -527
  5. package/dist/web/index-25g5ahxa.css +1 -0
  6. package/dist/web/index-s4mjwgw0.js +134 -0
  7. package/evals.md +117 -267
  8. package/package.json +10 -10
  9. package/plugins/shadowclone/.claude-plugin/plugin.json +1 -1
  10. package/plugins/shadowclone/.codex-plugin/plugin.json +1 -1
  11. package/plugins/shadowclone/plugin.json +1 -1
  12. package/skills/choose-by-consequence/SKILL.md +66 -0
  13. package/skills/design-deep-modules/SKILL.md +32 -13
  14. package/skills/diagnose-before-editing/SKILL.md +38 -15
  15. package/skills/plan-with-review-page/SKILL.md +73 -0
  16. package/skills/plan-with-review-page/assets/review-page.html +160 -0
  17. package/skills/plan-with-review-page/scripts/validate-page.mjs +158 -0
  18. package/skills/research-primary-sources/SKILL.md +32 -12
  19. package/skills/resolve-conflicts-by-intent/SKILL.md +34 -13
  20. package/skills/scope-confirmed-changes/SKILL.md +44 -12
  21. package/skills/shadowclone-work/SKILL.md +29 -20
  22. package/skills/tests-that-catch-bugs/SKILL.md +73 -0
  23. package/skills/typescript-type-safety/SKILL.md +32 -14
  24. package/skills/verify-and-review/SKILL.md +40 -14
  25. package/skills/verify-review-findings/SKILL.md +59 -0
  26. package/skills/write-plain-english/SKILL.md +58 -0
  27. package/skills/write-plain-english/scripts/check-ste.mjs +200 -0
  28. package/dist/web/index-dzztba9n.css +0 -1
  29. package/dist/web/index-s1e5043w.js +0 -134
  30. package/skills/prove-regression-tests/SKILL.md +0 -31
  31. package/skills/testing-first/SKILL.md +0 -33
  32. package/skills/testing-risk-based/SKILL.md +0 -32
@@ -1,32 +1,51 @@
1
1
  ---
2
2
  name: design-deep-modules
3
- description: Design or improve a module so callers get substantial behavior through a small stable interface. Use when choosing seams, reducing orchestration in callers, or making a subsystem easier to test and change.
3
+ description: 'Use when designing or reshaping a module, or when callers coordinate too many steps. Also use it when the user says "this is hard to test", "callers know too much", or "simplify this interface". Puts related state and decisions behind a small interface that states what the caller wants. Tests then use that interface instead of the internal steps. Not for typing values at a boundary (use `typescript-type-safety`).'
4
4
  metadata:
5
5
  shadowclone-category: architecture
6
6
  shadowclone-section: engineering
7
- shadowclone-applies-when: designing or reshaping a module or subsystem
7
+ shadowclone-applies-when: when designing or reshaping a module or subsystem
8
8
  ---
9
9
  # Design Deep Modules
10
10
 
11
11
  ## Use when
12
12
 
13
- Callers coordinate too many steps, an abstraction only forwards arguments, or tests must reach through layers to observe behavior. A deep module provides substantial behavior through a small interface.
13
+ Callers repeat the same sequence of calls, a layer only forwards arguments, or tests must reach through layers to see behavior. A deep module gives callers a lot of behavior through a small interface.
14
+
15
+ ## Gates
16
+
17
+ 1. Can each caller state its intent in one call, without knowing the internal order of steps?
18
+ 2. Does one owner hold each piece of related state and each decision about it?
19
+ 3. Does each new layer serve a real variation or isolation need, not only forwarding?
20
+ 4. Do the tests use the public interface, and replace dependencies only at outside boundaries?
21
+ 5. Are the invariants and the error behavior part of the interface contract?
14
22
 
15
23
  ## Process
16
24
 
17
- 1. Read the callers and identify the state, ordering, and policy they currently coordinate.
18
- 2. Put related state and decisions under one owner. Define an operation that expresses the caller's intent.
19
- 3. When the boundary is unclear, compare two placements by caller burden, error handling, and testability.
20
- 4. Move internal sequencing behind the interface. Remove forwarding layers that provide no useful variation or isolation.
21
- 5. Test through the public interface, replacing dependencies only at real external boundaries.
22
- 6. Revisit callers and remove knowledge now owned by the module.
25
+ 1. Read the callers. List the state, the order of steps, and the rules that they coordinate.
26
+ 2. Put that state and those decisions under one owner. Name an operation for what the caller wants.
27
+ 3. If the boundary is not clear, compare two placements by caller effort, error handling, and test effort.
28
+ 4. Move the internal order of steps behind the interface.
29
+ 5. Remove each layer that only forwards calls.
30
+ 6. Write tests against the new interface, then remove the knowledge that callers no longer need.
23
31
 
24
- ## Guardrails
32
+ ## Example
25
33
 
26
- Keep invariants and error behavior in the interface contract. Use an options object when callers would otherwise correlate primitive arguments. Return outcomes callers need, and contain side effects within their owner.
34
+ **Situation:** Three commands each read a config file, merge it with defaults, check a policy, and take a lock before they write.
35
+ **Easy route:** Add a helper for each of the four steps, and keep the order in each command.
36
+ **Hidden cost:** Each command still owns the order. A new command can skip the policy check, and each test must repeat the four steps.
37
+ **Best route:** Add one operation that takes the intended change and does the four steps inside. The commands call that operation.
38
+ **Evidence:** Each command became one call. A test through the new operation failed without the policy check, and the step tests in each command were no longer needed.
39
+
40
+ ## Guardrails
27
41
 
28
- Add an abstraction for a concrete variation or isolation need. A short interface still imposes complexity if callers must know a hidden sequence of calls.
42
+ - Use an options object when callers would otherwise pass related values in a fixed order.
43
+ - Return the outcomes that callers need, and keep side effects inside their owner.
44
+ - Do not add an abstraction without a concrete variation or isolation need.
45
+ - A short interface still costs effort if callers must call it in a hidden order.
29
46
 
30
47
  ## Completion
31
48
 
32
- Callers express intent without coordinating internal steps, and tests exercise behavior without depending on that sequence.
49
+ - The new interface: its exported names and one call example.
50
+ - The caller steps that the module now owns.
51
+ - The test command for the new interface, with its result line.
@@ -1,35 +1,58 @@
1
1
  ---
2
2
  name: diagnose-before-editing
3
- description: Diagnose a reproducible defect or performance regression before changing production code. Use when behavior is broken, failing, throwing, flaky, or unexpectedly slow.
3
+ description: 'Use when behavior fails, flakes, or slows down, and nobody knows the cause yet. Also use it when the user says "why does this fail?", "it worked yesterday", or "this got slow". Reproduces the failure, makes it smaller, and tests one cause at a time until one cause predicts the symptom. Then fixes the cause at its origin, not the symptom. Not for scoping a fix whose cause you already know (use `scope-confirmed-changes`).'
4
4
  metadata:
5
5
  shadowclone-category: debugging
6
6
  shadowclone-section: workflow
7
- shadowclone-applies-when: diagnosing a reported defect or performance regression
7
+ shadowclone-applies-when: when behavior is wrong or slow and the cause is not known
8
8
  ---
9
9
  # Diagnose Before Editing
10
10
 
11
11
  ## Use when
12
12
 
13
- A defect or performance regression has no demonstrated cause yet.
13
+ Something fails, throws, flakes, or slows down, and nobody has shown the cause yet. A change before the cause is known can hide the bug instead of fixing it.
14
+
15
+ ## Gates
16
+
17
+ 1. Can you make the failure happen on demand with one command or test?
18
+ 2. Did you write down the input, the actual result, and the expected result?
19
+ 3. For each suspected cause, did you name an observation that would prove it wrong?
20
+ 4. Did you change one variable in each probe?
21
+ 5. Does the cause that you found predict the symptom, and does a controlled change remove it?
22
+ 6. Did you remove each temporary log line and probe before the handoff?
14
23
 
15
24
  ## Process
16
25
 
17
- 1. State the input, actual result, and expected result. Find the fastest repeatable command that reaches the failing path.
18
- 2. Reproduce the reported failure, then remove inputs or steps while preserving it.
19
- 3. Trace where correct state first becomes incorrect. List plausible causes and an observation that would disprove each.
20
- 4. Probe one cause at a time with a debugger, targeted instrumentation, timings, or controlled inputs.
21
- 5. Stop when a cause predicts the symptom and a controlled change removes it.
22
- 6. Preserve the reproduction as a regression test through a stable public interface where practical. Fix the cause at its origin.
23
- 7. Run the reduced and original reproductions, then remove temporary diagnostics.
26
+ 1. Find the fastest command that reaches the failing path. Run it until you see the failure.
27
+ 2. Remove inputs and steps while the failure stays. Stop when no further removal keeps it.
28
+ 3. Trace the state from the input. Find the first place where a correct value becomes wrong.
29
+ 4. List the possible causes. For each one, write the observation that would rule it out.
30
+ 5. Test one cause at a time with a debugger, a log line, a timer, or a controlled input.
31
+ 6. When one cause predicts the symptom, fix it at its origin.
32
+ 7. Run the small and the original reproductions. Keep one as a regression test where practical.
24
33
 
25
- For flaky behavior, measure the reproduction rate and control clocks, randomness, concurrency, or external responses. For performance work, establish a baseline and measure the suspected cost before changing it.
34
+ For a flaky failure, measure how often it happens. Control the clock, the random values, the concurrency, and the outside responses. For a slow path, measure a baseline first, and measure the suspected cost before you change it.
26
35
 
27
- ## Guardrails
36
+ ## Example
28
37
 
29
- Redact sensitive traces and paths before reporting them. Change one causal variable per probe. Keep unrelated defects out of the fix and avoid fallbacks that merely hide invalid state.
38
+ **Situation:** A test fails one run in ten on a busy machine. A teammate suggests a longer timeout.
39
+ **Easy route:** Raise the timeout from 5 to 30 seconds. The test passes.
40
+ **Hidden cost:** The cause stays. The real command can still hang for users, and the slow test now hides other delays.
41
+ **Best route:** Measure the failure rate, then control the clock in the test. The failure now shows on each run, when two writes wait for one lock.
42
+ **Evidence:** With the clock fixed, the test failed 10 of 10 runs before the fix and passed 10 of 10 after it. The timeout stayed at 5 seconds.
43
+
44
+ ## Guardrails
30
45
 
31
- If reproduction fails, report what was tried and the missing evidence. Do not turn an unverified theory into a production change.
46
+ - Do not turn a theory that you did not test into a production change.
47
+ - Do not add a fallback that only hides invalid state.
48
+ - Remove secrets and private paths from traces before you report them.
49
+ - Do not fix other defects in this change. List them in the handoff.
50
+ - If you cannot reproduce the failure, report what you tried and what evidence is missing.
32
51
 
33
52
  ## Completion
34
53
 
35
- The cause explains the original symptom, the fix passes both reproductions and affected checks, and temporary instrumentation is gone.
54
+ - The reproduction command, with its failing line before the fix.
55
+ - The same command after the fix, with its passing line.
56
+ - The cause in one sentence, with the `file:line` where the state first goes wrong.
57
+ - Each cause that you ruled out, with the observation that ruled it out.
58
+ - Anything that you could not reproduce.
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: plan-with-review-page
3
+ description: 'Use before a change that touches many files, needs many steps, or has open design choices. Also use it when the user says "plan this first", "what is your approach?", or "do not code yet". Explores the code read-only, asks one round of decisions with the recommended option first, and writes one plan. Builds a self-contained HTML review page from a template, and checks the page with a validator. Not for checking finished work (use `verify-and-review`).'
4
+ metadata:
5
+ shadowclone-category: workflow
6
+ shadowclone-section: workflow
7
+ shadowclone-applies-when: before a change that has many steps or open design choices
8
+ ---
9
+ # Plan With a Review Page
10
+
11
+ ## Use when
12
+
13
+ A change touches many files, needs many steps, or has a design choice that belongs to the user. An approved plan stops work in the wrong direction. A small and clear change does not need this skill.
14
+
15
+ ## Gates
16
+
17
+ 1. Are you read-only until the user approves? Before approval, write only the plan file and the review page.
18
+ 2. Did you read the code, the documents, and the history before you asked? Never ask what the code can answer.
19
+ 3. Did you ask one round of at most 4 questions, each with 2 to 4 options and the recommended option first?
20
+ 4. Is each risky behavior choice in that round, such as a skip, a block, a fallback, or a default?
21
+ 5. Is there one plan file for this conversation, with each skipped step still visible and its reason?
22
+ 6. Does the review page pass `scripts/validate-page.mjs` with 0 errors?
23
+
24
+ ## Process
25
+
26
+ ### Explore
27
+
28
+ 1. Read the code that the request touches, and the instructions of the repository for that code.
29
+ 2. Find the functions and patterns that the change can use again.
30
+
31
+ ### Ask
32
+
33
+ 1. List each open decision about scope, behavior, edge cases, data, release, and tests.
34
+ 2. Ask the decisions in one round of at most 4 questions. Put the recommended option first, with one reason.
35
+ 3. If an answer is not clear, ask again. Do not guess.
36
+
37
+ ### Write
38
+
39
+ 1. Put the plan at `/tmp/agent-artifacts/<repo>/<date>-<slug>/plan.md`. If the sandbox blocks `/tmp`, use the temporary folder of the agent.
40
+ 2. Write the context, the decisions with their source, the approach, the files, the steps, and the verification commands.
41
+ 3. Copy `assets/review-page.html` to the same folder as `index.html`. Edit only the JSON block with the id `plan`, and replace each value that starts with `TEMPLATE:`.
42
+ 4. Add Mermaid source in `diagram` when the change has more than two parts. The page shows the source as text, and draws it when the pinned Mermaid file loads.
43
+ 5. Run `node scripts/validate-page.mjs <page>`. If Node is not available, run the script with `bun`. Fix each error.
44
+ 6. Give the user both paths, the approach, and the main risk. Wait for approval.
45
+
46
+ ### Execute
47
+
48
+ 1. Do the steps in order. After each step, update its status in the page and in the plan.
49
+ 2. If a fact changes a decision, stop. Update the plan, and ask again.
50
+ 3. Keep a step that you skip in the list, with the status `skipped` and the reason in its note.
51
+
52
+ ## Example
53
+
54
+ **Situation:** The user asks for a sync command that updates installed skills. Some installed copies contain edits that the user made.
55
+ **Easy route:** Plan to replace each copy with the new version, and do not ask.
56
+ **Hidden cost:** The plan hides a choice that deletes the work of the user. The user finds the loss after the release.
57
+ **Best route:** Put the choice in the question round: keep each edited copy and list it (recommended), or replace it and save a backup. Record the answer in the plan.
58
+ **Evidence:** The decision table of the page shows "Edited copies: keep and list" with the source "user". The validator printed "validate-page: 0 errors in 1 file".
59
+
60
+ ## Guardrails
61
+
62
+ - Approval covers only the steps in the plan. Ask before a step that the plan does not list.
63
+ - Never commit the plan or the review page to a repository.
64
+ - Keep the review page self-contained. Do not add scripts, styles, or fonts from the network.
65
+ - Do not open a decision again unless a new fact changes it.
66
+ - Keep the plan short enough to scan. Write the recommended approach, not each option that you considered.
67
+
68
+ ## Completion
69
+
70
+ - The plan path and the review page path.
71
+ - The validator command and its result line.
72
+ - The question round, with the recommended option first in each question.
73
+ - Each step with its status, and each skipped step with its reason.
@@ -0,0 +1,160 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Plan review</title>
7
+ <style>
8
+ :root {
9
+ --page: #fbfaf7; --text: #1d1d1b; --muted: #66645f; --card: #ffffff; --line: #e2dfd8;
10
+ --done: #2f7d4f; --doing: #a8701c; --todo: #77756f; --blocked: #b5443b; --skipped: #6d5fa8;
11
+ }
12
+ @media (prefers-color-scheme: dark) {
13
+ :root {
14
+ --page: #161615; --text: #ecebe7; --muted: #a09e98; --card: #1f1f1d; --line: #34332f;
15
+ --done: #5fbf83; --doing: #e0a84a; --todo: #8f8c85; --blocked: #e57b72; --skipped: #a99be8;
16
+ }
17
+ }
18
+ * { box-sizing: border-box; }
19
+ body { margin: 0; background: var(--page); color: var(--text); font: 15px/1.55 system-ui, sans-serif; }
20
+ main { max-width: 960px; margin: 0 auto; padding: 32px 16px 64px; }
21
+ h1 { font-size: 24px; margin: 0 0 6px; }
22
+ h2 { font-size: 17px; margin: 28px 0 10px; }
23
+ .meta { color: var(--muted); margin: 0 0 18px; }
24
+ .card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; }
25
+ .counts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
26
+ .counts b { display: block; font-size: 22px; }
27
+ .counts span { color: var(--muted); font-size: 13px; }
28
+ ol.steps { list-style: none; margin: 0; padding: 0; }
29
+ ol.steps li { display: grid; grid-template-columns: 84px 1fr; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line); }
30
+ ol.steps li:last-child { border-bottom: 0; }
31
+ .status { font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
32
+ .done { color: var(--done); } .doing { color: var(--doing); } .todo { color: var(--todo); }
33
+ .blocked { color: var(--blocked); } .skipped { color: var(--skipped); }
34
+ .note { display: block; color: var(--muted); font-size: 13px; }
35
+ .scroll { overflow-x: auto; }
36
+ table { width: 100%; border-collapse: collapse; font-size: 14px; }
37
+ th, td { text-align: left; vertical-align: top; padding: 7px 8px; border-bottom: 1px solid var(--line); }
38
+ th { color: var(--muted); font-weight: 600; }
39
+ code, pre { font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; }
40
+ pre { white-space: pre-wrap; margin: 0; }
41
+ #diagram img { display: block; max-width: 100%; height: auto; margin-bottom: 10px; }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <main>
46
+ <h1 id="title"></h1>
47
+ <p id="goal"></p>
48
+ <p class="meta">Plan file: <code id="plan-path"></code>. Updated: <span id="updated"></span>.</p>
49
+ <div class="counts">
50
+ <div class="card"><b id="count-done">0</b><span>steps done</span></div>
51
+ <div class="card"><b id="count-left">0</b><span>steps left</span></div>
52
+ <div class="card"><b id="count-open">0</b><span>open questions</span></div>
53
+ </div>
54
+ <section id="diagram-section" hidden>
55
+ <h2>Diagram</h2>
56
+ <div class="card scroll">
57
+ <div id="diagram"></div>
58
+ <p class="note" id="diagram-note">The diagram shows as source text until the pinned Mermaid file loads.</p>
59
+ <pre id="diagram-source"></pre>
60
+ </div>
61
+ </section>
62
+ <h2>Steps</h2>
63
+ <div class="card"><ol class="steps" id="steps"></ol></div>
64
+ <h2>Decisions</h2>
65
+ <div class="card scroll">
66
+ <table>
67
+ <thead><tr><th>Question</th><th>Answer</th><th>Source</th></tr></thead>
68
+ <tbody id="decisions"></tbody>
69
+ </table>
70
+ </div>
71
+ <h2>Open questions</h2>
72
+ <div class="card"><ul id="open"></ul></div>
73
+ </main>
74
+ <script type="application/json" id="plan">
75
+ {
76
+ "title": "TEMPLATE: A short name for the change",
77
+ "goal": "TEMPLATE: One sentence about the result that the user wants.",
78
+ "planPath": "TEMPLATE: The absolute path of the plan file",
79
+ "updated": "TEMPLATE: The date and time of this update",
80
+ "steps": [
81
+ { "status": "done", "text": "TEMPLATE: Read the code that the request touches.", "note": "" },
82
+ { "status": "doing", "text": "TEMPLATE: Ask one round of decisions.", "note": "" },
83
+ { "status": "todo", "text": "TEMPLATE: Write the plan and this page.", "note": "" },
84
+ { "status": "skipped", "text": "TEMPLATE: A step that you skipped.", "note": "TEMPLATE: The reason for the skip." }
85
+ ],
86
+ "decisions": [
87
+ { "question": "TEMPLATE: A decision that the user made.", "answer": "TEMPLATE: The answer.", "source": "user" }
88
+ ],
89
+ "open": ["TEMPLATE: A question that is still open."],
90
+ "diagram": "flowchart LR\n Request --> Plan --> Approval --> Steps"
91
+ }
92
+ </script>
93
+ <script>
94
+ const plan = JSON.parse(document.getElementById("plan").textContent);
95
+ const element = (name, text, className) => {
96
+ const node = document.createElement(name);
97
+ node.textContent = text;
98
+ if (className) node.className = className;
99
+ return node;
100
+ };
101
+
102
+ document.title = plan.title;
103
+ document.getElementById("title").textContent = plan.title;
104
+ document.getElementById("goal").textContent = plan.goal;
105
+ document.getElementById("plan-path").textContent = plan.planPath;
106
+ document.getElementById("updated").textContent = plan.updated;
107
+ document.getElementById("count-done").textContent = plan.steps.filter((step) => step.status === "done").length;
108
+ document.getElementById("count-left").textContent = plan.steps.filter((step) => step.status !== "done" && step.status !== "skipped").length;
109
+ document.getElementById("count-open").textContent = plan.open.length;
110
+
111
+ for (const step of plan.steps) {
112
+ const item = document.createElement("li");
113
+ const body = element("div", step.text);
114
+ if (step.note) body.append(element("span", step.note, "note"));
115
+ item.append(element("span", step.status, `status ${step.status}`), body);
116
+ document.getElementById("steps").append(item);
117
+ }
118
+
119
+ for (const decision of plan.decisions) {
120
+ const row = document.createElement("tr");
121
+ row.append(element("td", decision.question), element("td", decision.answer), element("td", decision.source));
122
+ document.getElementById("decisions").append(row);
123
+ }
124
+
125
+ for (const question of plan.open) {
126
+ document.getElementById("open").append(element("li", question));
127
+ }
128
+
129
+ if (plan.diagram) {
130
+ document.getElementById("diagram-section").hidden = false;
131
+ document.getElementById("diagram-source").textContent = plan.diagram;
132
+
133
+ const script = document.createElement("script");
134
+ script.src = "https://cdn.jsdelivr.net/npm/mermaid@12.1.0/dist/mermaid.min.js";
135
+ script.integrity = "sha384-EbBpjO7rlR6eqZEcG7GaPpyk9H9WrMyPWX4d3KvPYltgt8Z8l0z6R56B1qP40pR4";
136
+ script.crossOrigin = "anonymous";
137
+ script.onload = async () => {
138
+ const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
139
+ window.mermaid.initialize({
140
+ startOnLoad: false,
141
+ securityLevel: "strict",
142
+ htmlLabels: false,
143
+ flowchart: { htmlLabels: false },
144
+ theme: dark ? "dark" : "default",
145
+ });
146
+ const { svg } = await window.mermaid.render("plan-diagram", plan.diagram);
147
+ const image = document.createElement("img");
148
+ image.alt = "Plan diagram";
149
+ image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
150
+ document.getElementById("diagram").replaceChildren(image);
151
+ document.getElementById("diagram-note").textContent = "Diagram source:";
152
+ };
153
+ script.onerror = () => {
154
+ document.getElementById("diagram-note").textContent = "Mermaid did not load, so the diagram shows as source text.";
155
+ };
156
+ document.head.append(script);
157
+ }
158
+ </script>
159
+ </body>
160
+ </html>
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ const statuses = ["todo", "doing", "done", "blocked", "skipped"];
5
+ const dashes = new RegExp(`[${String.fromCharCode(0x2013, 0x2014)}]`);
6
+ const mermaidUrl = "https://cdn.jsdelivr.net/npm/mermaid@12.1.0/dist/mermaid.min.js";
7
+ const mermaidIntegrity = "sha384-EbBpjO7rlR6eqZEcG7GaPpyk9H9WrMyPWX4d3KvPYltgt8Z8l0z6R56B1qP40pR4";
8
+
9
+ function readPlan(html) {
10
+ const blocks = [
11
+ ...html.matchAll(/<script type="application\/json" id="plan">([\s\S]*?)<\/script>/g),
12
+ ];
13
+
14
+ if (blocks.length !== 1) {
15
+ return {
16
+ error: `found ${blocks.length} plan blocks, keep exactly one JSON block with the id plan`,
17
+ };
18
+ }
19
+
20
+ try {
21
+ return { plan: JSON.parse(blocks[0][1]) };
22
+ } catch (error) {
23
+ return { error: `the plan block is not valid JSON: ${error.message}` };
24
+ }
25
+ }
26
+
27
+ function textValues(value, at) {
28
+ if (typeof value === "string") {
29
+ return [[at, value]];
30
+ }
31
+
32
+ if (Array.isArray(value)) {
33
+ return value.flatMap((entry, index) => textValues(entry, `${at}[${index}]`));
34
+ }
35
+
36
+ if (value !== null && typeof value === "object") {
37
+ return Object.entries(value).flatMap(([key, entry]) =>
38
+ textValues(entry, at ? `${at}.${key}` : key),
39
+ );
40
+ }
41
+
42
+ return [];
43
+ }
44
+
45
+ const filled = (value) => typeof value === "string" && value.trim().length > 0;
46
+
47
+ function validate(html) {
48
+ const { plan, error } = readPlan(html);
49
+
50
+ if (error) {
51
+ return [error];
52
+ }
53
+
54
+ const errors = [];
55
+ const steps = Array.isArray(plan.steps) ? plan.steps : [];
56
+ const decisions = Array.isArray(plan.decisions) ? plan.decisions : [];
57
+
58
+ for (const key of ["title", "goal", "planPath", "updated"]) {
59
+ if (!filled(plan[key])) {
60
+ errors.push(`${key} must be filled in`);
61
+ }
62
+ }
63
+
64
+ if (steps.length === 0) {
65
+ errors.push("steps must list at least one step");
66
+ }
67
+
68
+ for (const [index, step] of steps.entries()) {
69
+ if (!statuses.includes(step?.status)) {
70
+ errors.push(`steps[${index}].status must be one of ${statuses.join(", ")}`);
71
+ }
72
+
73
+ if (!filled(step?.text)) {
74
+ errors.push(`steps[${index}].text must be filled in`);
75
+ }
76
+
77
+ if ((step?.status === "skipped" || step?.status === "blocked") && !filled(step.note)) {
78
+ errors.push(`steps[${index}] is ${step.status}, so its note must give the reason`);
79
+ }
80
+ }
81
+
82
+ if (!Array.isArray(plan.decisions)) {
83
+ errors.push("decisions must be a list");
84
+ }
85
+
86
+ for (const [index, decision] of decisions.entries()) {
87
+ for (const key of ["question", "answer", "source"]) {
88
+ if (!filled(decision?.[key])) {
89
+ errors.push(`decisions[${index}].${key} must be filled in`);
90
+ }
91
+ }
92
+ }
93
+
94
+ if (!Array.isArray(plan.open) || plan.open.some((entry) => typeof entry !== "string")) {
95
+ errors.push("open must be a list of questions");
96
+ }
97
+
98
+ if (typeof plan.diagram !== "string") {
99
+ errors.push("diagram must be Mermaid source, or empty when there is no diagram");
100
+ }
101
+
102
+ for (const [at, text] of textValues(plan, "")) {
103
+ if (text.startsWith("TEMPLATE:")) {
104
+ errors.push(`${at} still has template text, replace it`);
105
+ }
106
+
107
+ if (dashes.test(text)) {
108
+ errors.push(`${at} has an em or en dash, use a comma, a period, or parentheses`);
109
+ }
110
+ }
111
+
112
+ for (const [url] of html.matchAll(/https?:\/\/[^\s"'<>)]+/g)) {
113
+ if (url !== mermaidUrl) {
114
+ errors.push(`${url} loads from the network, keep the page self-contained`);
115
+ }
116
+ }
117
+
118
+ if (html.includes(mermaidUrl) && !html.includes(mermaidIntegrity)) {
119
+ errors.push("the Mermaid script must keep its pinned integrity hash");
120
+ }
121
+
122
+ return errors;
123
+ }
124
+
125
+ async function main(files) {
126
+ if (files.length === 0) {
127
+ console.error("Usage: validate-page.mjs <page.html>...");
128
+
129
+ return 2;
130
+ }
131
+
132
+ let total = 0;
133
+
134
+ for (const file of files) {
135
+ let html;
136
+
137
+ try {
138
+ html = await readFile(file, "utf8");
139
+ } catch (error) {
140
+ console.error(`${file}: cannot read the file (${error.code ?? error.message})`);
141
+
142
+ return 2;
143
+ }
144
+
145
+ for (const message of validate(html)) {
146
+ console.log(`${file}: error ${message}`);
147
+ total += 1;
148
+ }
149
+ }
150
+
151
+ const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
152
+
153
+ console.log(`validate-page: ${plural(total, "error")} in ${plural(files.length, "file")}`);
154
+
155
+ return total > 0 ? 1 : 0;
156
+ }
157
+
158
+ process.exitCode = await main(process.argv.slice(2));
@@ -1,31 +1,51 @@
1
1
  ---
2
2
  name: research-primary-sources
3
- description: Resolve a technical implementation question against authoritative first-party evidence. Use when behavior depends on a library, platform, protocol, standard, or tool that may have changed.
3
+ description: 'Use when a decision depends on how a library, platform, protocol, or tool behaves, and that behavior can change by version. Also use it when the user says "check the docs", "is this still true?", or "what does the spec say?". Finds the first-party source for the installed version and reads it. Separates what the source guarantees from what you infer. Not for debugging a failure in your own code (use `diagnose-before-editing`).'
4
4
  metadata:
5
5
  shadowclone-category: research
6
6
  shadowclone-section: workflow
7
- shadowclone-applies-when: resolving an implementation question that depends on external facts
7
+ shadowclone-applies-when: when a decision depends on facts outside the repository
8
8
  ---
9
9
  # Research Primary Sources
10
10
 
11
11
  ## Use when
12
12
 
13
- An implementation decision depends on external facts or behavior that varies by version.
13
+ A decision depends on outside facts about a library, a platform, a protocol, a standard, or a tool. Those facts change with versions, and memory or a search snippet can be out of date.
14
+
15
+ ## Gates
16
+
17
+ 1. Is the question specific enough to have one answer?
18
+ 2. Did you record the installed version, the runtime, and the platform that the answer must match?
19
+ 3. Did you read the first-party source itself, such as the specification, the official documents, the source code, or the release notes?
20
+ 4. Does each claim say whether the source guarantees it or you infer it?
21
+ 5. If no source answers the question, did you say so and keep the choice easy to reverse?
14
22
 
15
23
  ## Process
16
24
 
17
- 1. State the decision as an answerable question. Record the project's relevant version, runtime, platform, and constraints.
18
- 2. Find the specification, official documentation, implementation, release note, or first-party issue that defines the behavior.
19
- 3. Open the exact source and check its version and date. Trace consequential claims back from summaries.
20
- 4. Compare the evidence with the installed version and code path. Explain disagreements caused by different environments or versions.
21
- 5. Give the conclusion with direct supporting links and the implementation constraint it creates.
25
+ 1. Write the decision as a question. Record the version, the runtime, the platform, and the limits that apply.
26
+ 2. Find the source that defines the behavior. Prefer the specification, the official documents, the source code, a release note, or a first-party issue.
27
+ 3. Open the exact page or file. Check that its version and date match yours.
28
+ 4. Compare the source with the installed code. Explain each difference that comes from a version or an environment.
29
+ 5. Write the answer, the link or `file:line` for each claim, and the limit that it puts on the change.
22
30
 
23
- ## Guardrails
31
+ ## Example
24
32
 
25
- Search snippets are leads; read the underlying source. Distinguish documented guarantees from inferences and identify the observations supporting an inference.
33
+ **Situation:** A change must know which instruction file a coding agent reads when two files exist in one folder.
34
+ **Easy route:** Answer from memory: "it reads both".
35
+ **Hidden cost:** If memory is wrong, an install can write a file that hides a file of the team, and nobody notices.
36
+ **Best route:** Open the official documents for the installed version, find the section about file order, and quote it.
37
+ **Evidence:** The documents said that the agent reads the override file first, and reads at most one file in each folder. The install now stops before it writes a file that hides another one.
38
+
39
+ ## Guardrails
26
40
 
27
- Quote sparingly and keep credentials, private URLs, and captured user data out of research notes. If authoritative evidence is unavailable, state the unknown and contain it behind a reversible implementation choice.
41
+ - A search result is a lead. Read the source that it points to.
42
+ - Quote only the lines that support the decision.
43
+ - Keep credentials, private links, and user data out of research notes.
44
+ - If the evidence is old, say so, and name the version that it covers.
28
45
 
29
46
  ## Completion
30
47
 
31
- The question has a supported answer or an explicit unknown, and the resulting constraint is clear enough to implement.
48
+ - The answer, or the exact unknown.
49
+ - Each claim with its source: a link, or a `file:line`.
50
+ - The version that each source covers.
51
+ - The limit that the answer puts on the change.