@nodii/dev 0.6.29 → 0.6.30
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 +17 -1
- package/dist/cli.js +15 -4
- package/dist/core.js +37 -14
- package/dist/devops-openapi.json +1 -1
- package/dist/doctor.js +18 -1
- package/dist/freeze.js +328 -0
- package/dist/gather.js +241 -0
- package/dist/hooks.js +16 -42
- package/dist/lanes.js +170 -9
- package/dist/nodii-dev.js +12 -2
- package/dist/setup.js +23 -6
- package/dist/starting.js +52 -23
- package/dist/upload.js +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ nodii-dev login [--no-browser] [--scope KEYS] [--force]
|
|
|
87
87
|
nodii-dev logout
|
|
88
88
|
nodii-dev whoami # OAuth identity plus the registered agent
|
|
89
89
|
nodii-dev config show
|
|
90
|
-
nodii-dev doctor [--figma-file KEY] [--json] # preflight:
|
|
90
|
+
nodii-dev doctor [--figma-file KEY] [--json] # preflight: Dev, hooks, local Ollama + Gather model, optional Figma
|
|
91
91
|
|
|
92
92
|
nodii-dev api list [--tag TAG] [--json]
|
|
93
93
|
nodii-dev api OPERATION_ID [--param name=value ...] [--body @file|-|JSON]
|
|
@@ -129,6 +129,15 @@ nodii-dev decisions get D123
|
|
|
129
129
|
nodii-dev decisions search WORDS
|
|
130
130
|
nodii-dev decisions create|lock|supersede|amend
|
|
131
131
|
|
|
132
|
+
nodii-dev lane gather [KEY] [--delta] [--objective TEXT] [--done-check TEXT]
|
|
133
|
+
[--paths A,B] [--components K,L]
|
|
134
|
+
nodii-dev lane freeze add KEY --section SECTION --file ITEM.json --reason TEXT
|
|
135
|
+
nodii-dev lane freeze drop KEY --section SECTION --item ID --reason TEXT
|
|
136
|
+
nodii-dev lane freeze swap KEY --section SECTION [--item ID] --file VALUE.json --reason TEXT
|
|
137
|
+
nodii-dev lane freeze show KEY
|
|
138
|
+
nodii-dev lane freeze history KEY
|
|
139
|
+
nodii-dev rules find "QUESTION" [--limit N]
|
|
140
|
+
|
|
132
141
|
nodii-dev open-questions list|show|answer
|
|
133
142
|
nodii-dev feature-docs list|show|claim|release|status
|
|
134
143
|
nodii-dev contracts grpc|rest|outbox|saga|table publish|status
|
|
@@ -764,6 +773,13 @@ call until `lane gather --delta` serves the current rules and acknowledges the
|
|
|
764
773
|
newest displayed version. `NODII_DEV_RUN_ID` makes this check per subagent run,
|
|
765
774
|
so one child cannot acknowledge for its siblings.
|
|
766
775
|
|
|
776
|
+
Gather also persists the lane's bounded Freeze: the objective and done check,
|
|
777
|
+
strict repository/Figma boundaries, the current pinned clauses, and the lane's
|
|
778
|
+
lookups, decisions, and unconfirmed questions. Ordinary CLI calls print and
|
|
779
|
+
acknowledge a changed Freeze before continuing, and compact SessionStart uses
|
|
780
|
+
the same backstop. `lane freeze add|drop|swap` makes a reasoned, version-checked
|
|
781
|
+
change; `show|history` reads the current contract or its append-only history.
|
|
782
|
+
|
|
767
783
|
`lane start` creates and starts a lane. It infers the repository (by short
|
|
768
784
|
name, as Dev compares it), branch and worktree from git unless overridden, and resolves `--issue` keys to the UUIDs
|
|
769
785
|
required by Dev. Lane keys are passed directly to every lane and Built
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { harnessCommand } from "./harness.js";
|
|
|
26
26
|
import { hookRuntime, recordHookFire, safetyPostHook, safetyPreHook } from "./safety.js";
|
|
27
27
|
import { doctorCommand } from "./doctor.js";
|
|
28
28
|
import { dispatchManagedHook } from "./managed-hooks.js";
|
|
29
|
+
import { freezeCommand } from "./freeze.js";
|
|
29
30
|
import {
|
|
30
31
|
figmaChangeSetShow,
|
|
31
32
|
figmaChangeSetText,
|
|
@@ -95,8 +96,8 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
95
96
|
login [--force] | logout | whoami | config show
|
|
96
97
|
# login says so when the machine is already signed in; --force
|
|
97
98
|
# replaces that one sign-in (never from a lane)
|
|
98
|
-
doctor [--figma-file KEY] [--json] # preflight: sign-in, agent, permissions, machine, repo, lane slots
|
|
99
|
-
#
|
|
99
|
+
doctor [--figma-file KEY] [--json] # preflight: sign-in, agent, permissions, machine, repo, lane slots,
|
|
100
|
+
# local Ollama + pinned Gather model, and optional Figma registration
|
|
100
101
|
agent register --name NAME | agent heartbeat
|
|
101
102
|
issues list|show|create|update|claim|release
|
|
102
103
|
issues comment ISSUE --body TEXT # or --body-file FILE; issues update --comment TEXT|--comment-file FILE adds one too
|
|
@@ -135,7 +136,7 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
135
136
|
figma file add FILE_KEY --name TEXT --kind design-system|surfaces|prototype [--module M]
|
|
136
137
|
# register a Figma file in Dev so it can be locked
|
|
137
138
|
hook claude-figma-pre|claude-figma-post|safety-pre|safety-post|session-start|managed-dispatch|print-settings
|
|
138
|
-
setup [--yes|--check] # dry-run by default
|
|
139
|
+
setup [--yes|--check] # dry-run by default; installs/checks Ollama and pulls the Gather model
|
|
139
140
|
harness install|update [--include-drafts[=false]] [--adopt PATH,...] [--dry-run] [--system]
|
|
140
141
|
# --system stages managed hooks and only prints operator-reviewed sudo commands
|
|
141
142
|
harness check # 0 current, 1 differs from Dev, 2 not installed
|
|
@@ -150,6 +151,7 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
150
151
|
rulebook list|show CODE [--version V]|diff CODE
|
|
151
152
|
rules core # the generated core (M05-33); refused over the cap
|
|
152
153
|
rules report [--section S] [--lane KEY] # delivery totals per document; rules served per lane
|
|
154
|
+
rules find "QUESTION" [--limit N] # answer an ad-hoc rules question from the indexed sections
|
|
153
155
|
rules cap TOKENS # operator: set the core token cap (default 3000)
|
|
154
156
|
|
|
155
157
|
coordinator start --machine NAME # register this machine's one watched coordinator; refuses a live second
|
|
@@ -162,7 +164,8 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
162
164
|
# authoritative preflight, one worktree per repo, watched launch
|
|
163
165
|
# uses the provisioned agents/lane-<key>.json Dev identity
|
|
164
166
|
lane amend <key> --objective|--scope|--rules TEXT --reason TEXT
|
|
165
|
-
lane gather [<key>] [--delta]
|
|
167
|
+
lane gather [<key>] [--delta] [--objective TEXT] [--done-check TEXT]
|
|
168
|
+
# infers a launched worktree; explicit contract inputs support manual lane start
|
|
166
169
|
run [--kind build|test_run|subagent|deploy] [--name TEXT] -- <command> [args...]
|
|
167
170
|
# watched command: registers with NODII_DEV_RUN_ID,
|
|
168
171
|
# heartbeats, and records exit code + redacted log tail
|
|
@@ -175,12 +178,18 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
175
178
|
lane heartbeat|resume|land|verify|prioritize|deprioritize|stop <key>
|
|
176
179
|
lane review <key> --file REVIEW.json # base/head, outcome and clause-cited findings
|
|
177
180
|
lane reviews <key> # stored review history for the lane
|
|
181
|
+
lane freeze add <key> --section SECTION --file ITEM.json --reason TEXT
|
|
182
|
+
lane freeze drop <key> --section SECTION --item ID --reason TEXT
|
|
183
|
+
lane freeze swap <key> --section SECTION [--item ID] --file VALUE.json --reason TEXT
|
|
184
|
+
lane freeze show|history <key> # durable compact-safe contract and all versions
|
|
178
185
|
lane non-issue <key> --reason TEXT --evidence TEXT --reroute KEY[,KEY]|none
|
|
179
186
|
--branch carried:KEY|dropped[:REASON] [--commits SHA[,SHA]]
|
|
180
187
|
# DEC-0011: terminal non_issue; issue -> wont_do; slot frees
|
|
181
188
|
lane list [--kind KIND] [--status STATUS] [--mine] [--cursor CURSOR]
|
|
182
189
|
lane show <key> | next <key> [--paths A,B] [--components K,L] [--role R]
|
|
183
190
|
# next step, plus the rules Dev serves for it (DEC-0020)
|
|
191
|
+
lane gather [key] [--delta] [--paths A,B] [--components K,L]
|
|
192
|
+
# local document pick; prints Lane contract then Clause map
|
|
184
193
|
built list | built ack <key...>
|
|
185
194
|
machine list|show|set-coordinator|assign|report
|
|
186
195
|
agent lane-limit|grant|revoke
|
|
@@ -574,6 +583,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
574
583
|
if (command === "lane" && action === "launch") return emitCommand(await launchLaneCommand(rest[0], f));
|
|
575
584
|
if (command === "lane" && action === "amend") return emitCommand(await amendLaneCommand(rest[0], f));
|
|
576
585
|
if (command === "lane" && action === "gather") return emitCommand(await gatherLaneCommand(rest[0], f));
|
|
586
|
+
if (command === "lane" && action === "freeze")
|
|
587
|
+
return emitCommand(await freezeCommand(rest[0], rest.slice(1), f));
|
|
577
588
|
if (command === "lane") return emitCommand(await laneCommand(action, rest, f));
|
|
578
589
|
if (command === "next") return emitCommand(await nextCommand(action, f));
|
|
579
590
|
if (command === "built") return emitCommand(await builtCommand(action, rest, f));
|
package/dist/core.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { homedir, platform } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
|
|
7
|
-
export const VERSION = "0.6.
|
|
7
|
+
export const VERSION = "0.6.30";
|
|
8
8
|
export const configPath = () =>
|
|
9
9
|
process.env.NODII_DEV_CONFIG_PATH ??
|
|
10
10
|
join(homedir(), ".config/nodii-dev/config.json");
|
|
@@ -119,9 +119,30 @@ const staleCode = (raw) => {
|
|
|
119
119
|
}
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const withSignal = (promise, signal) => {
|
|
123
|
+
if (!signal) return promise;
|
|
124
|
+
if (signal.aborted) return Promise.reject(signal.reason ?? new Error("aborted"));
|
|
125
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
126
|
+
const abort = () => rejectPromise(signal.reason ?? new Error("aborted"));
|
|
127
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
128
|
+
Promise.resolve(promise).then(
|
|
129
|
+
(value) => { signal.removeEventListener("abort", abort); resolvePromise(value); },
|
|
130
|
+
(error) => { signal.removeEventListener("abort", abort); rejectPromise(error); },
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
async function devFetch(method, path, bodyText, contentType, accept, extra = {}, signal) {
|
|
136
|
+
const wait = async (promise) => {
|
|
137
|
+
try {
|
|
138
|
+
return await withSignal(promise, signal);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (signal?.aborted) throw new CliError("DEV_TIMEOUT", `Dev request timed out: ${method} ${path}`, 1);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const config = await wait(readConfig());
|
|
145
|
+
const auth = await wait(authProvider());
|
|
125
146
|
const url = requestUrl(config, path);
|
|
126
147
|
// The edge refuses a token minted before the principal's claims epoch with
|
|
127
148
|
// 401 token_stale (refresh and retry once). The request never reached Dev,
|
|
@@ -132,26 +153,28 @@ async function devFetch(method, path, bodyText, contentType, accept, extra = {})
|
|
|
132
153
|
for (;;) {
|
|
133
154
|
const headers = { accept, ...extra };
|
|
134
155
|
if (bodyText !== undefined) headers["content-type"] = contentType;
|
|
135
|
-
Object.assign(headers, await auth.headers({ method, path, body: bodyText ?? "" }));
|
|
156
|
+
Object.assign(headers, await wait(auth.headers({ method, path, body: bodyText ?? "" })));
|
|
136
157
|
let response;
|
|
137
158
|
try {
|
|
138
|
-
response = await fetch(url, { method, headers, body: bodyText });
|
|
159
|
+
response = await wait(fetch(url, { method, headers, body: bodyText, signal }));
|
|
139
160
|
} catch (error) {
|
|
161
|
+
if (error?.code === "DEV_TIMEOUT") throw error;
|
|
162
|
+
if (signal?.aborted) throw new CliError("DEV_TIMEOUT", `Dev request timed out: ${method} ${path}`, 1);
|
|
140
163
|
throw new CliError("DEV_UNREACHABLE", `Cannot reach Dev at ${new URL(url).origin}: ${error.message}`);
|
|
141
164
|
}
|
|
142
|
-
const raw = await response.text();
|
|
165
|
+
const raw = await wait(response.text());
|
|
143
166
|
if (response.status !== 401 || typeof auth.refreshStale !== "function" || !staleCode(raw)) return { response, raw };
|
|
144
167
|
if (refreshed) return { response, raw };
|
|
145
168
|
// A cached token that is stale too is not adopted twice: the next one is refreshed.
|
|
146
169
|
const used = String(headers.authorization ?? "").replace(/^Bearer /, "");
|
|
147
|
-
if ((await auth.refreshStale(used, { force: adopted })) === "refreshed") refreshed = true;
|
|
170
|
+
if ((await wait(auth.refreshStale(used, { force: adopted }))) === "refreshed") refreshed = true;
|
|
148
171
|
else adopted = true;
|
|
149
172
|
}
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
/** A JSON call that keeps the status: `{ status, payload }`. Errors throw as devRawRequest's do. */
|
|
153
|
-
export async function devExchange(method, path, bodyText, { contentType = "application/json", headers = {} } = {}) {
|
|
154
|
-
const { response, raw } = await devFetch(method, path, bodyText, contentType, "application/json", headers);
|
|
176
|
+
export async function devExchange(method, path, bodyText, { contentType = "application/json", headers = {}, signal } = {}) {
|
|
177
|
+
const { response, raw } = await devFetch(method, path, bodyText, contentType, "application/json", headers, signal);
|
|
155
178
|
let payload;
|
|
156
179
|
try {
|
|
157
180
|
payload = raw ? JSON.parse(raw) : {};
|
|
@@ -168,8 +191,8 @@ export async function devExchange(method, path, bodyText, { contentType = "appli
|
|
|
168
191
|
return { status: response.status, payload };
|
|
169
192
|
}
|
|
170
193
|
|
|
171
|
-
export async function devRawRequest(method, path, bodyText, contentType = "application/json") {
|
|
172
|
-
return (await devExchange(method, path, bodyText, { contentType })).payload;
|
|
194
|
+
export async function devRawRequest(method, path, bodyText, contentType = "application/json", options = {}) {
|
|
195
|
+
return (await devExchange(method, path, bodyText, { ...options, contentType })).payload;
|
|
173
196
|
}
|
|
174
197
|
|
|
175
198
|
/** A Dev route that answers with text (e.g. a document body as text/markdown). Errors are Dev's JSON. */
|
|
@@ -181,8 +204,8 @@ export async function devTextRequest(method, path) {
|
|
|
181
204
|
throw new CliError(payload.code ?? `HTTP_${response.status}`, payload.message ?? `Dev returned HTTP ${response.status}`, 1, payload.details);
|
|
182
205
|
}
|
|
183
206
|
|
|
184
|
-
export const devRequest = (method, path, body) =>
|
|
185
|
-
devRawRequest(method, path, body === undefined ? undefined : JSON.stringify(body));
|
|
207
|
+
export const devRequest = (method, path, body, options) =>
|
|
208
|
+
devRawRequest(method, path, body === undefined ? undefined : JSON.stringify(body), "application/json", options);
|
|
186
209
|
|
|
187
210
|
/** A JSON call sent with `Prefer: respond-async` (RFC 7240): `{ status, payload }`, 202 when Dev took it as a job. */
|
|
188
211
|
export const devAsyncRequest = async (method, path, body) =>
|