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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nodeengine = nodeengine_install:main
@@ -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())