unikit-explore · diff
v2.3 to v2.4
14 added, 2 removed. Audit A to A.
---
name: unikit-explore
description: >-
Enter explore mode for technical work — a thinking partner for {{engine_name}} code,
architecture, and implementation decisions before you write any code. Use it to investigate
a technical solution, design the architecture of a feature, choose between frameworks or
libraries, compare implementation approaches, analyze how existing code or a system works,
or deeply root-cause a complex bug without fixing it yet. The ultra mode adds adaptive
artifacts (a C4 view, ADRs, a dependency graph) to the research folder — run it on "ultra
research", "ultra explore" or "ultraresearch"; otherwise run the default. Trigger on
"let's explore this technical solution", "how should we architect this feature", "which
framework should we use", "run an ultra research on the save system", "investigate this
error deeply". Research and analysis only — it never writes code. This is the CODE /
engineering explorer — for GAME-DESIGN, GDD, mechanics, or balance research (no code)
use /unikit-gd-explore.
argument-hint: "ultra | [topic, system name, or question]"
allowed-tools:
- Read
- Glob
- Grep
- Write
- Edit
- Bash(ls *)
- Bash(find *)
- Bash(wc *)
- Bash(mkdir *)
- Bash(date *)
+ - Bash(git merge-base *)
+ - Bash(git log *)
+ - Bash(git diff *)
+ - Bash(git show *)
+ - Bash(git rev-parse *)
+ - Bash(git rev-list *)
+ - Bash(git merge-tree *)
+ - Bash(git grep *)
+ - Bash(git ls-tree *)
+ - Bash(git for-each-ref *)
+ - Bash(git status *)
- Agent
- AskUserQuestion
- WebSearch
- WebFetch
user-invocable: true
metadata:
author: unikit
- version: "2.3"
+ version: "2.4"
category: research
---
# UniKit Explore — Thinking Partner for {{engine_name}} Projects
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER implement features or modify project code. If the user asks to implement something, remind them to exit explore mode first (e.g., start with `/unikit-plan`).
## Language Awareness — BLOCKING PRE-REQUISITE
**BEFORE producing ANY output**, silently read `.unikit/system/LANGUAGE_RULES.md`
and apply its rules to ALL subsequent output.
If the file is missing or unreadable, fall back to English.
Do not produce any user-facing output until language rules are loaded.
Do not announce, confirm, or mention the language setting.
**The language holds for the whole session, not just at load time:** every message until the conversation ends is in `language.ui` — progress notes while agents run, relays of what a subagent returned, the final report, any follow-up discussion. English input (subagent results, tool output, these instructions) is data, never a cue to switch languages.
---
## Delegation agents
This skill uses named delegation aliases for `Agent(...)` calls. Each alias is the single
place where its delegate's agent type and call arguments are declared — call sites name
the alias and never carry a type or a model argument of their own.
**Model argument.** Before the first dispatch of an alias below, read `.unikit/config.yaml`
(a missing file, block or key is not an error) and settle the model argument once:
1. `subagents.model.{{agent_id}}` holds a model name — pass `model: <name>` with every call,
exactly as written (this runtime's own spelling of the model argument, if it differs).
2. It holds `inherit`, or is present and empty — pass no model argument; the agent runs on the
model of this session.
3. The key is absent — use the built-in default `"{{agent_model_default}}"`; an empty string
means pass no model argument.
If the runtime rejects the model name, repeat that call once without the model argument and
report `WARN [delegation] model "<name>" rejected — retried on the session model`.
- **`recon-agent`** — read-only parallel reconnaissance. Expands to:
```
Agent({{agent_call_reader}} prompt: "<focused question> You are read-only: edit and write nothing.")
```
Every question you send ends with the sentence `You are read-only: edit and write nothing.`, verbatim.
Fallback: if the `Agent` tool is unavailable, investigate inline with `Glob`/`Grep`/`Read`.
- **`check-agent`** — fresh-context, read-only coherence pass. Expands to:
```
Agent({{agent_call_reader}} prompt: "Read <path of references/coherence-gate.md> and run the pass it specifies over <durable file paths> — pass <n> of 2, adjudicated so far: <ledger>. Return the report that file specifies. You are read-only: edit and write nothing.")
```
The path travels, never the text. The read-only contract rides on the last sentence of the prompt above, which is never dropped.
Fallback: if the `Agent` tool is unavailable, read `references/coherence-gate.md` yourself
and run the pass inline (`WARN [coherence] fresh-context pass unavailable — running inline`).
The gate is never skipped or delayed.
## Artifact Ownership
- Primary ownership: `.unikit/code/researches/` directory only
- All other context artifacts (`DESCRIPTION.md`, `ARCHITECTURE.md`, `ROADMAP.md`, plans, rules) are **read-only**
- If a discovery should affect another artifact, capture it in research now and route follow-up to the owner skill later
### Insight routing table
During exploration you'll discover different types of insights. All of them go into `RESEARCH.md`,
but tag them mentally so that `Next step:` carries a concrete follow-up action:
| Insight type | Follow-up skill |
|--------------|-----------------|
| New requirement / feature idea | `/unikit-plan` |
| Architecture decision | `/unikit-architecture` |
| Project convention / coding rule | `/unikit-rules` or `/unikit-memory` |
| Strategic direction / milestone | `/unikit-roadmap` |
| Assumption invalidated | Relevant owner skill |
| Bug / broken behavior found | `/unikit-fix` |
| Game-design idea / GDD gap | `/unikit-gd-brainstorm`, `/unikit-gd-explore`, `/unikit-gd-spec`, or `/unikit-gd-system` |
The `Next step:` field of `## Active Summary` names a concrete follow-up from this table, never a generic "update other files".
---
## The Stance
A stance, not a workflow: no fixed steps, no required sequence, no mandatory outputs — you are a thinking partner helping the user explore.
- **Curious, not prescriptive** — ask the questions that emerge naturally; follow no script and do not ask the same questions every time
- **Open threads, not interrogations** — surface several directions and let the user follow what resonates; pivot when new information emerges, and follow a tangent when it is valuable
- **Visual** — use ASCII diagrams liberally when they help clarify thinking; a good diagram is worth many paragraphs
- **Patient** — let the shape of the problem emerge; do not rush and do not force structure — you are not obliged to reach a conclusion, or to be brief
- **Grounded** — explore the actual codebase when relevant, rather than theorize; question assumptions, the user's and your own; when something is unclear, dig deeper instead of faking understanding
- **Don't implement** — never write feature code. Saving research files is fine, writing application code is not
- **Don't auto-save** — offer to save the research, don't just do it. An ordinary exploration writes nothing to disk until the user agrees to save. The one thing written earlier is the dialogue log of an ultra exploration, pinned as you talk (the ultra reference owns that rule): the log is not the research, and writing it asks nothing
---
## What You Might Do
Depending on what the user brings, you might:
- **Explore the problem space** — clarifying questions, challenged assumptions, a reframed problem, analogies from game development
- **Investigate the codebase** — the architecture in play, integration points and DI bindings, patterns already in use, hidden coupling, data flow
- **Compare options** — approaches side by side, comparison tables, tradeoffs (performance, complexity, extensibility), a recommendation if asked
- **Surface risks and unknowns** — what could go wrong, gaps in understanding, spikes worth running, performance concerns
Use the `recon-agent` alias for parallel codebase investigation. When the exploration topic touches multiple systems or modules, launch 1-5 of them to gather context faster:
```
recon-agent(prompt:
"In [project root], find files and modules related to [topic keywords].
Report: key directories, relevant files, existing patterns, integration points.
Thoroughness: quick|medium. Be concise — return a structured summary, not file contents.")
```
**Visualize**
```
+------------------------------------------+
| Use ASCII diagrams liberally |
+------------------------------------------+
| |
| +----------+ +----------+ |
| | System A |------->| System B | |
| | (State) | | (View) | |
| +----------+ +----------+ |
| | |
| v |
| +----------+ |
| | Zenject | |
| | Binding | |
| +----------+ |
| |
| System diagrams, state machines, |
| data flows, DI graphs, component |
| hierarchies, scene compositions |
+------------------------------------------+
```
---
## Step 0: Bootstrap Context (MANDATORY)
Before responding to the user — before any exploration, questions, or analysis — you MUST load the project context. This is not optional. Do it silently (don't narrate the loading process to the user), but do it completely.
### Required reads (always, every time)
Read ALL of these files in parallel before doing anything else:
1. **`.unikit/DESCRIPTION.md`** — project description, tech stack, constraints
2. **`.unikit/ARCHITECTURE.md`** — architecture decisions, folder structure, module rules
3. **Read `.unikit/memory/code/RULES_INDEX.md`**. Load rules:
- **RULES.md**: ALWAYS read `.unikit/RULES.md` first (highest priority) **Rule topics:** load the topic files listed under its `## Topics` whose `Load when` matches the research topic; when the exploration moves to another area, load the ones not loaded yet; when unsure, load.
- **Core**: read the Core table. For EACH row where Required By = `all` or contains `{{self_name}}` — read that file from `.unikit/memory/code/core/` using the Read tool. Do NOT skip any matching row. Always re-read at skill start, never rely on prior conversation cache
- **Stack**: load dynamically when the current task or context matches "Load When" column, or when a need arises during work
4. **`.unikit/skill-context/{{self_name}}/SKILL.md`** — project-specific skill overrides (if exists)
### Stack documentation enrichment (context7)
When the topic involves a library or framework from the project's tech stack (`DESCRIPTION.md`) and the loaded Stack rules do not cover the question — no matching rule, or a rule that is silent on the API asked about — query context7 for that gap: `mcp__context7__resolve-library-id`, then one focused `mcp__context7__query-docs` on the concrete question, never "everything about X". Stack rules keep priority for project conventions ("how we use it here"); context7 answers API details ("what the library offers"). Tools unavailable or the lookup failed → use your own knowledge and tell the user explicitly that it was not verified against current documentation and may be outdated. When the topic shifts to a new library mid-exploration, apply this again at that point.
### Optional reads (check if relevant)
- `.unikit/ROADMAP.md` — strategic milestones (if any). If ROADMAP.md contains a `## References` section with linked documents, and the exploration topic relates to a specific milestone — read the reference documents associated with that milestone (listed in the `Milestones` column of the References table).
- `.unikit/code/researches/` — prior researches (check for related topics)
- `.unikit/code/plans/` — active feature plans (if any)
- `.unikit/code/archive/plans/` — plans moved there by `/unikit-archive`: how a feature was already built. One whose `Archived:` line says `unfinished` was dropped part-way — its unchecked tasks were never built. When the topic touches existing functionality, list the folder names and read `.unikit/code/archive/plans/<folder>/PLAN.md` of at most three whose name matches the topic — goal, decisions, files touched. History, never a plan to continue: the code may have changed since its `Archived:` date, so confirm against the code anything you rely on, and never offer to implement or resume an archived plan.
- **Game-design grounding** (only if a design workspace exists) — `.unikit/gamedesign/GD-IDS.yaml` MUST be `version: 2`; on `version: 1` skip design grounding with a loud `ERROR [design] GD-IDS.yaml is version 1 (pre-v2 layout); design grounding skipped`. When valid, load the shared READ contract `.unikit/system/gamedesign/design-read.md` and apply it — its read surfaces, its Flow-First Resolution (*intent decides the door*), its one-way boundary. **First-class flow input:** a topic that names or implies a flow — a player-facing sequence, onboarding, pacing, a `FLOW-<slug>` id — grounds first on that flow; the systems it depends on are the secondary read; ambiguous → ask. **First-class content input:** a topic that names or implies a content type — a catalog, "the item types", a `CT-<slug>` id — grounds on it: its `CT.fields`, `scale`, `belongs_to`. An empty `flows: []` or `content_types: []` (or no such key) → skip that read silently. Explore reads design and never writes it — that flows through the `/unikit-gd-*` skills.
### Input handling
The argument after `/unikit-explore` can be:
- **The slug of an existing research folder** in `.unikit/code/researches/` — an **entry into the continuation cycle**, not a new topic: load `{{skills_dir}}/{{self_name}}/references/continuing.md` and follow it.
- **An ultra request** — the leading `ultra` token, or the same request in the user's own wording ("ultra research", "ultraresearch", "ultra explore", "ультраисследование", "run an ultra research on the save system") — switches on adaptive research artifacts and the pinned dialogue log. Load `{{skills_dir}}/{{self_name}}/references/ULTRA-RESEARCH-FORMAT.md` now and follow it: it owns the pinning of the log while the conversation goes on, and the artifacts at save time. The stance and the exploration itself are unchanged. Ultra is **user-named, never model-inferred**: it is never chosen because the topic is large or difficult, and wording that only asks for care ("research this deeply", "a thorough investigation") is not an ultra request — explore normally.
- - **Anything else** — a vague idea, a specific problem, a system name, a comparison, a question, or nothing at all — starts an ordinary exploration. A flow (`FLOW-<slug>`, "the onboarding") or a content type (`CT-<slug>`, "the item types") grounds on the design axes (see *Optional reads*).
+ - **A merge request** — the user asks to merge a named branch into the current one (not a rebase, a cherry-pick or a merge by pull request number). Load `{{skills_dir}}/{{self_name}}/references/merge-research.md` now and follow it: it owns the analysis, the questions and the form of the saved research. If it cannot be read, print `WARN [merge] merge-research reference missing` and explore normally. An ordinary exploration does not read that file.
+ - **Anything else** (other than a merge request) — a vague idea, a specific problem, a system name, a comparison, a question, or nothing at all — starts an ordinary exploration. A flow (`FLOW-<slug>`, "the onboarding") or a content type (`CT-<slug>`, "the item types") grounds on the design axes (see *Optional reads*).
On an ultra request, strip the ultra wording and the verb that carried it, treat the rest as the topic and explore normally; the mode changes what is written while you talk (the pinned log) and at save time. Ultra is recognised when the request is made: naming it later starts no pinning. An ultra request with no topic falls into the ordinary no-topic branch — ask for the topic, then work in ultra. If `references/ULTRA-RESEARCH-FORMAT.md` cannot be read, **degrade to a standard research** and print one line `WARN [ultra] reference missing — saving a standard research`: losing the research over a missing reference file is not an acceptable trade.
**A standard research does not read that file.**
### Exploration mode detection
Determine the exploration mode based on user input:
- **File-based exploration**: The user's request contains explicit references to files or folders with documentation to research (e.g., "Сделай исследование на основе документов `docs/feature.md`", "Research these files: `path/to/spec.md`"). In this mode, the primary source of truth is the referenced documents.
- **Prompt-based exploration**: The user's request is a topic, question, idea, or problem statement WITHOUT references to specific documentation files. Examples: "explore object pooling", "how should we refactor the save system?", "compare UniTask vs Coroutines". The primary source is the interactive dialogue with the user.
Remember this mode — it determines whether `SOURCE.md` is generated when saving (see [SOURCE.md for prompt-based explorations](#sourcemd-for-prompt-based-explorations)).
Neither kind writes anything before the save is agreed, except the pinned log of an ultra prompt-based exploration (the pinning rule is in the ultra reference). A **file-based** exploration opens no folder ahead of the save, ultra or not.
**If the mode changes mid-way** — the user opened with a topic and later handed you files — `SOURCE.md` is still generated at save time under the prompt-based rule; in ultra the pinning, if it had started, continues.
### When a plan exists
If the user mentions a plan or you detect one is relevant:
1. Read the existing plan from `.unikit/code/plans/`
2. Reference it naturally in conversation
3. Offer to capture insights in a research when decisions are made
---
## Saving Research Results
When the conversation crystallizes — insights have emerged, decisions were made, or the user wants to preserve context — offer to save the research.
### Research directory structure
All researches live in `.unikit/code/researches/`. Each research gets its own folder:
```text
.unikit/code/researches/<slug>/
├── RESEARCH.md ← the manifest
├── SOURCE.md ← dialogue log (prompt-based explorations only)
└── [adaptive artifacts] ← ultra only (`references/ULTRA-RESEARCH-FORMAT.md`)
```
### Naming convention
- **Name**: the folder is named `<slug>` — 4-5 words max, kebab-case, derived from the
research topic. **No date.**
- The date lives in the `Created:` and `Updated:` fields of the manifest header.
<!-- Why the folder name carries no date: docs/skills/unikit-explore-save-rationale.md in the UniKit repository. -->
### How to save
Ask:
```
Save this research to .unikit/code/researches/?
Research name: <generated-folder-name>
Options:
1. 💾 Yes — save research
2. 🚫 No
```
Based on choice:
- Yes → save the research to `.unikit/code/researches/<slug>/`, then re-render `researches/INDEX.md`
- No → skip saving → **STOP**
If the user agrees:
1. Determine the folder name: generate `<slug>` from the research topic (4-5 words,
kebab-case, no date).
**In an ultra exploration the log was pinned**, so the folder already exists under the slug
the topic carried at that moment. Recompute the slug from the topic as it has now settled,
and when the two differ, rename the folder — **before** the manifest is written. A saved
research is never renamed.
**If that slug already names a folder in `.unikit/code/researches/`** — any folder other
than this exploration's own pinned one (ultra only) — do not resolve it silently. Ask:
```
AskUserQuestion: A research named <slug> already exists. What should happen?
Options:
1. Continue the existing research — append a session to it
2. Use another name — you type the slug
```
- *Continue* → load `{{skills_dir}}/{{self_name}}/references/continuing.md` and follow it; print
`WARN [research] <slug> exists — continuing it`.
- *Another name* → save under the slug the user gave; print
`WARN [research] <slug> exists — saving under <new-slug>`.
Both lines are printed **after** the answer, never instead of the question. An automatic
suffix (`-2`) is **forbidden**. Refusing outright is not an option either.
2. Create the research directory:
```
mkdir -p .unikit/code/researches/<slug>
```
**Write in this order.** A standard research skips items 2 and 5; ultra runs all six.
1. `SOURCE.md` (prompt-based explorations only) — it comes first, because a requirement's
anchor is grepped against it the moment the requirement is written. An ordinary
exploration writes it now, whole, from the conversation; an ultra exploration pinned
it as you talked, so what happens here is the append of whatever the log is still
missing.
2. The adaptive artifacts.
3. `RESEARCH.md` — with the `## Artifact Index` pointing at files already written.
4. `researches/INDEX.md` — **re-rendered whole** from the contents of the folders (Step 4).
5. The Integrity checks (`references/ULTRA-RESEARCH-FORMAT.md` → `## Integrity`).
6. The coherence gate.
Never write the index of artifacts before the artifacts, and never run the gate before the
registry is re-rendered.
3. Write `RESEARCH.md` — the manifest. One file carries the whole research: the machine-read header, the planner's input between the `## Active Summary` markers, the evidence in `## Findings`, and the append-only `## Sessions` log.
**In ultra mode**, the very first line of the file is the research mode marker `<!-- unikit:research-mode:ultra -->`, and a `## Artifact Index` section follows immediately after `## Table of Contents`. Both are specified in `references/ULTRA-RESEARCH-FORMAT.md`.
Take `Created:` and `Updated:` from `date`, never from memory. Both feed the registry and `Updated:` is the sort key every reader uses, so a remembered date fails **silently** — it surfaces to the user as "no researches found", not as an error.
```markdown
# <Research Title>
Created: YYYY-MM-DD HH:MM
Updated: YYYY-MM-DD HH:MM
Status: completed | in-progress | needs-follow-up
Lifecycle: active | paused | superseded
Research: <slug>
Target: SYS-<slug> | FLOW-<slug> | CONTENT-<slug> (optional)
Kind: feature | improvement (optional)
Supersedes: <slug> (optional)
## Table of Contents
- [Artifact Index](#artifact-index) — ultra mode only; omit this line in a standard research
- [Active Summary](#active-summary)
- [Findings](#findings)
- [Sub-section 1](#sub-section-1)
- [Sub-section 2](#sub-section-2)
- [Sessions](#sessions)
- [References](#references)
## Active Summary
<!-- unikit:active-summary:start -->
Topic: <1-2 sentences: what was explored>
Goal: <what this research is for — the decision it has to enable>
Scope: <in / out — the out-half carries the stop condition>
Constraints: <`C-<n>` — what the subject imposes>
Requirements: <`REQ-<n>` — "<the user's own words>" (`SOURCE.md:<from>-<to>`): <gloss>.
A requirement not sourced from the user carries `inferred` in place of a
quotation; one that departs from what they said carries `diverges`.>
Decisions: <`DEC-<n>` — taken, each with its reason in one line. A requirement marked
`diverges` is invalid without a `DEC-` here naming why the user's own proposal
was not taken.>
Risks: <`RISK-<n>` — material, not hypothetical>
Open questions: <`OQ-<n>` — unresolved; say which ones block>
Success signals: <how we will know the work landed>
Next step: <the one action that follows>
<!-- unikit:active-summary:end -->
## Findings
<Everything presented to the user during the exploration. This is the EVIDENCE and the
reasoning, never the requirement. Include ALL:
- ASCII diagrams and visualizations
- Detailed descriptions of systems and components
- Code examples and patterns found
- Comparison tables
- Architecture analysis
- Data flow descriptions
- Risk analysis and performance considerations>
## Sessions
<!-- unikit:sessions:start -->
### <YYYY-MM-DD HH:MM> — <session title>
- **What changed**: <one line per changed item, as `<ID> <name>: <old> → <new>`, with the
literal values and the tokens that carry them elsewhere. This line is the input to the
coherence gate's value sweep on the next save — prose here is paid for later in gate passes>
- **Key notes**: <what was learned>
- **Gate**: <`passed (N passes)` | `stopped at budget: <k> unresolved — OQ-…`>
- **Readback**: <`<N> shown · <M> corrected · <K> demoted to OQ`, or
`not needed (every requirement was stated)`>
- **Links (paths)**: <files read or written>
<!-- unikit:sessions:end -->
## References
<Relevant files, documentation, external resources.
If context7 or web search was used during exploration, include the library names
and specific topics that were queried — this helps reproduce or update the research later.
Example: "R3 (context7: Observable.CombineLatest usage patterns)", "DOTween (web: sequence API)">
```
Filling the `What changed` line — one item per line, values and their carriers named:
```markdown
- **What changed**: `DEC-51` direction_count: base 3 → 5 (carriers: `direction_count`, "base 3")
`DEC-49` card catalogue: 12 → 13 (carriers: `DEC-49`, "13", "thirteen")
```
**The `Readback` field is never empty.** Where there was nothing to show it reads
`not needed (every requirement was stated)`. `K > 0` names the unconfirmed remainder — those
requirements are already sitting in `Open questions:`, and the field only makes them countable.
The three numbers are counters and never a second home for the requirements themselves, and they
are not obliged to add up: `M + K` can be less than `N`, because the rest were confirmed as they
stood.
**The two state axes are separate.** `Status` is completeness, and its three values never
change — the `/unikit-plan` registry filter greps this field by name and answers "no
researches" rather than an error when it is renamed. `Lifecycle` is currency. Neither is
derivable from the other.
**Table of Contents is mandatory.** Place it immediately after the header block and before
`## Artifact Index` / `## Active Summary`. It must reflect the document's actual sections and
sub-sections — not a copy of the template above.
**`## Active Summary` is the planner's input and the only hashed region.** Write it to be read
cold, by someone who never saw the conversation. `## Findings` holds the reasoning that
produced it; the summary holds the conclusion. A fact belongs to exactly one of the two — the
other refers to it by ID, never by retelling it. A requirement pointing at a `## Findings`
sub-heading for its layout is that same reference by ID in another form — ordinary forward
resolvability, not a new mechanism.
**A requirement the user stated carries their own words and an anchor into the log.** The line
has three parts, and they do not substitute for one another:
```
`REQ-<n>` — "<the user's own words>" (`SOURCE.md:<from>-<to>`): <your gloss>
```
| Part | Role | Required |
|------|------|----------|
| the quotation | **normative** — it *is* the requirement, and it doubles as the anchor | whenever the source is something the user said |
| `` `SOURCE.md:<from>-<to>` `` | a hint for the eye, not a contract | written alongside the quotation; never checked |
| the gloss | your own formulation, in the project's terms | always; **subordinate** to the quotation, never a substitute for it |
1. **Quote the words that distinguish, not only the noun.**
2. **What gets checked is that the phrase is present, not that the line range exists** — a grep
over `SOURCE.md`, done by you at the moment the requirement is written. A quotation that
resolves to nothing blocks the save until it is repaired: the coherence gate does not read
`SOURCE.md`, so nobody downstream will catch it.
3. **The anchor is the quotation, and the line number is a hint.** Numbers that have drifted are
not a finding — repair them next time you touch the requirement.
4. **A requirement with no user source carries no anchor.** That is not an omission but a
different class, and it is marked as one below.
5. A requirement may rest on up to three places in the log, joined by `·`. More than three is a
sign that it is not one requirement; split it.
The order is not a convenience: the anchor points at whatever was the source of the wording at
the moment of writing, so the log has to be verbatim **before** an anchor is put onto it. An
anchor onto a digest is the same defect wearing a reference.
**Every requirement carries where it came from.** The marker is written inline in the line, in
backticks, and there are exactly three of them:
| Marker | Means | Rights |
|--------|-------|--------|
| `stated` | the source is something the user said; the line carries the quotation and the anchor above | trusted; not put back to them |
| `inferred` | your own conclusion — from the code, from documentation, from a defect you found | **carries none of the user's authority**; shown in the readback before it becomes the planner's input |
| `diverges` | the requirement departs from what the user actually said | **invalid without a paired `DEC-`** naming why their own proposal was not taken; shown in the readback |
1. **The marker is an attribute of the line, not a prefix.** The identifier vocabulary stays
closed at six (`### Identifiers`).
2. **`diverges` without its `DEC-` is a defect.**
3. **An unmarked requirement is read as `stated`**. An unmarked line owes a quotation and an
anchor, so the omission surfaces as a missing anchor instead of passing in silence.
4. `stated` and `inferred` mirror `extracted from SOURCE.md` and `generated` in `gd-provenance`.
Two cases worth settling in advance:
- **Partly the user's, partly yours** — they named the goal and you worked out the mechanism.
Their half stays `stated` with the quotation on it, and the worked-out half becomes its own
`REQ-` marked `inferred`. There is no mixed marker.
- **They agreed to something you proposed** ("yes, let's do that"). That is `stated`, and what
you quote is their agreement *together with* the proposal it accepted. Quote the "yes" on its
own and the anchor resolves to a word that decides nothing.
**A requirement about structure is tested for two readings before it is written down.** The
trigger is a closed list: a requirement about structure, layout, order or quantity. Not the
ones that feel suspicious, and not all of them — in a summary of twenty this catches three to
five.
The operation is to **write out both readings**, two lines, and look at what you got:
```
REQ-6 "the payload is shown as five sections"
A) five sibling frames; no "payload" container at all
B) one "payload" frame, five subsections inside it
both buildable, the code differs → FAIL
```
If the second reading **does not write** — you try and there is nothing to put on the B line —
the test has passed.
On a failure, resolve from cheapest to dearest:
1. **Put the distinguishing word from the source into the requirement.**
2. **The source does not distinguish → separate the readings with a diagram.** The diagram lives
in `## Findings`, which already holds the ASCII drawings, and the requirement carries a
reference to its sub-heading:
`` `REQ-<n>` — "<quote>" (`SOURCE.md:…`): <gloss> (layout: `## Findings` → "<sub-heading>") ``.
The diagram is **normative, not an illustration**: where it and the gloss disagree, the
diagram is the one that is right. And where the layout cannot be drawn in ASCII at all —
three-dimensional, animated, data-dependent — this rung is skipped rather than forced, and
the requirement goes to the next one.
3. **Still two readings after that → it is not a requirement but an `OQ-<n>`**, and it goes to
the readback below.
Three notes on the trigger. More than two readings fails the same way; the readback then
offers up to three readings as options and, beyond three, prints them without a question. A structural requirement the user never dictated (`inferred`)
is tested all the same. And where you cannot tell whether a requirement is structural, treat it
as structural: a false positive costs two written lines, a miss costs a replanned phase.
This is a discipline of writing, not a sixth criterion of the gate. The gate's scope is closed
at five, and one of its passes costs minutes where this costs a line.
**Language:** translate section headings and prose of `RESEARCH.md` and `SOURCE.md` into the configured language; header field names (`Created:`, `Status:`, `Lifecycle:`, …), marker comments, ID prefixes, code identifiers, code blocks and file paths stay in English — machines read them.
<!-- Where the sections of the retired three-file format went: docs/skills/unikit-explore-save-rationale.md in the UniKit repository. -->
### Identifiers
IDs are optional. Add one only when something else references it — another artifact or a
handoff. Do not add IDs to make a short note look formal.
The vocabulary is closed. Six prefixes, and adding a seventh is a decision, not a
convenience:
| Prefix | Means | Lives in |
|--------|-------|----------|
| `C-<n>` | a constraint the subject imposes | `RESEARCH.md` → `## Active Summary` → `Constraints:` |
| `REQ-<n>` | a requirement established by evidence | `## Active Summary` → `Requirements:` |
| `DEC-<n>` | a decision taken | `## Active Summary` → `Decisions:` |
| `RISK-<n>` | a material risk | `## Active Summary` → `Risks:` |
| `OQ-<n>` | an open question | `## Active Summary` → `Open questions:` |
| `ADR-<nnnn>` | a decision heavy enough to need its own file | its own file; the ID **is** the filename |
**An ID that affects the plan's requirements must exist in `## Active Summary` of `RESEARCH.md`.**
IDs that live only in `## Findings` trace the reasoning rather than state a requirement, and
that is allowed.
**One value is stated in exactly one owning section.** A number, a threshold, a set of
parameters, an enumeration, a path, a signature, the membership of a list — written once, and
referenced by ID everywhere else. `## Findings` holds the evidence and the reasoning;
`## Active Summary` holds the requirement; an artifact holds the rationale.
**Characterizing a referenced item in your own words is not a duplicate.** A table cell, a
diagram label, a consequence line and an ADR `## Context` are read where they stand, by
someone who has not opened the owning section, and they are obliged to remain readable there.
The obligation is to not repeat the **value** — `overwrites the zone size (RISK-1)` is
correct; `overwrites the zone size to 356.4 × 356.4 (RISK-1)` is a second copy of a number
that now has to be kept true by hand.
This is what makes the coherence gate decidable: whether a value appears in a second place is
grepped, whereas whether two sentences are "the same fact" is a judgement that lands
differently every time it is made.
**A value that lives in an artifact carries a revision marker in `## Active Summary`.** The
summary line carries the marker instead of the value:
```
Decisions: `DEC-9` — MoverConfig holds the reference, not a copy (rev.2 · parameters in `ADR-0003`)
```
Change a value in the artifact and raise `rev.<n>` in the same save. The raised markers are
also the input to the next save's value sweep: they name exactly which decisions have carriers
worth grepping.
**An ID is stable and is never reused.** A withdrawn question keeps its number out of
circulation; the next one takes the following number.
**A superseded item keeps its ID.** Mark it superseded, name what replaced it, and leave it
in place. `ADR-<nnnn>` additionally carries `Supersedes:` / `Status: superseded` per the ADR
format of `references/ULTRA-RESEARCH-FORMAT.md`.
Numbering is per research folder and starts at 1 — `ADR-` at `0001`, zero-padded to four.
### SOURCE.md for prompt-based explorations
If the exploration was **prompt-based** (see [Exploration mode detection](#exploration-mode-detection)), generate an additional artifact `SOURCE.md` in the same research directory.
**When it is written.** An ordinary exploration writes it at save time, whole, from the conversation, as the first write (*How to save*, step 2). An ultra exploration pinned it as the conversation went on (the pinning rule is in the ultra reference), and the save appends what is still missing.
`SOURCE.md` is a **log, not a derived representation of the research.** Two consequences, both
deliberate: it is **not** part of the hashed region (only `## Active Summary` inside
`RESEARCH.md` is), and it is **not** in the coherence gate's durable scope. A log is allowed to
be redundant with the manifest.
**Template**:
```markdown
# Exploration Request
## Original Request
<The exact text of the user's initial request/prompt that started this exploration>
## Questions & Answers
<!-- Every answer below is a blockquote carrying the user's own words. -->
### Q1: <Exact question text as it was asked>
Offered:
A) <the option exactly as it was put to the user>
B) <...>
**Answer**:
> <The user's own words, verbatim. A blockquote is the only admissible form of an
> answer the user gave. When the answer was not the user's but was discovered during
> exploration, say so on the line after the quote block.>
### Q2: <Exact question text>
**Answer**:
> <verbatim>
<!-- Continue for every question that carried subject content — framework questions are not logged -->
## Additional Clarifications
<All additional details, corrections, and clarifications the user provided, each as a
blockquote in the user's own words. If none — write "None">
## Conclusion
<The final result of the exploration: what was decided, what approach was chosen, what
understanding was reached. This is the one section written in your own words.>
```
**An answer is a quotation, never a digest.** The blockquote is the only admissible form of an
answer the user gave.
**A choice with no recorded menu is not an answer.** When the answer picked among options you
put to the user, write those options down next to it under `Offered:`. Without them a reply
like "option 1" or "let's go with B" resolves to nothing, and the only surviving way to learn
what was agreed is the document derived from this log — which leaves the derived document
certifying itself.
The readback menus of Step 3.5 are menus in exactly this sense.
**Framework questions are not logged.** Three exchanges belong to the machinery of this skill,
not to the subject, and are left out of the log whole — the question and its answer: *Save this
research?*; the choice of the folder name together with the existing-slug question; and the
coherence gate's question when its budget is spent. The list is closed. For anything outside it
the test is whether the answer could become or change a requirement, a constraint or a
decision of the subject: if it could, the exchange is logged, whoever asked. That is why the
readback is **not** on this list — its answer (`Yours`, `A` / `B`, a correction in the user's
own words) becomes the anchor of a requirement. When the answer to a listed question carries
subject content as well ("yes, but also include X"), the content part of the answer is logged
as an ordinary user turn, and the service part ("yes") is not. What is left out is a whole
exchange, never words inside another exchange.
**Mark your own cuts, and only your own.** `[…]` means *you* left something out of the
quotation. A bare `…` means the user spoke that way — trailed off, paused, thought better of a
sentence. Two different things get two different marks.
**Never cut inside a noun phrase that names a structure, an order or a quantity.** What tells
two such requirements apart is rarely the noun — it is the modifiers standing next to it. "A
separate block of the same kind" and "a block" carry the same noun and describe different
designs, and the whole of the difference sits in the words a cut removes first, because they
are the ones that read as decoration.
**When in doubt, do not cut.** The cost of an extra quoted line is measured and small; the
cost of a cut modifier is a phase planned against a requirement nobody stated.
The one admissible exception is a secret: a credential, a token or a key quoted by accident is
cut, marked `[…]`, and the reason is written on its own line under the quote.
**When the user's words are no longer there.** The log is written from the conversation as it
stands in your context. If the oldest part of that context is a summary of earlier turns instead
of the turns themselves — the client compacted it — those turns cannot be quoted, and a digest
must not pass for a quotation. Do not reconstruct them. Put a section directly under
`# Exploration Request`:
```markdown
## Earlier turns (not verbatim)
<!-- unikit:not-verbatim -->
These turns reached the log as a summary carried by the context, not as the user's words.
<the summary, as short as the context allows>
```
`## Original Request` then holds the exact request if it is still visible, and otherwise the
single line `Not available verbatim — see the section above.` A requirement that rests only on
what that section covers is not `stated`: it is `inferred`, with no quotation and no anchor, and
it goes through the readback — the user's answer there is logged and becomes its anchor. What you
can see is a summary block at the start of the context, and that is all this check can rely on:
it is a heuristic, not a guarantee.
**On a continuation, append — never rewrite.** A new session adds a fresh block
`## Session <YYYY-MM-DD HH:MM>` at the end of the file, carrying that session's questions,
answers and clarifications. Everything already in the file stays exactly as it is.
### Step 3.5: Readback
Before the registry is re-rendered and before the gate runs, put back to the user the
requirements they have not actually seen.
**What goes in — three classes, and only these:**
| Class | Where it comes from |
|-------|---------------------|
| `inferred` | the provenance marker |
| `diverges` | the provenance marker |
| failed the two-readings test | the third rung of the resolution ladder |
A requirement marked `stated` that passed the test is **not shown**. Out of twenty-two
requirements it typically prints three.
**One question per requirement, at most four per `AskUserQuestion` call** — `k` requirements take ⌈k/4⌉ calls, in ID order.
1. **Print the grounds first, then ask.** Before each call, print the grounds of every requirement that call carries, as plain markdown in a block of their own — the question mechanism carries the options and nothing else:
```
Before saving — <N> requirements out of <M>. The rest go as they are.
<ID> <CLASS>
I wrote: "<your formulation>"
You said: "<the quotation>" <anchor>
Grounds: <the code, the log line or the document it rests on>
Other path: <the alternative> — cost: <what it would change>
```
`You said:` is printed for `diverges` and for two readings — there it is the quotation the readings interpret. For two readings, `Other path:` gives way to the readings themselves — `A) …`, `B) …` (and `C) …`), each with what it would build.
2. **Ask.** One question per requirement, its options set by the class:
| Class | Options |
|-------|---------|
| `diverges` | `Mine (DEC-<n>)` · `Yours` · `Open question` |
| `inferred` | `Requirement` · `Finding only` · `Open question` |
| two readings | `A` · `B` · `Open question` — three readings: `A` · `B` · `C` · `Open question`; more than three: the readings are printed with the grounds, no question is asked, and the item stays the `OQ-<n>` it already is |
The free answer ("Other") is added by the tool itself — never list it as an option.
3. **"Recommended" only on a two-readings question**, and only on the reading your code or log evidence supports — that evidence is its `Grounds:` line. The `diverges` and `inferred` menus stay neutral: no option is marked.
4. **No `AskUserQuestion` → the same questions as numbered text**, one block per requirement, answered by number (`REQ-8 — 1, OQ-5 — B`). Print them, then **end your turn and wait for the answer** — do not go on with the save:
```
REQ-8 inferred
1) Requirement 2) Finding only 3) Open question
```
**What the answers do:**
- `Mine (DEC-<n>)` → the requirement keeps its marker and its `DEC-`; nothing is rewritten.
- `Yours` → rewrite the requirement to the user's original words, mark it `stated`, and keep its anchor on the original quotation — it is in the log already. Its paired `DEC-<n>` is marked superseded by this requirement.
- `Requirement` → the `inferred` marker stays and nothing is rewritten.
- `Finding only` → the line leaves `Requirements:` and its content moves to `## Findings`. Its `REQ-<n>` is **not reused**; the next requirement takes the following number.
- `A` / `B` / `C` → the item is already an `OQ-<n>` (the third rung of the resolution ladder); the chosen reading becomes a `REQ-` with the next number, marked `stated` and anchored on the logged answer, and the `OQ-<n>` keeps its number and is marked superseded by that `REQ-<m>`.
- `Open question` → the requirement leaves `Requirements:` and becomes an `OQ-<n>` with the next number; a `diverges` item's paired `DEC-<n>` is marked superseded. An item that already is an `OQ-<n>` (two readings) stays as it is — no second number.
- **"Other" — a correction** → `Edit` the `RESEARCH.md` already on disk: rewrite the requirement, change its marker to `stated`, and anchor it to the user's words from this very exchange — they are in the log already, appended as they were said (see *Order of writes*). A correction that turns out to be a new requirement opens a new `REQ-` with the next number, marked `stated`.
- **"Other" — a counter-question** → answer it, then put the same menu for that requirement again.
**Order of writes.** Append the readback exchange — the printed grounds, the question, `Offered:` and the answer — to `SOURCE.md` **before** the `Edit` it causes: the quotation a correction or a chosen reading is anchored on must already be in the log when the requirement is written, because that phrase is grepped at that moment.
**Counting for the `Readback` field of `## Sessions`.** `Yours`, `Finding only`, `Open question` on an item that was not already an `OQ-<n>`, `A` / `B` / `C` and an "Other" correction count as `corrected`; `Mine` and `Requirement` do not. `<K> demoted to OQ` counts only what **Nobody answered** demoted — never an item that already was an `OQ-<n>`.
**A text-tier question left unanswered** — the next message is about something else — applies **Nobody answered** and continues the save from Step 4: `RESEARCH.md` is already on disk and must not stay unchecked by the registry and the gate.
**Log every readback question in `SOURCE.md`** (prompt-based explorations; a file-based one has no log) like any other menu: the question, `Offered:` with the option labels — for `A` / `B` / `C` each label together with the text of its reading, since a letter alone resolves to nothing — and the chosen label verbatim; a free answer is a blockquote, as every answer is.
**Nobody answered** — the user left, or interrupted. The save is **not** cancelled. Every
requirement of the three classes that went unconfirmed is demoted to an `OQ-<n>` and leaves
`Requirements:`, and one line is printed:
```
WARN [readback] <k> requirements went unconfirmed — demoted to OQ
```
An item that already is an `OQ-<n>` (two readings) stays as it is and is not counted in `<k>`.
**Nothing to show** — every requirement is `stated` and every test passed. Skip the step in
silence: no block, no line.
### Step 4: Re-render the Researches Index
`.unikit/code/researches/INDEX.md` is a **generated** registry. It is not edited and it is not
appended to: on every save it is re-rendered whole from the folders on disk.
1. List the subfolders of `.unikit/code/researches/`.
2. For each, read its `RESEARCH.md` and take: the **first line** (the mode marker — present
means `ultra`, absent means `standard`), the H1 title, the header fields `Created` /
`Updated` / `Status` / `Lifecycle` / `Target`, and the `Topic:` line from
`## Active Summary` — that line **is** the `Summary`.
3. A folder with no readable `RESEARCH.md` is skipped, and the skip is **printed**. Which line
gets printed depends on what the folder holds.
A folder carrying a `SOURCE.md` and no manifest is an **unfinished exploration**, not a
broken one — an ultra conversation whose pinned log ended before it was saved, or a save that
stopped after the log, which is written first. It stays out of the registry all the same, and
it is announced by a line that names the way back into it:
```
NOTE [research] <folder> — unfinished exploration: SOURCE.md is there, the manifest is not.
Continue it with: /unikit-explore <folder>
```
`NOTE`, not `WARN`: the work resumes through `references/continuing.md`.
Every other folder without a manifest keeps the line it has always had:
```
WARN [research] skipped <folder> — no readable RESEARCH.md
```
Skipping in silence is forbidden. What skips a folder is the missing manifest, and nothing else.
4. Overwrite the file whole. The header is always:
```markdown
# Researches Index
> Generated by /unikit-explore on every save. Do not edit manually — edits are overwritten.
```
5. Each record:
```markdown
---
### <Research Title>
- **Created**: YYYY-MM-DD HH:MM
- **Updated**: YYYY-MM-DD HH:MM
- **Status**: completed | in-progress | needs-follow-up
- **Lifecycle**: active | paused | superseded
- **Mode**: standard | ultra
- **Summary**: <1-2 sentences, from `Topic:`>
- **Path**: `<slug>/`
- **Target**: SYS-<slug> (optional, omit the line when absent)
```
6. Sort by `Updated` descending; ties broken by `Created` descending; then by folder name.
`Updated` is the key on purpose — in a research with a continuation cycle, freshness means
"when this was last confirmed", not "when the folder was opened".
7. After the re-render, print the reconciliation summary:
```
researches/INDEX.md re-rendered:
- Kept: N
- Added: N (list names)
- Removed: N (list names)
```
<!-- Why reconciliation stopped being a separate command: docs/skills/unikit-explore-save-rationale.md in the UniKit repository. -->
### Research Coherence Gate
After **all** writing is done — the artifacts, `RESEARCH.md`, `SOURCE.md` and the re-rendered
`researches/INDEX.md` — and **before** confirming the save to the user, hand the path
`{{skills_dir}}/{{self_name}}/references/coherence-gate.md` to a `check-agent` dispatch, and
run the gate it specifies through that dispatch. The agent reads the file; this session does
not, because this is the peak of the session and only the report is needed here.
Act on the report's `Next:` line, and on nothing the report does not carry:
- `pass` → write the `Gate:` value it names, record the remainder it lists, go to Step 5;
- `repair` → apply the listed repairs as one batch, keeping to the repair rules the report
quotes, then dispatch pass 2 with the findings adjudicated so far;
- `ask` → put the unresolved findings, the count per pass and the four options to the user,
and on a save write the `OQ-<n>` entries and the `Gate:` value it names;
- `hold` → do not confirm; tell the user which durable file could not be read.
Only when the dispatch cannot run does this session read the file itself (the `check-agent`
fallback).
If `references/coherence-gate.md` does not exist, or the pass reports that it cannot read it,
print one line `WARN [coherence] reference missing — saving without the coherence pass` and
continue.
### Step 5: Confirm the save
Only once the gate has **finished** — passed, or stopped at its budget with the user's answer in
hand — tell the user what was written: the folder, the manifest, any adaptive artifacts, and the
registry line. Until then the save is not confirmed.
A save the user authorised over surviving findings is confirmed like any other; its session
entry then reads `stopped at budget: <k> unresolved`, not `passed`. That record is where the
override lives — not a withheld confirmation.
### Important rules for saving
- **Don't auto-save** — Always offer and let the user decide. What the ban covers is saving the **research**: the manifest, the adaptive artifacts, the re-rendered registry, the gate and the confirmation. Ultra pinning falls outside it — appending a turn to `SOURCE.md` is not a save, and neither is creating `<slug>/` to hold it
- **Generate the name** from the research context — don't ask the user to name it
- The user may edit the suggested name before you save
---
## Superseding a research
When a new research replaces an old one rather than continuing it, the new one carries
`Supersedes: <slug>` in its header and the old one is set to `Lifecycle: superseded`. The
superseded folder is **not deleted** — it is the trace of the reasoning, and removing it makes
the replacement look unmotivated.
---
## Handling Different Entry Points
**User brings a specific problem** — the shape every entry follows: read the code, draw what is there, and ask the one question that decides where to go next:
```
User: The inventory system is a mess, everything is coupled
You: [reads codebase]
+----------------------------------------------+
| CURRENT INVENTORY FLOW |
+----------------------------------------------+
|
+-------------+-------------+
v v v
+---------+ +---------+ +---------+
| Item | | Slot | | UI |
| Manager | | System | | Display |
+----+----+ +----+----+ +----+----+
| | |
+------+------+------+------+
| |
v v
+---------+ +---------+
| Save | | Zenject |
| System | | Binding |
+---------+ +---------+
I see tight coupling in three places. Which one hurts most?
```
The other entries take the same shape:
- **A vague idea** ("adding an object pooling system") — map the space in one diagram, simple to advanced, then ask what narrows it ("What are you pooling?")
- **A comparison** ("two async approaches for our loading") — check what the project already uses before comparing, and ground the table in that usage
- **Stuck mid-implementation** (`/unikit-explore save-load-system`) — read the active plan in `.unikit/code/plans/`, find the task, trace what it involves, and offer to capture the result as a research
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into action**: "Ready to plan? Run `/unikit-plan`"
- **Result in saved research**: "Saved to `.unikit/code/researches/inventory-decoupling/`"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When it feels like things are crystallizing, you might summarize:
```
## What We Figured Out
**The problem**: [crystallized understanding]
**The approach**: [if one emerged]
**Open questions**: [if any remain]
**Next steps** (if ready):
- Save research: I'll create a research record
- Create a plan: /unikit-plan [fast|full|ultra] <description>
- Keep exploring: just keep talking
```
But this summary is optional. Sometimes the thinking IS the value.