@mafianacho/nacholens 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +13 -0
- package/README.de.md +46 -0
- package/README.fr.md +46 -0
- package/README.ja.md +46 -0
- package/README.ko.md +46 -0
- package/README.md +45 -2
- package/README.zh.md +46 -0
- package/demo/shop/v1/README.md +7 -0
- package/demo/shop/v1/api.js +78 -0
- package/demo/shop/v1/db.js +38 -0
- package/demo/shop/v1/gateway.js +50 -0
- package/demo/shop/v1/mock-external.js +17 -0
- package/demo/shop/v1/public/app.js +32 -0
- package/demo/shop/v1/public/format.js +2 -0
- package/demo/shop/v1/public/index.html +13 -0
- package/demo/shop/v1/public/styles.css +4 -0
- package/demo/shop/v2/README.md +7 -0
- package/demo/shop/v2/api.js +88 -0
- package/demo/shop/v2/db.js +38 -0
- package/demo/shop/v2/gateway.js +50 -0
- package/demo/shop/v2/mock-external.js +17 -0
- package/demo/shop/v2/public/app.js +33 -0
- package/demo/shop/v2/public/format.js +3 -0
- package/demo/shop/v2/public/index.html +13 -0
- package/demo/shop/v2/public/styles.css +5 -0
- package/docs/guide.ko.md +97 -0
- package/docs/guide.md +142 -0
- package/docs/model-schema.md +137 -0
- package/docs/review.md +78 -0
- package/examples/demo-shop.artifact.html +1535 -0
- package/examples/demo-shop.artifact.json +7 -0
- package/examples/demo-shop.html +1535 -0
- package/examples/external/README.md +12 -0
- package/examples/external/full-stack-fastapi-template/LICENSE +21 -0
- package/examples/external/full-stack-fastapi-template/changes.json +56 -0
- package/examples/external/full-stack-fastapi-template/full-stack-fastapi-template-backend.html +1535 -0
- package/models/demo-shop/changes.auto.json +121 -0
- package/models/demo-shop/changes.example.json +16 -0
- package/models/demo-shop/intent.example.json +75 -0
- package/models/demo-shop/model.json +3151 -0
- package/package.json +60 -4
- package/skills/nacholens/SKILL.md +235 -0
- package/src/cli.js +377 -0
- package/src/demo.js +42 -0
- package/src/diff.js +254 -0
- package/src/gitio.js +67 -0
- package/src/install.js +94 -0
- package/src/model.js +434 -0
- package/src/page.js +87 -0
- package/src/player/fonts/FONTLOG.md +30 -0
- package/src/player/fonts/OFL-Anton.txt +93 -0
- package/src/player/fonts/OFL-IBMPlex.txt +94 -0
- package/src/player/fonts/anton-400.woff2 +0 -0
- package/src/player/fonts/nacho-mono-500.woff2 +0 -0
- package/src/player/fonts/nacho-mono-600.woff2 +0 -0
- package/src/player/fonts/nacho-sans-400.woff2 +0 -0
- package/src/player/fonts/nacho-sans-500.woff2 +0 -0
- package/src/player/fonts/nacho-sans-600.woff2 +0 -0
- package/src/player/fonts/nacho-sans-700.woff2 +0 -0
- package/src/player/player.css +318 -0
- package/src/player/player.js +872 -0
- package/src/player/review.css +49 -0
- package/src/player/review.js +230 -0
- package/src/player/template.html +58 -0
- package/src/review.js +321 -0
- package/src/scan.js +255 -0
- package/src/template.js +36 -0
- package/src/verify.js +90 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Model and change-overlay formats
|
|
2
|
+
|
|
3
|
+
`src/model.js` (`validateModel`, `validateChanges`) is the source of truth; this page is its description.
|
|
4
|
+
|
|
5
|
+
## `model.json`
|
|
6
|
+
|
|
7
|
+
```jsonc
|
|
8
|
+
{
|
|
9
|
+
"schemaVersion": 1,
|
|
10
|
+
"product": { "id": "my-api", "name": "My API", "description": "One or two sentences." },
|
|
11
|
+
"source": { "repo": "owner/repo", "commit": "SHA of the commit whose code you read" },
|
|
12
|
+
"components": [{
|
|
13
|
+
"id": "api", // lowercase-with-hyphens, unique
|
|
14
|
+
"kind": "client | gateway | service | datastore | external",
|
|
15
|
+
"label": "Order API\nNode http", // text after \n is the sub-title
|
|
16
|
+
"detail": "What it is and does, one or two sentences.",
|
|
17
|
+
"lane": 2, "row": 1, // optional layout hints (0-9). Default lanes: client 0, gateway 1, service 2, datastore/external 3
|
|
18
|
+
"evidence": [{ "file": "api.js", "lines": [36, 85], "contains": "async function route" }]
|
|
19
|
+
}],
|
|
20
|
+
"links": [{ "a": "browser", "b": "gateway", "label": "HTTP, same origin /api/*", "evidence": [ ... ] }], // one per pair, undirected
|
|
21
|
+
"useCases": [{
|
|
22
|
+
"id": "list-orders", "title": "List my orders", "summary": "...", "trigger": "GET /api/orders",
|
|
23
|
+
"steps": [{
|
|
24
|
+
"id": "query",
|
|
25
|
+
"kind": "request | response | call | internal",
|
|
26
|
+
"from": "api", "via": ["gateway"], "to": "db", // via: components passed through (e.g. the gateway); every hop needs a link
|
|
27
|
+
"label": "Read the user's orders",
|
|
28
|
+
"narration": "One or two plain sentences for a non-developer.",
|
|
29
|
+
"request": "one-line summary", // required for request and call
|
|
30
|
+
"response": "one-line summary", // required for response and call
|
|
31
|
+
"evidence": [ ... ]
|
|
32
|
+
}]
|
|
33
|
+
}]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Step kinds: `request` one way in · `response` one way out · `call` a round trip (database, third party) · `internal` a decision inside one component (`from` = `to`, no `via`).
|
|
38
|
+
|
|
39
|
+
`evidence` entries are `{file, lines:[start,end], contains?}`: a repository-relative path (no `..`, not absolute), an inclusive line range, and an optional string that must appear inside that range. `verify` fails when the file, the range or the string does not match the source at `source.commit`. Use `contains`: without it a range that drifted onto other code goes unnoticed (`verify` warns).
|
|
40
|
+
|
|
41
|
+
Validation rejects: items without evidence, links or steps over undeclared links, duplicate ids, unknown kinds or keys, absolute or `..` paths, `call` steps without a response summary, leftover `TODO`s from the template.
|
|
42
|
+
|
|
43
|
+
## Optional drill-down detail
|
|
44
|
+
|
|
45
|
+
All fields below are **optional**. A model without them is valid and renders exactly as before; a node that has any of them gets a "· 상세" caption and opens a detail view on click (Esc, the back button or browser Back closes it). Every item carries its own `evidence` and `verify` checks it like any other item.
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
// on a client / gateway / service component: its inside and the wiring between the parts
|
|
49
|
+
"parts": [{
|
|
50
|
+
"id": "orders-svc", "label": "OrderService",
|
|
51
|
+
"role": "route | handler | middleware | service | port | adapter | job | module",
|
|
52
|
+
"detail": "What this part does, one or two sentences.",
|
|
53
|
+
"connects": "db", // optional: the component this part talks to (adapters)
|
|
54
|
+
"evidence": [ ... ]
|
|
55
|
+
}],
|
|
56
|
+
"wires": [{ "from": "orders-route", "to": "orders-svc", "label": "listOrders(user)", "evidence": [ ... ] }], // directed, both ends are parts of this component
|
|
57
|
+
|
|
58
|
+
// on a datastore component: its tables and columns
|
|
59
|
+
"tables": [{
|
|
60
|
+
"name": "order_events", "detail": "...",
|
|
61
|
+
"columns": [{ "name": "kind", "type": "text", "note": "created|paid|shipped" }], // type and note optional
|
|
62
|
+
"evidence": [ ... ] // the migration / schema lines
|
|
63
|
+
}],
|
|
64
|
+
|
|
65
|
+
// on an external component: the endpoints this code calls
|
|
66
|
+
"endpoints": [{
|
|
67
|
+
"id": "track", "method": "GET | POST | PUT | PATCH | DELETE | HEAD | WS | RPC | SSE", "path": "/v1/track",
|
|
68
|
+
"auth": "Bearer API key (server setting)", // optional, the NAME of the scheme only
|
|
69
|
+
"detail": "...",
|
|
70
|
+
"request": [{ "name": "code", "note": "tracking code" }], // optional, key fields only
|
|
71
|
+
"response": [{ "name": "status" }], // optional
|
|
72
|
+
"evidence": [ ... ]
|
|
73
|
+
}]
|
|
74
|
+
|
|
75
|
+
// on a step: how the step uses that detail
|
|
76
|
+
"trace": ["api/orders-route", "api/orders-svc"], // "component/part" in the order the request passes them
|
|
77
|
+
"data": [{ "store": "db", "table": "orders", "op": "read | insert | update | upsert | delete",
|
|
78
|
+
"columns": ["user_id", "status"], "evidence": [ ... ] }], // the query lines; columns must be declared on the table
|
|
79
|
+
"endpoints": ["tracker/track"] // "component/endpoint" this step calls
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Rules the validator enforces:
|
|
83
|
+
|
|
84
|
+
- `parts`/`wires` only on `client | gateway | service`; `tables` only on `datastore`; `endpoints` only on `external`. `wires` need `parts`.
|
|
85
|
+
- `trace`: every entry is a declared part of a component the step passes (`from`, `via`, `to`). Two consecutive parts of the same component need a declared wire between them (either direction); parts of different components need a declared link.
|
|
86
|
+
- `data`: `store` is a datastore the step passes; `table` and every `columns` entry are declared on it.
|
|
87
|
+
- `endpoints` on a step: declared on a component the step passes.
|
|
88
|
+
- **No secrets anywhere in a model.** Strings that look like keys or tokens (`sk-…`, `ghp_…`, `AKIA…`, JWTs, PEM blocks, `Bearer <long value>`) are rejected. Write the auth scheme's name, never a value.
|
|
89
|
+
|
|
90
|
+
### Optional data shapes and transforms (on a step)
|
|
91
|
+
|
|
92
|
+
`payload` is what travels on a moving step, shown when the user clicks the dot or the data label. `transforms` is how the data changes at a part on the step's `trace`, shown in the right panel and on the current wire inside the detail view. Both are optional; a step without them shows its `request`/`response` summary as before.
|
|
93
|
+
|
|
94
|
+
```jsonc
|
|
95
|
+
"payload": [{
|
|
96
|
+
"leg": "request | response", // call: both; request step: request; response step: response; internal: not allowed
|
|
97
|
+
"format": "json | http | sql | row | text | vector | sse | ws",
|
|
98
|
+
"title": "read my orders", // optional, <= 80 chars
|
|
99
|
+
"body": "SELECT id, item FROM orders WHERE email = ?", // optional, <= 2000 chars / 40 lines
|
|
100
|
+
"bind": [{ "name": "?1 email", "value": "<email>" }], // optional: SQL parameters
|
|
101
|
+
"columns": [{ "name": "id", "value": "INTEGER" }], // optional: row shape (table, column, value type)
|
|
102
|
+
"evidence": [ ... ] // the statement, the struct, or the code that builds the body
|
|
103
|
+
}],
|
|
104
|
+
"transforms": [{
|
|
105
|
+
"at": "api/orders-post", // a "component/part" on this step's trace
|
|
106
|
+
"in": { "type": "validated order", "fields": ["item", "qty"] },
|
|
107
|
+
"out": { "type": "insertOrder arguments", "fields": ["email", "item (cut to 80)", "qty", "tracking"] },
|
|
108
|
+
"map": [{ "from": "item", "to": "item.slice(0, 80)", "how": "optional note" }],
|
|
109
|
+
"evidence": [ ... ] // the lines that do the conversion
|
|
110
|
+
}]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- At least one of `body`, `bind`, `columns`. Values are **shapes and placeholders** (`<uuid>`, `<text>`, `<int>`, `<email>`, `INTEGER`), never real data: the validator refuses e-mail addresses (other than `example.com/org/net`), phone numbers, resident-number shapes and runs of 10+ digits, besides the secret patterns above.
|
|
114
|
+
- Write only fields you saw in code (the struct, the `json!` body, the SQL text). Elide the rest with `…`; do not guess.
|
|
115
|
+
- `transforms` need `trace`; `at` must be on it. List them in the order the data flows.
|
|
116
|
+
- Their evidence belongs to the step (`verify` checks it; a changed line marks the step).
|
|
117
|
+
|
|
118
|
+
`diff` maps changed lines to detail items too: `changes.details` lists parts, wires, tables and endpoints whose own evidence changed (`{component, kind: part|wire|table|endpoint, id, change, note}`; a wire's id is `from>to`). They mark items inside the detail view and never badge the component. Table access cited in `step.data` belongs to the step, so a changed query marks the step.
|
|
119
|
+
|
|
120
|
+
## `changes.json` (change overlay)
|
|
121
|
+
|
|
122
|
+
`nacholens diff` / `build --repo` produce it; a hand-written file with only the first four fields is accepted too.
|
|
123
|
+
|
|
124
|
+
```jsonc
|
|
125
|
+
{
|
|
126
|
+
"title": "...", "summary": "...", "ref": "repo base..head",
|
|
127
|
+
"components": [{ "id": "api", "change": "added | modified | removed", "note": "..." }],
|
|
128
|
+
"steps": [{ "useCase": "list-orders", "step": "shipping", "change": "added", "note": "..." }],
|
|
129
|
+
// optional, written by `diff`
|
|
130
|
+
"unmapped": [{ "file": "...", "category": "code | style", "status": "added | modified | deleted", "lines": "...", "hint": "..." }],
|
|
131
|
+
"details": [{ "component": "db", "kind": "table", "id": "order_events", "change": "added", "note": "..." }],
|
|
132
|
+
"ignored": [{ "file": "...", "reason": "tests | docs | lock file | static asset" }],
|
|
133
|
+
"generated": { "by": "nacholens diff", "base": "sha", "head": "sha", "modelCommit": "sha", "lineTranslation": true }
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
A complete worked example lives in `models/demo-shop/`: `model.json`, the hand-written `changes.example.json` and the generated `changes.auto.json`.
|
package/docs/review.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Review: before/after, intent check, regression map
|
|
2
|
+
|
|
3
|
+
These answer the questions a reviewer has *after* "what changed?": "what was it before?", "is this what we asked for?" and
|
|
4
|
+
"what else can this break?". Nothing is guessed: whatever the model cannot show is reported as `unknown` / "not found", never as a pass.
|
|
5
|
+
|
|
6
|
+
On the page they live in the **review dock** under the stage, as three tracks:
|
|
7
|
+
|
|
8
|
+
| Tab | For | Still to check (the count on the tab) |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| 기획 의도 | planner | expectations not met or not provable, and flows with changes no expectation points at |
|
|
11
|
+
| 회귀 테스트 | QA | use cases to re-test |
|
|
12
|
+
| 모델 밖 변경 | developer | folders with changed code that no step cites (config/style and ignored files are listed apart) |
|
|
13
|
+
|
|
14
|
+
Each tab carries its status (a coloured dot and how many items are left), so the tab bar is the page's to-do summary.
|
|
15
|
+
Ticks are a per-viewer convenience kept in browser storage when it is available. Rows never repeat a step: they link to it and the
|
|
16
|
+
stage moves there. `#dock=intent|impact|unmapped` in the URL opens a tab; clicking the open tab folds the dock; its top edge resizes it.
|
|
17
|
+
The dock is shown whenever there is a change overlay or review data.
|
|
18
|
+
|
|
19
|
+
## 1. Before / after (`compare`)
|
|
20
|
+
|
|
21
|
+
Rows are aligned by step id for every use case: **added** (nothing before), **removed** (nothing after), **modified**, **moved**, **same**,
|
|
22
|
+
with the component path before and after (a component reached only by an added step is marked new). They are in `review.json`.
|
|
23
|
+
On the page, before/after belongs to the step: with two models the current step's explanation lists the fields it had before
|
|
24
|
+
(name, kind, route, request, response) or says it only moved, and the flow overview lists the steps that are gone. In overlay mode the
|
|
25
|
+
step shows the reason from the overlay (its changed evidence) and nothing is claimed about the old text.
|
|
26
|
+
|
|
27
|
+
| Mode | How | What it can say |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| overlay (default) | model + change overlay | added / removed / "code changed". The old text of a modified step is not in the model, and the row says so. |
|
|
30
|
+
| two models | `build --before-model <model written against the base commit>` | the flow itself: label, kind, from/to/via, request/response differences per field, removed steps, moved steps, new/removed use cases |
|
|
31
|
+
|
|
32
|
+
## 2. Intent check (`intent`)
|
|
33
|
+
|
|
34
|
+
The planner (or the agent from the planner's words) writes `intent.json`; the CLI checks every expectation against the model.
|
|
35
|
+
|
|
36
|
+
```jsonc
|
|
37
|
+
{
|
|
38
|
+
"schemaVersion": 1,
|
|
39
|
+
"title": "Show shipping status on the order list",
|
|
40
|
+
"source": "link or name of the spec", // optional
|
|
41
|
+
"expectations": [
|
|
42
|
+
{ "id": "ship-lookup", "text": "The order list asks the carrier for each status", "useCase": "list-orders",
|
|
43
|
+
"check": { "type": "step", "match": { "kind": "call", "to": "shipping" } } },
|
|
44
|
+
{ "id": "login-first", "text": "Orders are read only after the session check", "useCase": "list-orders",
|
|
45
|
+
"check": { "type": "order", "first": { "table": "sessions", "op": "read" }, "then": { "table": "orders", "op": "read" } } },
|
|
46
|
+
{ "id": "no-export", "text": "There is no CSV export", "useCase": "list-orders",
|
|
47
|
+
"check": { "type": "absent", "match": { "text": "csv|export" } } },
|
|
48
|
+
{ "id": "wording", "text": "Shipping wording is friendly" } // no check -> stays "unknown"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `check.type`: `step` (some step matches), `absent` (no step matches), `order` (a `first` step comes before a `then` step).
|
|
54
|
+
- A matcher uses any of `kind`, `from`, `to`, `with` (component the step passes), `table`, `op`, `text` (alternatives separated by `|`, case-insensitive substring over label, narration, request, response and column names; no regular expressions).
|
|
55
|
+
- Verdict per expectation: **met** (with the proving steps and their `file:lines`), **unmet** (the use case exists but the model shows no such step, or the opposite), **unknown** (no check).
|
|
56
|
+
An expectation whose proving steps were changed by this work is flagged *touched*, so the planner re-reads it.
|
|
57
|
+
- **Uncovered changes**: changed steps that no expectation points at. They are candidates for scope drift; a human decides. An expectation with a `useCase` but no `check` claims that use case's changed steps.
|
|
58
|
+
- Matching reads the *model*, which cites real code. If the model is wrong the verdict is wrong, so keep `verify` green.
|
|
59
|
+
|
|
60
|
+
`nacholens intent --model M --intent I [--repo R --base B | --changes C] [--out report.json] [--strict-intent]` prints the verdicts; with `--strict-intent` an unmet expectation exits with code **4**.
|
|
61
|
+
|
|
62
|
+
## 3. Regression map (`impact`)
|
|
63
|
+
|
|
64
|
+
For the change overlay, which use cases should be checked again, why, and whether a test mentions them.
|
|
65
|
+
|
|
66
|
+
- **direct**: the use case holds a changed step.
|
|
67
|
+
- **indirect**: a step of another use case shares something the change touched: a **table** (also a changed schema), an external **endpoint**, a **part**, a **wire** (a drill-down item whose own evidence changed), or a **component** whose own evidence changed.
|
|
68
|
+
A component that is merely an end point of a changed step is *not* a link (every use case passes the API, that would flag everything). A changed step's own parts and endpoints are not repeated as reasons.
|
|
69
|
+
- **tests** (needs `--repo`): test files at `--head` that mention the use case's route (`/api/orders` matches `"/api/orders"` and `/api/orders/7`, not `/api/orders-archive`).
|
|
70
|
+
States: `covered-changed` (the test file changed in this diff), `covered`, `none-found` (**not** "untested": a test that builds the URL differently is missed), `unknown` (no repository was read).
|
|
71
|
+
- Sorted so that use cases without a test come first.
|
|
72
|
+
|
|
73
|
+
`nacholens impact --repo R --base B [--head H] [--model M] [--out impact.json]` prints it.
|
|
74
|
+
|
|
75
|
+
## Output
|
|
76
|
+
|
|
77
|
+
`build` embeds the review data in the page whenever there is an overlay, `--before-model` or `--intent`; with `--repo` it also writes `review.json`
|
|
78
|
+
and adds a `review` summary (`compare`, `intent`, `impact` counts) to `report.json`. Without an overlay or review data the dock stays hidden.
|