leyline-code 0.8.1__py3-none-any.whl
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.
- leyline/__init__.py +3 -0
- leyline/adapters/__init__.py +19 -0
- leyline/adapters/csharp.py +1026 -0
- leyline/adapters/generic.py +844 -0
- leyline/adapters/python.py +929 -0
- leyline/adapters/typescript.py +1512 -0
- leyline/affected.py +509 -0
- leyline/agent_skills.py +311 -0
- leyline/change.py +500 -0
- leyline/channels.py +1782 -0
- leyline/cli.py +1029 -0
- leyline/cluster.py +149 -0
- leyline/context.py +531 -0
- leyline/coupling.py +485 -0
- leyline/coverage.py +376 -0
- leyline/diagrams.py +680 -0
- leyline/diff.py +1078 -0
- leyline/drift.py +747 -0
- leyline/exact.py +410 -0
- leyline/explain.py +973 -0
- leyline/export.py +673 -0
- leyline/fncoupling.py +328 -0
- leyline/formats.py +923 -0
- leyline/grade.py +207 -0
- leyline/incremental.py +931 -0
- leyline/indexer.py +3733 -0
- leyline/learnings.py +743 -0
- leyline/loop.py +584 -0
- leyline/model.py +142 -0
- leyline/outline.py +1066 -0
- leyline/packing.py +86 -0
- leyline/patterns.py +478 -0
- leyline/pr.py +1249 -0
- leyline/props.py +255 -0
- leyline/query.py +683 -0
- leyline/quick.py +853 -0
- leyline/related.py +266 -0
- leyline/removal.py +231 -0
- leyline/renames.py +231 -0
- leyline/rereview.py +375 -0
- leyline/roslyn_refs/Program.cs +179 -0
- leyline/roslyn_refs/RoslynRefs.csproj +16 -0
- leyline/rules.py +171 -0
- leyline/schema.sql +178 -0
- leyline/scip_pb2.py +97 -0
- leyline/server.py +1230 -0
- leyline/skills/leyline-adversarial-review/SKILL.md +121 -0
- leyline/skills/leyline-ask/SKILL.md +57 -0
- leyline/skills/leyline-change-impact/SKILL.md +79 -0
- leyline/skills/leyline-explain-flow/SKILL.md +70 -0
- leyline/skills/leyline-explore-module/SKILL.md +55 -0
- leyline/skills/leyline-pr-review/SKILL.md +77 -0
- leyline/skills/leyline-quick-change/SKILL.md +63 -0
- leyline/skills/leyline-spec/SKILL.md +179 -0
- leyline/skills/leyline-tour/SKILL.md +46 -0
- leyline/spec.py +1819 -0
- leyline/store.py +560 -0
- leyline/tours.py +352 -0
- leyline/verdicts.py +390 -0
- leyline/viewer/viewer.html +1622 -0
- leyline_code-0.8.1.dist-info/METADATA +1325 -0
- leyline_code-0.8.1.dist-info/RECORD +65 -0
- leyline_code-0.8.1.dist-info/WHEEL +4 -0
- leyline_code-0.8.1.dist-info/entry_points.txt +2 -0
- leyline_code-0.8.1.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: leyline-spec
|
|
3
|
+
description: Turn a change someone wants into an OpenSpec change folder checked against a Leyline map, and verify it after it is implemented. Use when asked to plan, spec, scope or design a change to a mapped codebase, or to check that an implemented change matches its spec.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Spec a change with Leyline
|
|
7
|
+
|
|
8
|
+
The person decides the design. You write it down in a form that can be checked, and Leyline checks
|
|
9
|
+
it against the code. The person should finish able to say three things in a sentence each: what
|
|
10
|
+
code will be written, what it will affect, and how they will know it was done.
|
|
11
|
+
|
|
12
|
+
For a change of a function or two and a constant, with no design to agree (a value, a one-line fix), skip the folder:
|
|
13
|
+
use `quick` (`leyline quick "<what>" --about <names>` before, `--done quick-<slug>` after), and write a spec only if its
|
|
14
|
+
answer says the change grew. The leyline-quick-change skill says how. Code someone else already wrote, with no spec, is
|
|
15
|
+
reviewed with the leyline-pr-review skill.
|
|
16
|
+
|
|
17
|
+
It works with the Leyline MCP server or with the `leyline` command alone. Without the server, use the
|
|
18
|
+
command for each tool this skill names:
|
|
19
|
+
|
|
20
|
+
| MCP tool | Command |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `map` | `leyline map <repo>` |
|
|
23
|
+
| `plan` (with `test_output`) | `<test command> \| leyline plan <id> --tests -` (`--new-baseline` for `new_baseline`) |
|
|
24
|
+
| `check` (with `test_output`) | `<test command> \| leyline check <id> --tests -` |
|
|
25
|
+
| `overview`, `search`, `expand`, `impact`, `source` | `leyline overview`, `leyline search <text>`, `leyline expand <id>`, `leyline impact <name>`, `leyline source <id>` |
|
|
26
|
+
| `spec_review_facts` | `leyline spec facts <id> --reviewer logic` (or `performance`) |
|
|
27
|
+
| `spec_finding` | `leyline spec finding <id> --reviewer logic --severity medium --claim "..." --evidence <node id> --proposal "..."` |
|
|
28
|
+
| `spec_findings`, `spec_resolve` | `leyline spec findings <id>`, `leyline spec resolve <finding id> accepted\|rejected\|deferred "why"` |
|
|
29
|
+
| `check` (with `coverage_path`) | `... \| leyline check <id> --tests - --coverage .coverage` (per-test coverage: `pytest --cov --cov-context=test`) |
|
|
30
|
+
| `affected_tests` | `leyline affected-tests <id>`: the tests to run for the change, as a command |
|
|
31
|
+
| `drift` (`accept`) | `leyline drift` (`--accept`) |
|
|
32
|
+
|
|
33
|
+
Where this skill says a result field (`next`, `status.blocking`, `page`), the command prints the same
|
|
34
|
+
thing: the `Next:` line, the "Before implementation" list, and the plan itself. If neither the server nor
|
|
35
|
+
the command is there, say so and stop.
|
|
36
|
+
|
|
37
|
+
The path has three Leyline calls, the same three commands the person can type:
|
|
38
|
+
|
|
39
|
+
1. `map`, only if the code was never mapped (`overview` returns no repos).
|
|
40
|
+
2. You write the spec, then `plan`; review; the person decides; `plan` again until nothing blocks.
|
|
41
|
+
3. Someone implements the tasks, then `check`.
|
|
42
|
+
|
|
43
|
+
`plan` and `check` re-map changed code on their own and return `next`. Follow it, or tell the person
|
|
44
|
+
why not.
|
|
45
|
+
|
|
46
|
+
## Before any code: write the spec
|
|
47
|
+
|
|
48
|
+
1. **Hear the change.** Restate it in two sentences. If it could mean two designs, ask which.
|
|
49
|
+
2. **Look before writing.** `overview`, `search`, `expand`, `impact` on what the change touches (the
|
|
50
|
+
leyline-ask skill, for a question about how the code works now).
|
|
51
|
+
Tell the person anything that makes the change bigger or different than they said. Do this before
|
|
52
|
+
drafting: a spec written first and checked second anchors on the wrong shape.
|
|
53
|
+
3. **Write the OpenSpec folder** at `openspec/changes/<kebab-id>/`:
|
|
54
|
+
- `proposal.md`: `# Change: <title>`, then `## Why`, `## What Changes`, `## Impact`.
|
|
55
|
+
- `specs/<capability>/spec.md`: `## ADDED Requirements` (or MODIFIED, REMOVED), each
|
|
56
|
+
`### Requirement: <name>` with at least one `#### Scenario: <name>` (four `#`) and
|
|
57
|
+
`- **WHEN** ...` / `- **THEN** ...` lines.
|
|
58
|
+
- `tasks.md`: `- [ ] 1.1 <task>` lines.
|
|
59
|
+
- `design.md` only when there is a real decision to record.
|
|
60
|
+
4. **Follow three conventions**, because the check depends on them:
|
|
61
|
+
- Name code in backticks in tasks: `` `Vehicle.Speed` ``, `` `Queue.Push` ``. New code
|
|
62
|
+
states its home: `` `Owner.NewName` `` for a member; `` `module.new_func` ``,
|
|
63
|
+
`` `path/to/file.py: new_func` `` or `` `new_func` in `file.py` `` for a top-level function. A task
|
|
64
|
+
that names no code (docs) is left for the person to check; it does not block.
|
|
65
|
+
- Start each task with what it does: add, remove, rename, change the signature of. One task, one
|
|
66
|
+
thing a person could tick. The code right after the verb (and any joined to it by "and" or a
|
|
67
|
+
comma) is what the task changes; other code in the sentence, such as `` `SimConfig.QueueSpeed` `` in
|
|
68
|
+
"counting vehicles slower than ...", is context the plan shows as "mentions" and does not count.
|
|
69
|
+
- Name each scenario so a test can carry the same name. Add a task to write that test, quoting
|
|
70
|
+
the scenario's name: `- [ ] 3.1 Add the test "<scenario name>"`.
|
|
71
|
+
- A scenario that states an invariant ("for any amount, the balance is never negative"; always,
|
|
72
|
+
never, for every) gets a property test, not one example: the plan marks it "invariant: a property
|
|
73
|
+
test fits" and names the library. Name the test like the scenario. The shape, for "Balance is
|
|
74
|
+
never negative":
|
|
75
|
+
```python
|
|
76
|
+
@given(st.integers(min_value=0), st.integers()) # Hypothesis
|
|
77
|
+
def test_balance_is_never_negative(balance, amount): assert apply(balance, amount) >= 0
|
|
78
|
+
```
|
|
79
|
+
```ts
|
|
80
|
+
it("Balance is never negative", () => // fast-check
|
|
81
|
+
fc.assert(fc.property(fc.nat(), fc.integer(), (b, a) => apply(b, a) >= 0)));
|
|
82
|
+
```
|
|
83
|
+
```csharp
|
|
84
|
+
[Property] public bool Balance_is_never_negative(NonNegativeInt b, int a) => Apply(b.Get, a) >= 0; // FsCheck
|
|
85
|
+
```
|
|
86
|
+
When it fails, `check` shows the shrunk counterexample: "contradicted: fails for amount=-1".
|
|
87
|
+
- "Remove `X`" is done when `X` is gone and nothing still calls it; "Rename `X` to `Y`" when `X`
|
|
88
|
+
is gone, `Y` is there and nothing still calls `X`. Name the callers' edits in tasks too.
|
|
89
|
+
5. **Record the tests as they pass now**, before anyone edits code: run the test suite and pass its
|
|
90
|
+
output to `plan` as `test_output` (TAP from vitest or node --test, `pytest -rA`, `go test -v`, `jest
|
|
91
|
+
--verbose`, `dotnet test --logger "console;verbosity=detailed"`, or one PASS or FAIL line per test; several
|
|
92
|
+
runners' output can go in one text), or the
|
|
93
|
+
results as `test_results`. Without it, `check` cannot tell a test the change broke from one that
|
|
94
|
+
already failed. If you cannot run the tests, say so and give the person the command
|
|
95
|
+
(`<test command> | leyline plan <id> --tests -`).
|
|
96
|
+
6. **Call `plan`** with the folder. Read `status.blocking`. Fix the spec, not the tool: an ambiguous name
|
|
97
|
+
gets its owner, a must-edit with no task gets a task or a sentence in the proposal saying why not,
|
|
98
|
+
a scenario with no test gets a task. Call it again until `status.blocking` is empty or each
|
|
99
|
+
remaining item is a choice the person made.
|
|
100
|
+
Then read the lines under "Must agree with the change" (other ends of a channel the change crosses,
|
|
101
|
+
such as a second program that reads the same pipe) and "Shares a caller or a field with the change,
|
|
102
|
+
and no task names it". Each is code that shares a channel, a caller, a field or a look-alike new
|
|
103
|
+
member with the change. For each, either propose a task (see "Proposing a missing task") or be able
|
|
104
|
+
to say why it is right to leave alone. Do not pass this list to the person undigested.
|
|
105
|
+
"Usually changes with the files the tasks touch" comes from git history, not the map: a doc, schema,
|
|
106
|
+
fixture or test-case folder that changed in most past commits to a file the tasks touch. Treat it the
|
|
107
|
+
same way: a task, or a reason it is not needed this time.
|
|
108
|
+
7. **Show the person `leyline.md`** (also returned as `page`). It is one page. Do not summarise it
|
|
109
|
+
into something longer. Say which lines you are least sure of.
|
|
110
|
+
8. **Get it reviewed** (the `leyline-adversarial-review` skill, once for logic and once for
|
|
111
|
+
performance) before anyone implements. The person decides each finding; then call `plan` again.
|
|
112
|
+
|
|
113
|
+
## After implementation: check
|
|
114
|
+
|
|
115
|
+
1. Run the tests and call `check` with the folder and their output (`test_output` or `test_results`).
|
|
116
|
+
It re-maps the code itself.
|
|
117
|
+
2. Report `done_as_agreed` first. Then each task not done, each scenario not proven, and each edit
|
|
118
|
+
outside the spec. For every edit outside the spec, read its source and say whether the spec was
|
|
119
|
+
incomplete or the implementation wandered. The person decides which; do not decide for them. Propose
|
|
120
|
+
the task, or the undo, as in "Proposing a missing task".
|
|
121
|
+
Each task and scenario carries a `verdict` (proven, partial, contradicted, inconclusive, needs a
|
|
122
|
+
person) and `verdict_why`; quote them as they are, and name every item that needs a person.
|
|
123
|
+
3. Pass on `next`.
|
|
124
|
+
|
|
125
|
+
A check that finds the change done as agreed records what each code name in it means now, in
|
|
126
|
+
`openspec/leyline-anchors.json`. Commit that file with the spec. Later, `drift` says which names in the
|
|
127
|
+
living specs and finished changes have gone, moved or changed signature; `plan` lists those the new
|
|
128
|
+
change touches under "Specs that no longer match code this change touches". Update each such spec with
|
|
129
|
+
the change. Call `drift` with `accept=true` only when the person says the specs and the code agree.
|
|
130
|
+
|
|
131
|
+
`spec_brief`, `spec_verify` and `record_test_run` are the steps inside `plan` and `check`, for when one
|
|
132
|
+
is needed alone. `plan` stores its test run as `before:spec-<id>` and `check` as `after:spec-<id>`.
|
|
133
|
+
|
|
134
|
+
## Proposing a missing task
|
|
135
|
+
|
|
136
|
+
When `check` lists an edit outside the spec, or `plan` lists a line under "Shares a caller or a field with the
|
|
137
|
+
change, and no task names it" that needs a task:
|
|
138
|
+
|
|
139
|
+
1. Draft the exact line to add to `tasks.md`, numbered after the tasks around it, in the conventions above: it
|
|
140
|
+
starts with add, remove, rename or "change the signature of" when that is what it does, and names the code in
|
|
141
|
+
backticks with its owner: `` - [ ] 2.4 Change the signature of `Queue.push` to take a priority ``.
|
|
142
|
+
2. Show the person the line and its evidence: the node, what was edited or what it shares with the change, and
|
|
143
|
+
the source you read. For an edit outside the spec, also propose the other way out: undo the edit, and say what
|
|
144
|
+
undoing it loses.
|
|
145
|
+
3. Add the line only when the person agrees. If they choose the undo, undo that edit and nothing else.
|
|
146
|
+
4. Call `plan` again, and `check` again once the code is written.
|
|
147
|
+
|
|
148
|
+
## OpenSpec's verify
|
|
149
|
+
|
|
150
|
+
If the project uses OpenSpec's expanded profile and `/opsx:verify` is there, you may run it after `check`. Its
|
|
151
|
+
findings are an LLM's opinion, not a verdict. File each CRITICAL or WARNING as a finding with `spec_finding`, so
|
|
152
|
+
it is tracked and the person resolves it like any other:
|
|
153
|
+
|
|
154
|
+
- reviewer `logic` (Leyline has only logic and performance);
|
|
155
|
+
- severity high for CRITICAL, medium for WARNING;
|
|
156
|
+
- the claim in one sentence, starting "/opsx:verify:";
|
|
157
|
+
- as evidence, the node ids it is about (`search`, `expand`). A finding needs at least one node on the map; one
|
|
158
|
+
you cannot tie to a node goes in your report to the person instead, saying so.
|
|
159
|
+
|
|
160
|
+
The two verdicts are separate. Report `check`'s `done_as_agreed` as it is, whatever `/opsx:verify` says, and do
|
|
161
|
+
not drop a `/opsx:verify` finding because `check` passed.
|
|
162
|
+
|
|
163
|
+
## When the spec changes part-way
|
|
164
|
+
|
|
165
|
+
Edit the spec and call `plan` again. Once the code has changed it answers `baseline: kept`: the
|
|
166
|
+
picture of the code from the first plan stays, so `check` still compares with the code as it was
|
|
167
|
+
before any edit, and test output passed to `plan` then is refused as a baseline. Use `new_baseline`
|
|
168
|
+
only to abandon what was done and start over.
|
|
169
|
+
|
|
170
|
+
A new function that only code named in the spec calls is reported as a helper, not as an edit outside
|
|
171
|
+
the spec. Read it anyway.
|
|
172
|
+
|
|
173
|
+
## Rules
|
|
174
|
+
|
|
175
|
+
- Never tick a task or call a change done from the agent's own report. `check` reads the code.
|
|
176
|
+
- Never add a task or tick one without the person.
|
|
177
|
+
- A scenario with no test is unproven, however obvious it looks.
|
|
178
|
+
- Keep the spec short. If the brief does not fit on a page, the change is two changes.
|
|
179
|
+
- You do not resolve review findings. The person does.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: leyline-tour
|
|
3
|
+
description: Write a guided tour of a codebase on a Leyline map, or walk someone through an existing tour. Use when asked to onboard someone, explain how a feature works end to end, or say what to read before changing a part of the code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Write a tour with Leyline
|
|
7
|
+
|
|
8
|
+
A tour is an ordered list of stops on the map. Each stop points at one thing (a module, a type, a
|
|
9
|
+
function, a flow, a pattern, a saved view) and says what to notice there. Use this when the user
|
|
10
|
+
wants to learn a codebase or a slice of it, or wants something to hand to another person. It needs
|
|
11
|
+
the Leyline MCP server connected. If the `tours` tool is missing, say so and stop.
|
|
12
|
+
|
|
13
|
+
Leyline already writes an orientation tour on every index. Read it first (`tours`, then `tour`):
|
|
14
|
+
often the user wants that, or a narrower tour that starts where it ends. To explain one flow in the
|
|
15
|
+
conversation, with nothing saved, use the leyline-explain-flow skill; for what one module holds, leyline-explore-module.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
1. **Settle who it is for and what they will do next.** "New to the repo" and "about to change the
|
|
20
|
+
scoring code" need different tours. If the request does not say, ask one question.
|
|
21
|
+
2. **Find the spine.** A tour follows something: the path of one request, the life of one piece of
|
|
22
|
+
data, or the dependency order of a few modules. Use `overview`, `flows` with `through`, `trace`
|
|
23
|
+
and `patterns` to find it. Pick one spine. Do not mix two.
|
|
24
|
+
3. **Choose 5 to 9 stops.** For each, call `expand` and read `source`. Only point at code you read.
|
|
25
|
+
Prefer a function to its type and a type to its module: the narrower the stop, the more the
|
|
26
|
+
reader sees. Use a `flow` stop when order matters and a `pattern` stop when a structure does.
|
|
27
|
+
4. **Write each narrative** in two to four sentences: what this is, why the tour stops here, and
|
|
28
|
+
what to carry to the next stop. Say what you inferred and what the graph states. Name the thing
|
|
29
|
+
the reader should look at in the source panel (a field, a branch, a call).
|
|
30
|
+
5. **Order them the way you would explain it aloud.** Start where the reader already has footing,
|
|
31
|
+
usually an entry point or a test. End with what to do next or where the edges of the tour are.
|
|
32
|
+
6. **Call `save_tour`** with a title that says what the tour covers, the audience, and the stops as
|
|
33
|
+
`{"title", "kind", "ref", "narrative"}`. Check `missing` in the result: a stop whose id does not
|
|
34
|
+
exist was dropped.
|
|
35
|
+
7. **Tell the user** the tour is under the Tour tab after a refresh, and give the stop titles in
|
|
36
|
+
order so they can redirect you before reading it.
|
|
37
|
+
|
|
38
|
+
## Rules
|
|
39
|
+
|
|
40
|
+
- A tour is a reading order, not a summary. If a stop has nothing to look at, cut it.
|
|
41
|
+
- Do not restate the orientation tour. Link to where it left off.
|
|
42
|
+
- State blind spots: if the path crosses a process, an event or a file, say the link is not in the
|
|
43
|
+
type system and name both sides.
|
|
44
|
+
- Before saving, check every sentence against the source you read. A wrong sentence in a tour does
|
|
45
|
+
more harm than a missing stop, because the reader trusts the order.
|
|
46
|
+
- Keep sentences plain. No stop needs more than four.
|