nodeengine 0.1.0__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.
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nodeengine
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Install the NodeEngine staleness-guard scaffold into the current repo
|
|
5
|
+
Project-URL: Homepage, https://github.com/Alexyodude/NodeEngine
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# NodeEngine
|
|
12
|
+
|
|
13
|
+
A Claude Code plugin. Makes staleness **fail a check** instead of relying on
|
|
14
|
+
someone remembering.
|
|
15
|
+
|
|
16
|
+
*The name is literal: what it builds is a graph of **nodes** — clauses, blocks,
|
|
17
|
+
generated files — each holding the hash of the source it was derived from. The
|
|
18
|
+
engine walks that graph and reports every edge whose source has moved.*
|
|
19
|
+
|
|
20
|
+
A fact stated twice is a fact that will disagree with itself — not might, will,
|
|
21
|
+
usually within hours, because the two copies are edited by different people at
|
|
22
|
+
different times for different reasons.
|
|
23
|
+
|
|
24
|
+
The usual response is to be careful. That fails, because carefulness does not
|
|
25
|
+
survive a deadline. What works is to make the second copy *derived*, and then
|
|
26
|
+
make derivation *checkable*.
|
|
27
|
+
|
|
28
|
+
## The idea, in one line
|
|
29
|
+
|
|
30
|
+
> **Store the hash of the source beside the derived thing. Compare on every run.**
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
"6.2B": { "hash": "3e40ff04…", "summary": "The reservations preserve rights as partners only…" }
|
|
34
|
+
^^^^^^^^^^^^^^^^^ the source text when a human last confirmed
|
|
35
|
+
this summary was true of it
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Change the source and its hash changes; the summary no longer matches its
|
|
39
|
+
anchor; the check says `summary-stale: 6.2B`. Nobody had to remember. The
|
|
40
|
+
staleness is not caught by cleverness — it is **structurally unable to hide**.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
One line, in any repo:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
curl -sL https://raw.githubusercontent.com/Alexyodude/NodeEngine/main/install.py | python -
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That copies `stale.py`, `checkall.py` and `viewer.html` into `./tools/` and
|
|
51
|
+
prints what to do next. No clone, no plugin, no dependencies. Add
|
|
52
|
+
`--into scripts` or `--dry-run` after `python -` if you want them.
|
|
53
|
+
|
|
54
|
+
If you have [uv](https://docs.astral.sh/uv/), the same installer runs as a
|
|
55
|
+
packaged tool — same files land, same refusal to overwrite:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
uvx --from git+https://github.com/Alexyodude/NodeEngine nodeengine
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
*It runs code fetched over the network, which is worth being deliberate about.
|
|
62
|
+
To read it first:*
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
curl -sL https://raw.githubusercontent.com/Alexyodude/NodeEngine/main/install.py -o install.py
|
|
66
|
+
less install.py && python install.py
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**As a Claude Code plugin**, if you also want the skill and the guided setup:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
/plugin marketplace add Alexyodude/NodeEngine
|
|
73
|
+
/plugin install node-engine@node-engine
|
|
74
|
+
/node-engine:install
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The plugin adds the method itself — an agent that knows the three shapes, the
|
|
78
|
+
defect taxonomy, and the rule that a new check is guilty until it has failed on
|
|
79
|
+
demand. The one-liner just gives you the files.
|
|
80
|
+
|
|
81
|
+
## What it covers
|
|
82
|
+
|
|
83
|
+
Three shapes, because different relationships need different guarantees:
|
|
84
|
+
|
|
85
|
+
| Shape | For | Guarantee |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| **Anchored** | a human judgement about a source — summaries, descriptions, a sentence citing a spec | re-anchoring is an explicit act; you cannot clear a stale flag by accident |
|
|
88
|
+
| **Content-addressed** | anything that must track an original exactly — translations, mirrors | the hash *is* the key, so a changed source has no entry rather than the wrong one |
|
|
89
|
+
| **Ordered** | files a human generates by running a command | catches the case the others cannot: nobody ran it |
|
|
90
|
+
|
|
91
|
+
Plus the cheapest win in most repos: **a number restated in prose should be
|
|
92
|
+
computed at check time and compared** — version, file count, step count, size.
|
|
93
|
+
No hash needed; the artefact is the source of truth.
|
|
94
|
+
|
|
95
|
+
## Two files carry the human side
|
|
96
|
+
|
|
97
|
+
**`tools/DUPLICATION.md`** is the survey: what this repo states twice, where
|
|
98
|
+
both copies live, which shape guards each, and what is knowingly left
|
|
99
|
+
unguarded. The wiring-up writes it first, because a survey that lives only in
|
|
100
|
+
a conversation is gone when the session is.
|
|
101
|
+
|
|
102
|
+
**`tools/viewer.html`** is the search UI. Open it in a browser — no server, no
|
|
103
|
+
dependencies — drop in the store JSONs your checks keep, and search every id,
|
|
104
|
+
hash and summary. Paste a piece of source text and it computes the same key
|
|
105
|
+
`stale.py` computes, then tells you whether anything still anchors that exact
|
|
106
|
+
text. Read-only on purpose: re-anchoring stays a deliberate act in code,
|
|
107
|
+
because the one action that must remain expensive is clearing a stale flag
|
|
108
|
+
without looking.
|
|
109
|
+
|
|
110
|
+
## It carries the discipline, not just the machinery
|
|
111
|
+
|
|
112
|
+
When a defect is found:
|
|
113
|
+
|
|
114
|
+
1. Fix the instance.
|
|
115
|
+
2. **Name the class.**
|
|
116
|
+
3. **Measure what else has that shape** — grep for the *claim*, not the sentence.
|
|
117
|
+
4. Fix the class.
|
|
118
|
+
5. **Add a check, and make it fail on purpose before believing it.**
|
|
119
|
+
|
|
120
|
+
Step 5 is where this is won or lost. **A new check that passes has told you
|
|
121
|
+
nothing.** It might be correct; it might be structurally incapable of failing,
|
|
122
|
+
and you cannot tell those apart by reading it.
|
|
123
|
+
|
|
124
|
+
## Why that last point is not paranoia
|
|
125
|
+
|
|
126
|
+
`reference/traps.md` documents ten checks that shipped, each reporting a clean
|
|
127
|
+
pass, each unable to fail. In **eight of ten the failure mode was a clean
|
|
128
|
+
result** — not a crash, not a wrong answer, a pass. Examples:
|
|
129
|
+
|
|
130
|
+
- `glob_newer_than("*.pdf", ".pdf", ".pdf")` — compared every file to itself and
|
|
131
|
+
reported *"9 rendered and current"*
|
|
132
|
+
- a regex using `[^.]` to span `2.4 … 11.1`, where every id contains a period —
|
|
133
|
+
returned zero across the whole corpus, three times, by someone who had already
|
|
134
|
+
documented the bug twice
|
|
135
|
+
- `if not X: return {}` upstream of `if X:` downstream — an unparseable input
|
|
136
|
+
silently switched off every check that depended on it, while the run printed `ok`
|
|
137
|
+
- `if not path.exists(): continue` in a checker — a moved file quietly left the
|
|
138
|
+
watch list and the run still said `clean`
|
|
139
|
+
|
|
140
|
+
Remembering these does not prevent them. Breaking the check does.
|
|
141
|
+
|
|
142
|
+
## Contents
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
skills/node-engine/
|
|
146
|
+
SKILL.md the method, the rule, and when not to use it
|
|
147
|
+
reference/
|
|
148
|
+
patterns.md the three shapes, with implementations
|
|
149
|
+
traps.md ten checks that could not fail — all real
|
|
150
|
+
taxonomy.md thirteen defect classes, ranked by how many each produced
|
|
151
|
+
scaffold/
|
|
152
|
+
stale.py the store and comparators (~150 lines, meant to be read)
|
|
153
|
+
checkall.py the runner; `--prove` prints how to break each check
|
|
154
|
+
viewer.html search the stores in a browser; paste text, see what anchors it
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The scaffold is deliberately **not** a library. Copy it in, delete what you do
|
|
158
|
+
not need, change what does not fit. A dependency you cannot read is one you will
|
|
159
|
+
trust when it is wrong — which is the exact failure this plugin exists to prevent.
|
|
160
|
+
|
|
161
|
+
## Origin
|
|
162
|
+
|
|
163
|
+
Extracted from a bilingual legal package: nine documents making claims about a
|
|
164
|
+
tenth, in two languages, with a generated digest in the middle. Thirteen defect
|
|
165
|
+
classes and about forty real defects informed it.
|
|
166
|
+
|
|
167
|
+
The examples throughout stay concrete on purpose. *"A check compared PDFs to
|
|
168
|
+
themselves and reported 9 rendered and current"* transfers. *"Ensure comparison
|
|
169
|
+
operands differ"* does not.
|
|
170
|
+
|
|
171
|
+
## Licence
|
|
172
|
+
|
|
173
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
nodeengine_install.py,sha256=AIAGLVluklZ8kxH7ID121UeM-34ybSGukW2ZVLsIjw8,5855
|
|
2
|
+
nodeengine-0.1.0.dist-info/METADATA,sha256=6UpQGz0iQYsSiZrcxryQInvfJ0-BOSZ-cegZ7eO-Cfw,7128
|
|
3
|
+
nodeengine-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
4
|
+
nodeengine-0.1.0.dist-info/entry_points.txt,sha256=cM2X9L20-b-i7Q1kcTiEyOpfbcizLWU0DGAD77mkP70,55
|
|
5
|
+
nodeengine-0.1.0.dist-info/licenses/LICENSE,sha256=s4lN5fjZSMkhHFv97ur-mkWxol9g0c9od3QuuW_RT5U,1067
|
|
6
|
+
nodeengine-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexyodude
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
nodeengine_install.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Install the NodeEngine scaffold into whatever repository you are standing in.
|
|
2
|
+
|
|
3
|
+
One line, from anywhere:
|
|
4
|
+
|
|
5
|
+
curl -sL https://raw.githubusercontent.com/Alexyodude/NodeEngine/main/install.py | python -
|
|
6
|
+
|
|
7
|
+
Or, if you have the repo or the plugin:
|
|
8
|
+
|
|
9
|
+
python install.py # into ./tools/
|
|
10
|
+
python install.py --into scripts # somewhere else
|
|
11
|
+
python install.py --dry-run # say what would happen, touch nothing
|
|
12
|
+
|
|
13
|
+
**It works with or without the rest of the repository.** If the scaffold is
|
|
14
|
+
sitting next to it, it copies from disk. If it is not -- because you piped this
|
|
15
|
+
file in on its own -- it fetches the scaffold files over HTTPS. That is what
|
|
16
|
+
makes the one-liner possible without a clone or a plugin install.
|
|
17
|
+
|
|
18
|
+
**It refuses to overwrite.** If a file is already there it says so and leaves it,
|
|
19
|
+
because the scaffold is meant to be edited once it lands -- silently replacing an
|
|
20
|
+
edited `stale.py` would destroy exactly the local knowledge this method is for.
|
|
21
|
+
`--force` is available and tells you what you would lose.
|
|
22
|
+
|
|
23
|
+
The scaffold is copied, not imported. That is deliberate: a dependency you cannot
|
|
24
|
+
read is one you will trust when it is wrong, which is the failure this whole
|
|
25
|
+
thing exists to prevent.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import shutil
|
|
31
|
+
import subprocess
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
RAW = "https://raw.githubusercontent.com/Alexyodude/NodeEngine/main"
|
|
36
|
+
REL = "skills/node-engine/scaffold"
|
|
37
|
+
FILES = ["stale.py", "checkall.py", "viewer.html"]
|
|
38
|
+
|
|
39
|
+
#: `__file__` is absent when this is piped to the interpreter, which is exactly
|
|
40
|
+
#: how the one-liner runs it. Falling back to cwd rather than crashing is what
|
|
41
|
+
#: lets the same script serve both paths.
|
|
42
|
+
HERE = Path(__file__).resolve().parent if "__file__" in globals() else Path.cwd()
|
|
43
|
+
LOCAL = HERE / REL
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def repo_root(start: Path) -> Path:
|
|
47
|
+
"""The git root if there is one, else where you are standing.
|
|
48
|
+
|
|
49
|
+
Not `Path.cwd()` alone: running from a subdirectory would scatter the
|
|
50
|
+
scaffold somewhere arbitrary, and you would not notice until an import
|
|
51
|
+
failed.
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
out = subprocess.run(["git", "rev-parse", "--show-toplevel"],
|
|
55
|
+
cwd=start, capture_output=True, text=True)
|
|
56
|
+
if out.returncode == 0 and out.stdout.strip():
|
|
57
|
+
return Path(out.stdout.strip())
|
|
58
|
+
except OSError:
|
|
59
|
+
pass
|
|
60
|
+
return start
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def fetch(name: str) -> bytes:
|
|
64
|
+
from urllib.request import urlopen
|
|
65
|
+
with urlopen(f"{RAW}/{REL}/{name}", timeout=30) as r:
|
|
66
|
+
return r.read()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def source(name: str) -> bytes:
|
|
70
|
+
"""Prefer disk; fall back to the network. Say which, so it is never a guess."""
|
|
71
|
+
p = LOCAL / name
|
|
72
|
+
if p.exists():
|
|
73
|
+
return p.read_bytes()
|
|
74
|
+
return fetch(name)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main() -> int:
|
|
78
|
+
#: Here rather than under `__main__`: the `nodeengine` entry point calls
|
|
79
|
+
#: main() directly, and a piped stdout on Windows is cp1252 without this.
|
|
80
|
+
try:
|
|
81
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
82
|
+
except (AttributeError, OSError):
|
|
83
|
+
pass
|
|
84
|
+
ap = argparse.ArgumentParser(
|
|
85
|
+
description="Install the NodeEngine scaffold into this repository.")
|
|
86
|
+
ap.add_argument("--into", default="tools", help="directory to install into (default: tools)")
|
|
87
|
+
ap.add_argument("--dry-run", action="store_true")
|
|
88
|
+
ap.add_argument("--force", action="store_true", help="overwrite existing files")
|
|
89
|
+
a = ap.parse_args()
|
|
90
|
+
|
|
91
|
+
root = repo_root(Path.cwd())
|
|
92
|
+
dest = root / a.into
|
|
93
|
+
where = "disk" if (LOCAL / FILES[0]).exists() else RAW
|
|
94
|
+
print(f"\n repository : {root}")
|
|
95
|
+
print(f" installing : {a.into}/")
|
|
96
|
+
print(f" scaffold : {where}\n")
|
|
97
|
+
|
|
98
|
+
wrote, skipped = [], []
|
|
99
|
+
for name in FILES:
|
|
100
|
+
target = dest / name
|
|
101
|
+
if target.exists() and not a.force:
|
|
102
|
+
skipped.append(name)
|
|
103
|
+
continue
|
|
104
|
+
try:
|
|
105
|
+
blob = source(name)
|
|
106
|
+
except Exception as e: # network, 404, TLS…
|
|
107
|
+
print(f" could not obtain {name}: {e}", file=sys.stderr)
|
|
108
|
+
return 1
|
|
109
|
+
wrote.append(name)
|
|
110
|
+
if not a.dry_run:
|
|
111
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
target.write_bytes(blob)
|
|
113
|
+
|
|
114
|
+
for n in wrote:
|
|
115
|
+
print(f" {'would write' if a.dry_run else 'wrote'} {a.into}/{n}")
|
|
116
|
+
for n in skipped:
|
|
117
|
+
print(f" kept {a.into}/{n} (already there — not overwritten)")
|
|
118
|
+
|
|
119
|
+
if skipped and not a.force:
|
|
120
|
+
print("\n Existing files were left alone. The scaffold is meant to be edited")
|
|
121
|
+
print(" after it lands, so replacing it would destroy local changes.")
|
|
122
|
+
print(" Use --force only after reading what you would lose.")
|
|
123
|
+
|
|
124
|
+
print(f"""
|
|
125
|
+
Next, and in this order:
|
|
126
|
+
|
|
127
|
+
1. Find the duplication. What does this repo state in more than one place?
|
|
128
|
+
Versions, counts, file inventories, a README describing a config, a
|
|
129
|
+
summary describing code. Write the list to {a.into}/DUPLICATION.md before
|
|
130
|
+
writing any code -- the survey is the half of the work that evaporates if
|
|
131
|
+
it only ever exists in your head or a chat scrollback.
|
|
132
|
+
|
|
133
|
+
2. Decide which copy is the source. Exactly one. Everything else derives.
|
|
134
|
+
|
|
135
|
+
3. Pick a shape per relationship — anchored, content-addressed, or ordered.
|
|
136
|
+
|
|
137
|
+
4. Edit {a.into}/checkall.py so its table names your checks. Adding a watched
|
|
138
|
+
thing should mean adding a row, never writing a new function.
|
|
139
|
+
|
|
140
|
+
5. Break every check on purpose and confirm it fails. A new check that passes
|
|
141
|
+
has told you nothing — you cannot distinguish "correct" from "structurally
|
|
142
|
+
unable to fail" by reading it.
|
|
143
|
+
|
|
144
|
+
python {a.into}/checkall.py --prove
|
|
145
|
+
|
|
146
|
+
Then commit. Once your checks keep stores, open {a.into}/viewer.html in a
|
|
147
|
+
browser to search them and probe what still anchors a piece of text.""")
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
raise SystemExit(main())
|