@gonrocca/nodd 0.2.0 → 0.2.2
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/README.md +24 -0
- package/package.json +1 -1
- package/src/gates/request.ts +4 -1
- package/src/gates/track.test.ts +14 -0
- package/src/state.test.ts +14 -0
- package/src/state.ts +4 -1
package/README.md
CHANGED
|
@@ -87,6 +87,30 @@ steps**: they run no model and cannot be assigned one. `/nodd-models` shows them
|
|
|
87
87
|
as `mecanismo · sin modelo` and refuses an assignment. The other three are the
|
|
88
88
|
steps where a model actually does work.
|
|
89
89
|
|
|
90
|
+
## Choosing models
|
|
91
|
+
|
|
92
|
+
`/nodd-models` with no argument opens a picker: profiles, then slots, then the
|
|
93
|
+
providers and models of pi's live registry, then a thinking level. Nothing is
|
|
94
|
+
written until `— guardar y salir —`; leaving with `q` writes nothing at all.
|
|
95
|
+
|
|
96
|
+
A **profile** is a named set of assignments — `rapido`, `barato`, whatever you
|
|
97
|
+
call it — and the picker can create, edit, duplicate, activate and delete them.
|
|
98
|
+
The active profile is what the generated agents read. Editing a slot while a
|
|
99
|
+
profile is active edits that profile, so the two never drift apart silently.
|
|
100
|
+
|
|
101
|
+
The slot and the level both reach the generated agent's frontmatter as `model:`
|
|
102
|
+
and `thinking:`. **Changes apply to the next pi session**, not the running one:
|
|
103
|
+
the agent files are rewritten from the config when the extension loads, which
|
|
104
|
+
happens at startup.
|
|
105
|
+
|
|
106
|
+
The text forms do the same without the UI:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
/nodd-models implement=anthropic/claude-opus-4-1
|
|
110
|
+
/nodd-models profile new rapido
|
|
111
|
+
/nodd-models profile use rapido
|
|
112
|
+
```
|
|
113
|
+
|
|
90
114
|
## The gates
|
|
91
115
|
|
|
92
116
|
| gate | fires on | refuses when |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonrocca/nodd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Non-negotiable Organic Driven Development — the ODD protocol as runtime mechanism for pi: blocking gates, observed evidence, and promotion to /forge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/src/gates/request.ts
CHANGED
|
@@ -11,7 +11,10 @@ export type GateRequest = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export function targetPath(request: GateRequest): string | null {
|
|
14
|
-
|
|
14
|
+
// pi's own write and edit tools accept either name and read `file_path ??
|
|
15
|
+
// path` (`write.js:99`, `edit.js:92`), and the write tool sends `file_path`.
|
|
16
|
+
// Reading only `path` left every path-scoped rule blind on the real tool.
|
|
17
|
+
const path = request.input?.file_path ?? request.input?.path;
|
|
15
18
|
return typeof path === "string" && path !== "" ? path : null;
|
|
16
19
|
}
|
|
17
20
|
|
package/src/gates/track.test.ts
CHANGED
|
@@ -47,6 +47,20 @@ test("mutating bash blocks while a test command does not", () => {
|
|
|
47
47
|
|
|
48
48
|
// A doc the model can rewrite is a doc the model can forge, and then evidence
|
|
49
49
|
// is prose again. This holds even when the doc exists and the route is inline.
|
|
50
|
+
test("file_path is read like path: pi's write tool sends either", () => {
|
|
51
|
+
// `write.js:99` and `edit.js:92` both accept `file_path ?? path`, and pi's
|
|
52
|
+
// own write tool sends `file_path`. Reading only `path` left every
|
|
53
|
+
// path-scoped rule blind on the real tool: the .nodd/ guard below let a
|
|
54
|
+
// direct write to the feature document through.
|
|
55
|
+
const decision = trackGate(
|
|
56
|
+
routed("tracked"),
|
|
57
|
+
{ toolName: "write", input: { file_path: "/repo/.nodd/demo/feature.md" } },
|
|
58
|
+
emptyPolicy(),
|
|
59
|
+
present,
|
|
60
|
+
);
|
|
61
|
+
assert.equal(decision.allow, false, "a write under .nodd/ is refused however pi names the argument");
|
|
62
|
+
});
|
|
63
|
+
|
|
50
64
|
test("a direct write to .nodd/** is always blocked, pointing at nodd_task", () => {
|
|
51
65
|
for (const state of [routed("tracked"), routed("inline"), emptyCommitted()]) {
|
|
52
66
|
for (const path of [".nodd/x/feature.md", ".nodd/demo/state.json", "/repo/.nodd/demo/feature.md"]) {
|
package/src/state.test.ts
CHANGED
|
@@ -53,6 +53,20 @@ test("a fixed sequence folds into an exact snapshot", () => {
|
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
test("a write naming file_path is recorded, like pi's own write tool sends it", () => {
|
|
57
|
+
// pi's write tool sends `file_path` (`write.js:99`); edit accepts both
|
|
58
|
+
// (`edit.js:92`). Reading only `path` left filesWritten empty on every real
|
|
59
|
+
// write — and filesWritten is what evidence times a checkoff against and what
|
|
60
|
+
// delegate counts, so both went blind against the actual tool.
|
|
61
|
+
const committed = foldAll(emptyCommitted(), [
|
|
62
|
+
obs({ toolName: "write", input: { file_path: "c.ts" } }),
|
|
63
|
+
obs({ toolName: "read", input: { file_path: "a.ts" } }),
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
assert.deepEqual([...committed.filesWritten.keys()], ["c.ts"]);
|
|
67
|
+
assert.deepEqual([...committed.filesRead], ["a.ts"]);
|
|
68
|
+
});
|
|
69
|
+
|
|
56
70
|
test("distinct-path counting: one file read six times is one file", () => {
|
|
57
71
|
const events = Array.from({ length: 6 }, () => obs({ toolName: "read", input: { path: "same.ts" } }));
|
|
58
72
|
const committed = foldAll(emptyCommitted(), events);
|
package/src/state.ts
CHANGED
|
@@ -117,7 +117,10 @@ export function fold(committed: Committed, obs: Observation): Committed {
|
|
|
117
117
|
declaration: committed.declaration,
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
// `file_path ?? path`: pi's write tool sends the first, edit accepts both
|
|
121
|
+
// (`write.js:99`, `edit.js:92`). Reading only `path` left filesWritten empty
|
|
122
|
+
// on every real write, which is what evidence and delegate count.
|
|
123
|
+
const path = str(obs.input.file_path) || str(obs.input.path);
|
|
121
124
|
if (READ_TOOLS.has(obs.toolName) && path) next.filesRead.add(path);
|
|
122
125
|
if (WRITE_TOOLS.has(obs.toolName) && path) next.filesWritten.set(path, { at: obs.at, seq: next.toolCalls });
|
|
123
126
|
|