ambitgraph 0.0.1__tar.gz → 0.2.0__tar.gz
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.
- ambitgraph-0.2.0/LICENSE +25 -0
- ambitgraph-0.2.0/MANIFEST.in +25 -0
- ambitgraph-0.2.0/PKG-INFO +83 -0
- ambitgraph-0.2.0/README.md +55 -0
- ambitgraph-0.2.0/ambit_map/__init__.py +7 -0
- ambitgraph-0.2.0/ambit_map/__main__.py +6 -0
- ambitgraph-0.2.0/ambit_map/ambit-map-enrich.md +54 -0
- ambitgraph-0.2.0/ambit_map/ambit-mapllm-contract.md +246 -0
- ambitgraph-0.2.0/ambit_map/cli.py +223 -0
- ambitgraph-0.2.0/ambit_map/daemon.py +1029 -0
- ambitgraph-0.2.0/ambit_map/dialects/__init__.py +5 -0
- ambitgraph-0.2.0/ambit_map/dialects/contract.py +1500 -0
- ambitgraph-0.2.0/ambit_map/dialects/express_pack.py +220 -0
- ambitgraph-0.2.0/ambit_map/dialects/fastapi_pack.py +164 -0
- ambitgraph-0.2.0/ambit_map/dialects/flask_pack.py +184 -0
- ambitgraph-0.2.0/ambit_map/dialects/nextjs_pack.py +127 -0
- ambitgraph-0.2.0/ambit_map/dialects/supabase_pack.py +335 -0
- ambitgraph-0.2.0/ambit_map/export.py +321 -0
- ambitgraph-0.2.0/ambit_map/families.py +255 -0
- ambitgraph-0.2.0/ambit_map/graph.py +1949 -0
- ambitgraph-0.2.0/ambit_map/island_layout.py +289 -0
- ambitgraph-0.2.0/ambit_map/islands.py +336 -0
- ambitgraph-0.2.0/ambit_map/judged.py +279 -0
- ambitgraph-0.2.0/ambit_map/kitdemo.py +275 -0
- ambitgraph-0.2.0/ambit_map/library.py +1727 -0
- ambitgraph-0.2.0/ambit_map/load.py +739 -0
- ambitgraph-0.2.0/ambit_map/mapllm.py +594 -0
- ambitgraph-0.2.0/ambit_map/matched.py +705 -0
- ambitgraph-0.2.0/ambit_map/model.py +419 -0
- ambitgraph-0.2.0/ambit_map/objects.py +1000 -0
- ambitgraph-0.2.0/ambit_map/orchestrate.py +1230 -0
- ambitgraph-0.2.0/ambit_map/page.py +112 -0
- ambitgraph-0.2.0/ambit_map/paths.py +1442 -0
- ambitgraph-0.2.0/ambit_map/registry.py +3267 -0
- ambitgraph-0.2.0/ambit_map/serve.py +1931 -0
- ambitgraph-0.2.0/ambit_map/theme.py +515 -0
- ambitgraph-0.2.0/ambit_map/unitpage.py +1012 -0
- ambitgraph-0.2.0/ambitgraph/WALKTHROUGH.md +188 -0
- ambitgraph-0.2.0/ambitgraph/__init__.py +6 -0
- ambitgraph-0.2.0/ambitgraph/__main__.py +5 -0
- ambitgraph-0.2.0/ambitgraph/cli.py +2784 -0
- ambitgraph-0.2.0/ambitgraph/cockpit.py +32 -0
- ambitgraph-0.2.0/ambitgraph/codelib.py +782 -0
- ambitgraph-0.2.0/ambitgraph/config.py +771 -0
- ambitgraph-0.2.0/ambitgraph/console.py +341 -0
- ambitgraph-0.2.0/ambitgraph/demo.py +159 -0
- ambitgraph-0.2.0/ambitgraph/docs_discovery.py +280 -0
- ambitgraph-0.2.0/ambitgraph/doctor.py +178 -0
- ambitgraph-0.2.0/ambitgraph/duplication.py +724 -0
- ambitgraph-0.2.0/ambitgraph/edges.py +2155 -0
- ambitgraph-0.2.0/ambitgraph/emit.py +532 -0
- ambitgraph-0.2.0/ambitgraph/engine_identity.py +87 -0
- ambitgraph-0.2.0/ambitgraph/estimate.py +315 -0
- ambitgraph-0.2.0/ambitgraph/eval.py +126 -0
- ambitgraph-0.2.0/ambitgraph/feeds.py +153 -0
- ambitgraph-0.2.0/ambitgraph/gate.py +505 -0
- ambitgraph-0.2.0/ambitgraph/gate_check.py +552 -0
- ambitgraph-0.2.0/ambitgraph/gate_server.py +78 -0
- ambitgraph-0.2.0/ambitgraph/harvest.py +513 -0
- ambitgraph-0.2.0/ambitgraph/inventory.py +402 -0
- ambitgraph-0.2.0/ambitgraph/jscalls.py +1561 -0
- ambitgraph-0.2.0/ambitgraph/mds.py +145 -0
- ambitgraph-0.2.0/ambitgraph/merge.py +4481 -0
- ambitgraph-0.2.0/ambitgraph/merge_jsp.py +533 -0
- ambitgraph-0.2.0/ambitgraph/parameterize.py +1778 -0
- ambitgraph-0.2.0/ambitgraph/pipeline.py +565 -0
- ambitgraph-0.2.0/ambitgraph/portal.py +973 -0
- ambitgraph-0.2.0/ambitgraph/pycalls.py +218 -0
- ambitgraph-0.2.0/ambitgraph/ratify.py +263 -0
- ambitgraph-0.2.0/ambitgraph/report.py +1331 -0
- ambitgraph-0.2.0/ambitgraph/reports.py +88 -0
- ambitgraph-0.2.0/ambitgraph/sample.py +97 -0
- ambitgraph-0.2.0/ambitgraph/secretary.py +229 -0
- ambitgraph-0.2.0/ambitgraph/shell.py +7703 -0
- ambitgraph-0.2.0/ambitgraph/stages.py +341 -0
- ambitgraph-0.2.0/ambitgraph/symbols.py +7073 -0
- ambitgraph-0.2.0/ambitgraph/tables.py +286 -0
- ambitgraph-0.2.0/ambitgraph/tasks.py +106 -0
- ambitgraph-0.2.0/ambitgraph/urcode.py +78 -0
- ambitgraph-0.2.0/ambitgraph.egg-info/PKG-INFO +83 -0
- ambitgraph-0.2.0/ambitgraph.egg-info/SOURCES.txt +85 -0
- ambitgraph-0.2.0/ambitgraph.egg-info/entry_points.txt +2 -0
- ambitgraph-0.2.0/ambitgraph.egg-info/requires.txt +12 -0
- {ambitgraph-0.0.1 → ambitgraph-0.2.0}/ambitgraph.egg-info/top_level.txt +1 -0
- ambitgraph-0.2.0/pyproject.toml +62 -0
- ambitgraph-0.0.1/PKG-INFO +0 -23
- ambitgraph-0.0.1/README.md +0 -9
- ambitgraph-0.0.1/ambitgraph/__init__.py +0 -7
- ambitgraph-0.0.1/ambitgraph.egg-info/PKG-INFO +0 -23
- ambitgraph-0.0.1/ambitgraph.egg-info/SOURCES.txt +0 -7
- ambitgraph-0.0.1/pyproject.toml +0 -24
- {ambitgraph-0.0.1 → ambitgraph-0.2.0}/ambitgraph.egg-info/dependency_links.txt +0 -0
- {ambitgraph-0.0.1 → ambitgraph-0.2.0}/setup.cfg +0 -0
ambitgraph-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
AmbitGraph — Proprietary Software License
|
|
2
|
+
Copyright (c) 2026 AmbitGraph. All rights reserved.
|
|
3
|
+
|
|
4
|
+
This software and its source code (the "Software") are proprietary and
|
|
5
|
+
confidential. The Software is licensed, not sold.
|
|
6
|
+
|
|
7
|
+
1. GRANT. No license or right to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, or sell copies of the Software is granted except
|
|
9
|
+
under a separate written agreement with the copyright holder. Absent such
|
|
10
|
+
an agreement, no rights are conferred by the availability of this package.
|
|
11
|
+
|
|
12
|
+
2. RESTRICTIONS. You may not reverse engineer, decompile, or disassemble the
|
|
13
|
+
Software, nor remove or alter any proprietary notice, except to the extent
|
|
14
|
+
such restriction is prohibited by applicable law.
|
|
15
|
+
|
|
16
|
+
3. NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
17
|
+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
19
|
+
|
|
20
|
+
4. LIMITATION OF LIABILITY. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
21
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN
|
|
22
|
+
AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
|
|
23
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
For licensing inquiries, contact the copyright holder.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# The source distribution (.tar.gz on PyPI) ships the packages, the README,
|
|
2
|
+
# pyproject and the LICENSE only. Everything below is development-only.
|
|
3
|
+
|
|
4
|
+
prune tests
|
|
5
|
+
prune tools
|
|
6
|
+
prune docs
|
|
7
|
+
prune build
|
|
8
|
+
prune dist
|
|
9
|
+
|
|
10
|
+
# Internal-only packages -- also excluded from the wheel via the packages list.
|
|
11
|
+
prune ambit_contracts
|
|
12
|
+
prune ambit_telegraph
|
|
13
|
+
|
|
14
|
+
# Development evidence directories (screenshots, transcripts, review notes).
|
|
15
|
+
# Both spellings: the dated evidence-* folders and the single evidence/ folder they moved into.
|
|
16
|
+
prune evidence-*
|
|
17
|
+
prune evidence
|
|
18
|
+
|
|
19
|
+
# Internal process documents.
|
|
20
|
+
exclude AGENTS.md
|
|
21
|
+
exclude CHANGELOG.md
|
|
22
|
+
exclude DATA-FLOW.md
|
|
23
|
+
|
|
24
|
+
global-exclude *.pyc
|
|
25
|
+
global-exclude __pycache__/*
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ambitgraph
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Read-only codebase scan: reusable units, families, roles, ownership candidates, duplication.
|
|
5
|
+
Author: AmbitGraph
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Keywords: codebase,scan,audit,duplication,refactoring,reuse
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pyyaml>=6
|
|
19
|
+
Provides-Extra: ui
|
|
20
|
+
Requires-Dist: playwright>=1.40; extra == "ui"
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
23
|
+
Requires-Dist: mcp<2,>=1.2; extra == "dev"
|
|
24
|
+
Requires-Dist: jsonschema>=4; extra == "dev"
|
|
25
|
+
Provides-Extra: gate
|
|
26
|
+
Requires-Dist: mcp<2,>=1.2; extra == "gate"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# AmbitGraph
|
|
30
|
+
|
|
31
|
+
**You don't understand your own product!**
|
|
32
|
+
|
|
33
|
+
**You can't keep up with the amount of code!**
|
|
34
|
+
|
|
35
|
+
One command, and you do. Deterministic. No tokens.
|
|
36
|
+
|
|
37
|
+
AmbitGraph draws your whole repo as a visual interactive map & library and gives them to you. It needs Python on your machine, never in your project. Free, offline, no account, no API key.
|
|
38
|
+
|
|
39
|
+
Python 3.11 or newer.
|
|
40
|
+
|
|
41
|
+
## Install and run
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install ambitgraph
|
|
45
|
+
python -m ambitgraph map <folder>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`ambit map <folder>` does the same when your Python scripts folder is on your PATH.
|
|
49
|
+
|
|
50
|
+
The command gives your terminal back and the map keeps serving. Stop it with `python -m ambitgraph map <folder> --stop`.
|
|
51
|
+
|
|
52
|
+
Your codebase is displayed in your browser as local webpages.
|
|
53
|
+
|
|
54
|
+
It does not make any changes to your codebase.
|
|
55
|
+
|
|
56
|
+
## A GRAPH
|
|
57
|
+
|
|
58
|
+
Modules, Classes, Imports, Calls, DB Tables, anything in your code mapped on its connections.
|
|
59
|
+
|
|
60
|
+
## An Indexed and Searchable LIBRARY
|
|
61
|
+
|
|
62
|
+
Understand what you have, what it does, and what it means.
|
|
63
|
+
|
|
64
|
+
Understand your own codebase in a way you never have. Simple, visual, and if you want more you can ask your agent what the hell it just did. A vibe coder can finally see how their code works. A senior can see at a glance when something has got too complicated.
|
|
65
|
+
|
|
66
|
+
## Your machine
|
|
67
|
+
|
|
68
|
+
The map is served to your own machine, stays on your machine. It and your code belong to you
|
|
69
|
+
|
|
70
|
+
## No AI
|
|
71
|
+
|
|
72
|
+
No AI is required. The map is plain reading of your code.
|
|
73
|
+
|
|
74
|
+
No model runs, no tokens burn, no key exists. If you want more than the map, your own assistant can work on top of it, on your own subscription, with whatever model you already pay for. Optional, always, and the map is finished without it.
|
|
75
|
+
|
|
76
|
+
## In this version
|
|
77
|
+
|
|
78
|
+
Search finds functions and tables by name. The Library groups every file the
|
|
79
|
+
scan read into families. The Graph draws one file and everything one hop from
|
|
80
|
+
it, and every row in the Library opens it.
|
|
81
|
+
|
|
82
|
+
Search does not find routes or file names. Drawn connections do not carry
|
|
83
|
+
their meaning beside them. There are no written explanation pages.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# AmbitGraph
|
|
2
|
+
|
|
3
|
+
**You don't understand your own product!**
|
|
4
|
+
|
|
5
|
+
**You can't keep up with the amount of code!**
|
|
6
|
+
|
|
7
|
+
One command, and you do. Deterministic. No tokens.
|
|
8
|
+
|
|
9
|
+
AmbitGraph draws your whole repo as a visual interactive map & library and gives them to you. It needs Python on your machine, never in your project. Free, offline, no account, no API key.
|
|
10
|
+
|
|
11
|
+
Python 3.11 or newer.
|
|
12
|
+
|
|
13
|
+
## Install and run
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pip install ambitgraph
|
|
17
|
+
python -m ambitgraph map <folder>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`ambit map <folder>` does the same when your Python scripts folder is on your PATH.
|
|
21
|
+
|
|
22
|
+
The command gives your terminal back and the map keeps serving. Stop it with `python -m ambitgraph map <folder> --stop`.
|
|
23
|
+
|
|
24
|
+
Your codebase is displayed in your browser as local webpages.
|
|
25
|
+
|
|
26
|
+
It does not make any changes to your codebase.
|
|
27
|
+
|
|
28
|
+
## A GRAPH
|
|
29
|
+
|
|
30
|
+
Modules, Classes, Imports, Calls, DB Tables, anything in your code mapped on its connections.
|
|
31
|
+
|
|
32
|
+
## An Indexed and Searchable LIBRARY
|
|
33
|
+
|
|
34
|
+
Understand what you have, what it does, and what it means.
|
|
35
|
+
|
|
36
|
+
Understand your own codebase in a way you never have. Simple, visual, and if you want more you can ask your agent what the hell it just did. A vibe coder can finally see how their code works. A senior can see at a glance when something has got too complicated.
|
|
37
|
+
|
|
38
|
+
## Your machine
|
|
39
|
+
|
|
40
|
+
The map is served to your own machine, stays on your machine. It and your code belong to you
|
|
41
|
+
|
|
42
|
+
## No AI
|
|
43
|
+
|
|
44
|
+
No AI is required. The map is plain reading of your code.
|
|
45
|
+
|
|
46
|
+
No model runs, no tokens burn, no key exists. If you want more than the map, your own assistant can work on top of it, on your own subscription, with whatever model you already pay for. Optional, always, and the map is finished without it.
|
|
47
|
+
|
|
48
|
+
## In this version
|
|
49
|
+
|
|
50
|
+
Search finds functions and tables by name. The Library groups every file the
|
|
51
|
+
scan read into families. The Graph draws one file and everything one hop from
|
|
52
|
+
it, and every row in the Library opens it.
|
|
53
|
+
|
|
54
|
+
Search does not find routes or file names. Drawn connections do not carry
|
|
55
|
+
their meaning beside them. There are no written explanation pages.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""The AmbitGraph product package — the shipped two-tab map (Graph + Library).
|
|
2
|
+
|
|
3
|
+
Consumes the engine's artifact files (results/symbols.json, results/edges.json)
|
|
4
|
+
and drives the engine only as a subprocess (ambit scan). Imports NO company
|
|
5
|
+
code — not ambitgraph, not ambit_telegraph, not ambit_contracts — and the wall
|
|
6
|
+
is mechanical: tests/test_import_boundaries.py. The engine reaches this package
|
|
7
|
+
only through a sanctioned lazy seam in cli.py (lands with a later wave)."""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Prose judgment — the optional assistant pass
|
|
2
|
+
|
|
3
|
+
AmbitGraph's map is complete without any AI. Everything the Library and the Graph show is
|
|
4
|
+
measured: symbols and edges come from the engine's deterministic scan of your files, with
|
|
5
|
+
no model and no network.
|
|
6
|
+
|
|
7
|
+
What a scan cannot tell you is what a unit is **for** — the sentence a human would write.
|
|
8
|
+
That is *judgment*, not measurement, and this product never invents it. If you want it, you
|
|
9
|
+
run it yourself, with your own assistant, on your own machine, and the map keeps the two
|
|
10
|
+
vocabularies visibly apart: a judged note always renders inside the collapsed
|
|
11
|
+
"Judged by your assistant" block and always carries the `judged` chip. No judged number is
|
|
12
|
+
ever added to a measured number.
|
|
13
|
+
|
|
14
|
+
## What you need
|
|
15
|
+
|
|
16
|
+
- A completed scan. Its output directory is written as `<run-dir>` below — it is the
|
|
17
|
+
directory `ambit map` printed when it finished, the one holding `results/`,
|
|
18
|
+
`inventory.json` and `pending/`.
|
|
19
|
+
- An assistant that can run a workflow script and write a JSON file. Today's supported
|
|
20
|
+
path is Claude Code; any capable assistant can satisfy the same contract, because your
|
|
21
|
+
artifacts are plain JSON on your disk.
|
|
22
|
+
|
|
23
|
+
## The loop
|
|
24
|
+
|
|
25
|
+
One harvest advances **one stage only**. The stages run in order: `extract`, then
|
|
26
|
+
`cluster`, then `roles` — and `vocab` or `ownership` when your scan configures them.
|
|
27
|
+
The draft map does not exist until the last stage has run.
|
|
28
|
+
|
|
29
|
+
1. Open `<run-dir>/pending/` and pick the next `*.workflow.js` script.
|
|
30
|
+
2. Run that script in an assistant session.
|
|
31
|
+
3. Harvest what it wrote:
|
|
32
|
+
|
|
33
|
+
ambit harvest <stage> --scan "<run-dir>" --journal <transcriptDir>/journal.jsonl
|
|
34
|
+
|
|
35
|
+
`<stage>` is the script's own stage name. `<transcriptDir>/journal.jsonl` is the
|
|
36
|
+
journal your assistant session wrote; it is not a path this map can know ahead of time.
|
|
37
|
+
Quote `<run-dir>` — an unquoted path containing a space splits into two arguments.
|
|
38
|
+
4. Re-run the exact `ambit scan` command that produced this run, pointed at this same
|
|
39
|
+
output directory, from your terminal. This page cannot resume a run; it can only start
|
|
40
|
+
a fresh one.
|
|
41
|
+
5. Repeat 1-4 until `pending/` is empty and the draft map is written.
|
|
42
|
+
|
|
43
|
+
Only then does reloading the Library show judged notes.
|
|
44
|
+
|
|
45
|
+
## What you get, and what you do not
|
|
46
|
+
|
|
47
|
+
You get, per unit the assistant could pin to a file this scan read: what it is, a role
|
|
48
|
+
with a confidence, a parent, and a structural family name. You do not get any change to a
|
|
49
|
+
measured number, and you do not get a claim about a file the scan never read — a judged
|
|
50
|
+
note that cannot be joined to a real unit is listed separately, as unjoined, and counted
|
|
51
|
+
separately.
|
|
52
|
+
|
|
53
|
+
If the draft cannot be read at all, the map says so by name and shows nothing else from
|
|
54
|
+
that plane. Fail-closed is the rule: a plane that did not load never renders as a zero.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# The mapllm ingestion contract — the shapes your assistant's files must carry
|
|
2
|
+
|
|
3
|
+
ambit-map-enrich.md already states the law this document generalises: "Today's supported path
|
|
4
|
+
is Claude Code; ANY capable assistant can satisfy the same contract, because your artifacts are
|
|
5
|
+
plain JSON on your disk." That clause is the whole idea — this document turns it into an actual
|
|
6
|
+
schema, so an assistant that is not Claude Code (or a hand-written script) can write a
|
|
7
|
+
conforming file directly and be read exactly the same way.
|
|
8
|
+
|
|
9
|
+
The law stated plainly, again, because it never changes: everything in this document produces
|
|
10
|
+
*judgment*, not measurement. Nothing here is read, parsed, or counted by the deterministic scan.
|
|
11
|
+
The map keeps the two vocabularies visibly apart — a judged note always renders inside the
|
|
12
|
+
collapsed "Judged by your assistant" block, always carries the `judged` chip, and no judged
|
|
13
|
+
number is ever added to a measured number. A plane built from this contract DECORATES the map;
|
|
14
|
+
it never becomes authoritative over anything the scan itself measured.
|
|
15
|
+
|
|
16
|
+
## Two ways to produce these files
|
|
17
|
+
|
|
18
|
+
1. **The workflow-script loop** (ambit-map-enrich.md's own loop): run the script `pending/
|
|
19
|
+
<stage>.workflow.js` in your assistant session, then reduce its journal with `ambit harvest
|
|
20
|
+
<stage> --scan <run-dir> --journal <journal>`. The harvester folds one or many analysts'
|
|
21
|
+
records into the single `results/<stage>.json` file this document specifies below —
|
|
22
|
+
deduplicating, validating against the enumerations each stage declares, and degrading a
|
|
23
|
+
partial or malformed record rather than discarding the run's other work.
|
|
24
|
+
2. **The direct-results door**: write `results/<stage>.json` yourself, in the exact shape this
|
|
25
|
+
document names for that stage, and skip the workflow-script loop entirely. mapllm's conductor
|
|
26
|
+
accepts a directly-written results file exactly as it accepts one `ambit harvest` produced —
|
|
27
|
+
the file on disk is the whole contract; nothing tracks how it got there.
|
|
28
|
+
|
|
29
|
+
Either way, a stage advances only once its `results/<stage>.json` exists; re-running the same
|
|
30
|
+
scan command against the same output directory picks up from there.
|
|
31
|
+
|
|
32
|
+
## The stage results files
|
|
33
|
+
|
|
34
|
+
Every stage below is optional except `extract`, `cluster`, and `roles` — `vocab` and `ownership`
|
|
35
|
+
run only when the scan config declares a vocabulary or ownership areas; `suggest` runs only when
|
|
36
|
+
the draft has ambiguous units queued for a human's ruling.
|
|
37
|
+
|
|
38
|
+
### The `extract` stage — `results/<stage>.json`
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"units": [
|
|
43
|
+
{
|
|
44
|
+
"anchor": "helpers.py::shout",
|
|
45
|
+
"file": "helpers.py",
|
|
46
|
+
"hook": "shout",
|
|
47
|
+
"granularity": "component",
|
|
48
|
+
"role0": "structure_owner",
|
|
49
|
+
"hint": "a small formatting helper",
|
|
50
|
+
"ev": "imported and called by main.py",
|
|
51
|
+
"similar_to": []
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"uncovered": []
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`granularity` is one of `module` (whole-file scope), `component` (a cohesive unit inside a
|
|
59
|
+
file), or `fragment` (a small piece that composes into larger units) — or the empty string when
|
|
60
|
+
unknown. `role0` is one of `structure_owner` (this code DEFINES the shared structure), `filler`
|
|
61
|
+
(this code USES a structure defined elsewhere), or `unclear`. `anchor` is `file::hook` — the
|
|
62
|
+
stable pair every other stage's file joins on. `uncovered` names manifest paths no unit was
|
|
63
|
+
reported for; it is informational, never a refusal.
|
|
64
|
+
|
|
65
|
+
### The `cluster` stage — `results/<stage>.json`
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"families": [
|
|
70
|
+
{"name": "widgets", "members": ["helpers.py::shout", "main.py::run_report"]}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Every anchor from `extract.json` appears in exactly one family's `members`. A unit that fits no
|
|
76
|
+
real family belongs in one named `singletons`, never omitted. An optional `warnings` array
|
|
77
|
+
(strings) may accompany a run that had to degrade rather than discard part of the reduction.
|
|
78
|
+
|
|
79
|
+
### The `roles` stage — `results/<stage>.json`
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"roles": {
|
|
84
|
+
"helpers.py::shout": {"role": "structure_owner", "parent": "", "confidence": "high",
|
|
85
|
+
"family": "widgets"},
|
|
86
|
+
"main.py::run_report": {"role": "filler", "parent": "helpers.py::shout",
|
|
87
|
+
"confidence": "high", "family": "widgets"}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Keyed by anchor. `role` is `structure_owner` | `filler` | `unclear`. `parent` is the anchor a
|
|
93
|
+
`filler` delegates to, written verbatim as anchor text (empty when there is none). `confidence`
|
|
94
|
+
is `high` | `medium` | `low`.
|
|
95
|
+
|
|
96
|
+
### The `vocab` and `ownership` stages — `results/<stage>.json`
|
|
97
|
+
|
|
98
|
+
Written only when the scan config declares `vocabulary` (for `vocab.json`) or `areas` (for
|
|
99
|
+
`ownership.json`) — each a name-to-definition mapping the config itself states.
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"assignments": {
|
|
104
|
+
"helpers.py::shout": {"family": "widget", "confidence": "high"}
|
|
105
|
+
},
|
|
106
|
+
"missing": [],
|
|
107
|
+
"invalid": []
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`vocab.json` uses the key `family`; `ownership.json` uses `area` in its place. The value is one
|
|
112
|
+
of the config's own declared names, or `"none"` (vocab) / `"unknown"` (ownership) — any other
|
|
113
|
+
value is rejected into `invalid`, never silently accepted. `missing` names anchors this file
|
|
114
|
+
assigns nothing to.
|
|
115
|
+
|
|
116
|
+
### The `suggest` stage — `results/<stage>.json`
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"suggestions": {
|
|
121
|
+
"helpers.py::shout": {"ruling": "keep", "rationale": "a tiny standalone helper, used once."}
|
|
122
|
+
},
|
|
123
|
+
"missing": [],
|
|
124
|
+
"invalid": []
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`ruling` is `keep` or `wrong` only — never `defer`; deciding later is the human reviewer's own
|
|
129
|
+
authority, not a machine suggestion. A missing or empty `rationale` is rejected into `invalid`.
|
|
130
|
+
|
|
131
|
+
## The draft's own version and producer record
|
|
132
|
+
|
|
133
|
+
Once every required stage's results file exists, the scan assembles `map/draft_map.json` — the
|
|
134
|
+
one artifact this whole contract exists to make honest. It carries two fields no prior draft
|
|
135
|
+
ever had:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"v": 1,
|
|
140
|
+
"producer": {
|
|
141
|
+
"recorded_at": "2026-08-30T00:00:00Z",
|
|
142
|
+
"config_hash": "sha1:0123456789ab",
|
|
143
|
+
"engine_fingerprint": "…64 hex chars…"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`v` is the draft's schema version, checked on load — see "Fail-closed" below for exactly what
|
|
149
|
+
counts as a match. `producer.tool`, when present, names in plain free text what produced the
|
|
150
|
+
stage results this draft was assembled from. Nothing in this contract names a producer for you:
|
|
151
|
+
a `results/<stage>.json` header carries no `producer.tool` unless something put one there — the
|
|
152
|
+
workflow-script loop does not name itself automatically. Today that something is `ambit harvest`'s
|
|
153
|
+
own `--producer` flag:
|
|
154
|
+
|
|
155
|
+
ambit harvest <stage> --scan "<run-dir>" --journal <journal> --producer "your assistant's name and version"
|
|
156
|
+
|
|
157
|
+
`--producer` is optional and free text — name any assistant or tool, or omit it entirely. A
|
|
158
|
+
direct-results-door author writing `results/<stage>.json` by hand carries the identical header
|
|
159
|
+
shape without touching this flag at all: `{"producer": {"tool": "..."}}` sitting on the results
|
|
160
|
+
file is all either path needs to produce. When the `extract` stage's own `results/<stage>.json`
|
|
161
|
+
header never named a producer (the flag was omitted, or a legacy file predates this contract),
|
|
162
|
+
`producer.tool` is **omitted entirely** from the draft, exactly as the example above shows — never
|
|
163
|
+
written as a placeholder string, never guessed. A key that is present is always a real name
|
|
164
|
+
someone gave; a key that is absent means no one gave one. Whitespace-only names (`" "`) count as
|
|
165
|
+
no name at all, on both the writing and the reading side, and are stripped before anything is
|
|
166
|
+
stored — `ambit harvest --producer " "` prints one plain line saying so rather than silently
|
|
167
|
+
writing a blank name. `producer.config_hash` is the scan's own configuration hash (the same
|
|
168
|
+
value `results/symbols.json` and `results/edges.json` already carry); it binds this draft to the
|
|
169
|
+
scan it was produced against, so a draft copied onto a differently-configured scan is caught
|
|
170
|
+
rather than silently misjoined. `producer.engine_fingerprint` mirrors the draft's own top-level
|
|
171
|
+
fingerprint field, absence-disclosed the identical way (an `engine_fingerprint_absent` flag
|
|
172
|
+
stands in when no fingerprint could be computed).
|
|
173
|
+
|
|
174
|
+
## Fail-closed, on every branch
|
|
175
|
+
|
|
176
|
+
The reader that loads this draft never raises on anything it finds on disk, and never renders a
|
|
177
|
+
guess:
|
|
178
|
+
|
|
179
|
+
- No draft file at all — the normal, unremarkable state before judgment has run. Nothing is
|
|
180
|
+
shown, nothing is claimed.
|
|
181
|
+
- A draft with no `"v"` key — every draft written before this contract existed. It loads exactly
|
|
182
|
+
as it always has; the absence of a version is never treated as a mismatch.
|
|
183
|
+
- A draft whose `"v"` key is present but is anything other than the plain integer `1` — refused
|
|
184
|
+
by name, nothing from the draft is read further. This includes `"v": null` (declared but
|
|
185
|
+
unusable is not the same fact as absent), `"v": true` (a boolean is never accepted as an
|
|
186
|
+
integer, even where it happens to equal `1` by value), and `"v": 1.0` (a float is never accepted
|
|
187
|
+
in place of the declared integer schema version) — every one of these is a real, present value
|
|
188
|
+
that simply is not this reader's version `1`.
|
|
189
|
+
- A draft whose `producer.config_hash` disagrees with the scan actually being served, when both
|
|
190
|
+
values are present — refused by name: the facts were produced against a different scan.
|
|
191
|
+
- Anything else malformed (unparseable JSON, a top level that is not an object, a `units` field
|
|
192
|
+
that is not a list) — refused by name, the same as always.
|
|
193
|
+
|
|
194
|
+
A refused draft never renders as an empty or zero judged plane — a plane that did not load never
|
|
195
|
+
counts as a plane that loaded empty. Every other field this document does not name is tolerated
|
|
196
|
+
additively: an unrecognised key is ignored, never a reason to refuse the whole file.
|
|
197
|
+
|
|
198
|
+
## What the acceptance of a real repository taught
|
|
199
|
+
|
|
200
|
+
The rules above are the shapes; running a real assistant against a real, unmodified public
|
|
201
|
+
repository end to end found six ways a conforming file — or the run that produces it — can still
|
|
202
|
+
mislead a reader even while it parses cleanly. Each is a rule for the assistant writing these
|
|
203
|
+
files directly, not a change to the shapes themselves.
|
|
204
|
+
|
|
205
|
+
- **`parent` is a verbatim anchor, or the empty string — never prose, and never an external
|
|
206
|
+
framework's name.** It is the exact `file::hook` text some other unit's own `anchor` field
|
|
207
|
+
carries, copied byte for byte, so a reader can follow it with a lookup, not a guess. It is
|
|
208
|
+
never a sentence describing the relationship ("extends the base template"), and never the name
|
|
209
|
+
of a class, component or framework this scan's own unit set does not contain: when a filler's
|
|
210
|
+
true parent lives OUTSIDE what this run analysed — a third-party base class, a library
|
|
211
|
+
component — the correct value is the empty string, the SAME value written when there is no
|
|
212
|
+
parent at all. The external case IS the empty-string case; it is never spelled out in its own
|
|
213
|
+
words instead.
|
|
214
|
+
- **Evidence carries conclusions, never scratch reasoning or self-corrections.** The `ev` field,
|
|
215
|
+
and every free-text evidence field this contract accepts, states what you found — never the
|
|
216
|
+
thinking that got you there, and never a correction written into the record after the fact
|
|
217
|
+
("actually, on reflection, it might instead..."). A reader who has to debug a train of thought
|
|
218
|
+
to recover the actual claim was handed reasoning, not evidence.
|
|
219
|
+
- **Counts never go inside an anchor.** An anchor names WHICH unit a record is about — it is an
|
|
220
|
+
identity, never a place to carry a measurement. A number belongs in a field built to hold one,
|
|
221
|
+
or is left out; folding it into the anchor text itself (`"helpers.py::shout (12 calls)"`) turns
|
|
222
|
+
an identity into a claim, and the two must never be the same string.
|
|
223
|
+
- **Never write "not in this file set," or any other claim about the file list's horizon.** A
|
|
224
|
+
large repository is read in chunks, and each analyst sees only the subset it was handed — a
|
|
225
|
+
sentence asserting what is or is not present ANYWHERE in the repository is a claim about ground
|
|
226
|
+
this one pass never covered. Worse, the harvester's own reduction keeps every evidence sentence
|
|
227
|
+
it receives VERBATIM: a horizon claim that was true of one chunk's own narrow view is carried
|
|
228
|
+
unchanged into the assembled draft, where it now reads as a claim about the WHOLE repository —
|
|
229
|
+
and is simply false there. State only what this pass itself observed inside the files it was
|
|
230
|
+
actually given.
|
|
231
|
+
- **The number of stages a run produces is repository-dependent — never a fixed count to
|
|
232
|
+
expect.** `vocab` and `ownership` run only when the scan's own config declares them (see "The
|
|
233
|
+
stage results files" above); `suggest` runs only when at least one unit could not be accepted
|
|
234
|
+
in bulk (the next rule names every reason that can queue one). Watch until the conductor's own
|
|
235
|
+
exit; never assume in advance how many stages a given run will produce.
|
|
236
|
+
- **The `suggest` queue is every unit the draft could not accept in bulk, and each entry's own
|
|
237
|
+
`why` field names every reason that queued it, comma-joined, in the engine's own words.** Four
|
|
238
|
+
independent checks fill the queue: `role == "unclear"` writes `"role unclear"`; otherwise a low
|
|
239
|
+
`confidence` writes `"low role confidence"`; no structural family assigned by the `cluster`
|
|
240
|
+
stage writes `"no structural family"`; low vocabulary confidence from the `vocab` stage writes
|
|
241
|
+
`"low vocabulary confidence"` — a unit can carry more than one of these at once, and none of
|
|
242
|
+
them is about duplicate code. Rule on the entry's OWN stated `why`, never a guess at what queued
|
|
243
|
+
it: for `"role unclear"`/`"low role confidence"` the question is "was my own prior uncertainty
|
|
244
|
+
justified" — but the other two reasons ask a genuinely different question, and the `why` string
|
|
245
|
+
is what tells you which one you are actually answering. Read it before ruling; do not assume it
|
|
246
|
+
is always the hedging question.
|