@nodii/dev 0.6.24 → 0.6.26
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 +46 -11
- package/dist/cli.js +62 -16
- package/dist/core.js +2 -2
- package/dist/devops-openapi.json +1 -1
- package/dist/doctor.js +20 -0
- package/dist/harness.js +114 -21
- package/dist/hook-doctor.js +126 -0
- package/dist/hooks.js +31 -1
- package/dist/lanes.js +57 -5
- package/dist/managed-hooks.js +166 -0
- package/dist/safety.js +112 -27
- package/dist/setup.js +18 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -309,7 +309,8 @@ nodii-dev lane step KEY --side backend|frontend --start|--done|--block STEP [--r
|
|
|
309
309
|
# --block --reason records the new reason (Dev issue 125 moves it onto the step)
|
|
310
310
|
nodii-dev lane evidence KEY --pr|--commit|--figma|--deploy|--dev-check REF
|
|
311
311
|
nodii-dev lane heartbeat KEY [--note TEXT]
|
|
312
|
-
nodii-dev lane review KEY
|
|
312
|
+
nodii-dev lane review KEY --file REVIEW.json
|
|
313
|
+
nodii-dev lane reviews KEY
|
|
313
314
|
nodii-dev lane land KEY
|
|
314
315
|
nodii-dev lane verify KEY --dev-check REF
|
|
315
316
|
nodii-dev lane list [--kind KIND] [--status STATUS] [--machine UUID]
|
|
@@ -350,19 +351,40 @@ nodii-dev docs upload PATH... --class ruling|conformance|editorial
|
|
|
350
351
|
nodii-dev docs ratify --domain KEY [--domain KEY ...] --decision DECISION.json [--wait]
|
|
351
352
|
nodii-dev docs parked [CODE] [--status pending|acknowledged]
|
|
352
353
|
|
|
353
|
-
nodii-dev harness install [--include-drafts] [--adopt PATH,...] [--dry-run]
|
|
354
|
-
nodii-dev harness update [--include-drafts] [--adopt PATH,...] [--dry-run]
|
|
354
|
+
nodii-dev harness install [--include-drafts] [--adopt PATH,...] [--dry-run] [--system]
|
|
355
|
+
nodii-dev harness update [--include-drafts] [--adopt PATH,...] [--dry-run] [--system]
|
|
355
356
|
nodii-dev harness check [--json] # 0 current, 1 differs from Dev, 2 no harness
|
|
356
357
|
nodii-dev harness update --check # the same check
|
|
357
358
|
```
|
|
358
359
|
|
|
359
|
-
`harness install` and `harness update` merge the
|
|
360
|
-
`.claude/settings.json`; every installed hook invokes
|
|
360
|
+
`harness install` and `harness update` merge the safety hooks into both
|
|
361
|
+
`.claude/settings.json` and `.codex/hooks.json`; every installed hook invokes
|
|
362
|
+
only `nodii-dev`. The
|
|
361
363
|
pre-hook blocks edits outside a lane's optional `ownedPaths`, generated UI/API
|
|
362
364
|
files, unsafe Git operations, stale or missing review receipts, lane identity
|
|
363
365
|
changes, production-secret/test-auth bypasses, cleanup of Docker resources the
|
|
364
366
|
run did not create, and deploy/apply commands without an exact grant. Allowed
|
|
365
|
-
calls produce no output.
|
|
367
|
+
calls produce no output. Codex receives runtime-native `Bash` and `apply_patch`
|
|
368
|
+
matchers, JSON denials, and `additionalContext` nudges. A compact session start
|
|
369
|
+
prints the current lane Freeze again.
|
|
370
|
+
|
|
371
|
+
Hard blocks apply only after Dev proves the current agent, branch and worktree
|
|
372
|
+
belong to an active lane. Interactive or otherwise unproven sessions receive
|
|
373
|
+
nudges instead. Push is blocked only when that proven lane also exposes the
|
|
374
|
+
review-receipt flow; until then the hook says it cannot verify the review and
|
|
375
|
+
allows the command.
|
|
376
|
+
|
|
377
|
+
Codex trusts system-managed hooks without a per-hash prompt. `--system` writes
|
|
378
|
+
the repository harness normally and refreshes the user-owned dispatcher under
|
|
379
|
+
`~/.config/nodii-dev/codex-managed-hooks`, then only prints the one-time
|
|
380
|
+
`sudo install` commands an operator can review and run to place its stable
|
|
381
|
+
policy in `/etc/codex/requirements.toml`; nodii-dev never runs sudo itself.
|
|
382
|
+
That system layer covers both `CODEX_HOME=~/.codex` and
|
|
383
|
+
`CODEX_HOME=~/.codex-work`; later `--system` updates need no sudo. The
|
|
384
|
+
dispatcher records the absolute executable path so hook processes never
|
|
385
|
+
depend on `PATH`, and refreshes it on every update. Outside a lane, or when
|
|
386
|
+
that executable is missing or too old, it fails open without diagnostics;
|
|
387
|
+
`doctor` reports that fail-open state for each Codex profile.
|
|
366
388
|
|
|
367
389
|
Until Dev exposes a lane's owned paths (AGENT-HARNESS-10), an edit with no
|
|
368
390
|
ownership field emits a nudge instead of claiming it is safe or blocking an
|
|
@@ -602,11 +624,14 @@ OAuth login.
|
|
|
602
624
|
`harness install` writes the repository's agent harness as Dev generates it
|
|
603
625
|
from the ratified methodology: the `CLAUDE.md` and `AGENTS.md` pointer
|
|
604
626
|
blocks, one pointer skill per methodology document, the Claude Code hooks
|
|
605
|
-
merged into `.claude/settings.json`,
|
|
627
|
+
merged into `.claude/settings.json`, their Codex equivalents merged into
|
|
628
|
+
`.codex/hooks.json`, and Dev's `.nodii/harness.json`. Every
|
|
606
629
|
generated file carries the methodology version it came from. Nothing about
|
|
607
630
|
the rules is authored locally. Run it in the service's own repository: it
|
|
608
|
-
installs one service at a time, and there is no fleet mode.
|
|
609
|
-
|
|
631
|
+
installs one service at a time, and there is no fleet mode. While no
|
|
632
|
+
methodology is ratified, install and update automatically retry with the
|
|
633
|
+
latest draft versions and print the exact draft methodology and source
|
|
634
|
+
versions they used. `--include-drafts` requests that behavior explicitly.
|
|
610
635
|
|
|
611
636
|
The CLI records what it wrote in `.nodii/harness-state.json` and never touches
|
|
612
637
|
anything else. An existing file it did not write is refused, unless the
|
|
@@ -654,6 +679,8 @@ exits 0 only when every check is `OK`. `--json` returns the same checks as
|
|
|
654
679
|
| `lane_limit` | The agent's active lanes against its lane limit. A full limit is `OK` when a lane on this branch holds the slot. |
|
|
655
680
|
| `figma_file` | Only with `--figma-file KEY` (a key or figma.com link), or `NODII_DEV_FIGMA_FILE`: whether Dev has the brief's Figma file registered. `MISSING` names the `figma file add` command, since `figma lock` would fail with `DESIGN_FILE_NOT_FOUND`. |
|
|
656
681
|
| `figma_scopes` | With `figma_file`, when the file is registered: the change set this agent holds in the file and its lock scopes (or the whole file), or that it holds none (DEC-0032). |
|
|
682
|
+
| `hooks_claude` | Whether Claude hooks are installed, trusted by that runtime, and have a private firing receipt. |
|
|
683
|
+
| `hooks_codex_personal`, `hooks_codex_work` | For `~/.codex` and `~/.codex-work`, whether Codex's own hook registry reports the hooks installed and trusted (including managed status), whether the managed absolute CLI is present and new enough, and whether that profile has fired one. |
|
|
657
684
|
|
|
658
685
|
Doctor may refresh the access token, as any command does. The permission table
|
|
659
686
|
is kept by hand from Dev's route policy until Dev's OpenAPI names each
|
|
@@ -670,9 +697,9 @@ serves for that step (see Rule delivery). Server refusals,
|
|
|
670
697
|
including operator-only actions, workflow blocks, lane limits and Built items
|
|
671
698
|
that are not on dev, retain Dev's error code/message and exit non-zero.
|
|
672
699
|
|
|
673
|
-
`setup` installs the Claude Code workflow
|
|
700
|
+
`setup` installs the Claude Code and Codex workflow harnesses in the current repository.
|
|
674
701
|
It writes `.claude/skills/nodii-build-workflow/SKILL.md`, merges SessionStart,
|
|
675
|
-
Stop and
|
|
702
|
+
Stop and tool hooks into `.claude/settings.json` and `.codex/hooks.json`, and maintains one
|
|
676
703
|
marked section in an existing `CLAUDE.md` or `AGENTS.md` (creating `CLAUDE.md`
|
|
677
704
|
when neither exists). Existing settings and hooks are preserved. The default
|
|
678
705
|
is a read-only preview; `--yes` applies, and `--check` is suitable for drift
|
|
@@ -712,6 +739,14 @@ The command reaches Dev through the edge, so it needs the edge's route for
|
|
|
712
739
|
`/devops/api/v1/lanes/:id/non-issue`; until that is on an environment, the
|
|
713
740
|
command gets the edge's 404 there.
|
|
714
741
|
|
|
742
|
+
`lane review` reads one JSON object with the review's exact `origin/main`
|
|
743
|
+
commit in `base`, exact reviewed commit in `head`,
|
|
744
|
+
`iterationCount`, `exitReason`, `appliedPatches`, `unresolvedProposals`,
|
|
745
|
+
`reviewerErrors`, and `findings`. Each finding has `perspective` (`code` or
|
|
746
|
+
`rules`), `severity`, `summary`, `detail`, `disposition`, optional `path` and
|
|
747
|
+
`line`, and a required `clauseId` for rules findings. `lane reviews` reads the
|
|
748
|
+
append-only history, including whether each cited clause was a missed trigger.
|
|
749
|
+
|
|
715
750
|
Heartbeat notes are optional and limited by Dev to 500 characters.
|
|
716
751
|
Prioritize and deprioritize require a 1–500 character reason, which the CLI
|
|
717
752
|
sends to Dev. `lane review` records the review gate.
|
package/dist/cli.js
CHANGED
|
@@ -20,11 +20,12 @@ import {
|
|
|
20
20
|
refuseFromLane,
|
|
21
21
|
revokeOAuth,
|
|
22
22
|
} from "./oauth.js";
|
|
23
|
-
import { heartbeatCurrentLane, postHook, postHookFailure, preHook, settingsSnippet, workflowSuggestion } from "./hooks.js";
|
|
23
|
+
import { heartbeatCurrentLane, postHook, postHookFailure, preHook, sessionStartContext, settingsSnippet, workflowSuggestion } from "./hooks.js";
|
|
24
24
|
import { setupCommand } from "./setup.js";
|
|
25
25
|
import { harnessCommand } from "./harness.js";
|
|
26
|
-
import { safetyPostHook, safetyPreHook } from "./safety.js";
|
|
26
|
+
import { hookRuntime, recordHookFire, safetyPostHook, safetyPreHook } from "./safety.js";
|
|
27
27
|
import { doctorCommand } from "./doctor.js";
|
|
28
|
+
import { dispatchManagedHook } from "./managed-hooks.js";
|
|
28
29
|
import {
|
|
29
30
|
figmaChangeSetShow,
|
|
30
31
|
figmaChangeSetText,
|
|
@@ -120,9 +121,10 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
120
121
|
figma changeset show CHANGE_SET # items, variants, round, decision and comment (with the previous round's)
|
|
121
122
|
figma file add FILE_KEY --name TEXT --kind design-system|surfaces|prototype [--module M]
|
|
122
123
|
# register a Figma file in Dev so it can be locked
|
|
123
|
-
hook claude-figma-pre|claude-figma-post|safety-pre|safety-post|print-settings
|
|
124
|
+
hook claude-figma-pre|claude-figma-post|safety-pre|safety-post|session-start|managed-dispatch|print-settings
|
|
124
125
|
setup [--yes|--check] # dry-run by default
|
|
125
|
-
harness install|update [--include-drafts[=false]] [--adopt PATH,...] [--dry-run]
|
|
126
|
+
harness install|update [--include-drafts[=false]] [--adopt PATH,...] [--dry-run] [--system]
|
|
127
|
+
# --system stages managed hooks and only prints operator-reviewed sudo commands
|
|
126
128
|
harness check # 0 current, 1 differs from Dev, 2 not installed
|
|
127
129
|
harness update --check # the same check
|
|
128
130
|
docs check|validate|upload <path...> --class ruling|conformance|editorial
|
|
@@ -139,7 +141,9 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
139
141
|
lane step <key> --side SIDE --start|--done|--block <step> [--reason TEXT]
|
|
140
142
|
# --block --reason on a blocked step records its new reason
|
|
141
143
|
lane evidence <key> --pr|--commit|--figma|--deploy|--dev-check REF
|
|
142
|
-
lane heartbeat|
|
|
144
|
+
lane heartbeat|land|verify|prioritize|deprioritize|stop <key>
|
|
145
|
+
lane review <key> --file REVIEW.json # base/head, outcome and clause-cited findings
|
|
146
|
+
lane reviews <key> # stored review history for the lane
|
|
143
147
|
lane non-issue <key> --reason TEXT --evidence TEXT --reroute KEY[,KEY]|none
|
|
144
148
|
--branch carried:KEY|dropped[:REASON] [--commits SHA[,SHA]]
|
|
145
149
|
# DEC-0011: terminal non_issue; issue -> wont_do; slot frees
|
|
@@ -755,15 +759,29 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
755
759
|
if (command === "hook") {
|
|
756
760
|
if (action === "print-settings")
|
|
757
761
|
return console.log(JSON.stringify(settingsSnippet(), null, 2));
|
|
758
|
-
if (action === "
|
|
762
|
+
if (action === "managed-dispatch") {
|
|
763
|
+
if (!rest[0] || rest.length > 1) throw new CliError("USAGE", "hook managed-dispatch needs one event name", 2);
|
|
764
|
+
const output = await dispatchManagedHook(rest[0], await stdinText());
|
|
765
|
+
if (output) process.stdout.write(output);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (["claude-figma-pre", "figma-pre"].includes(action)) {
|
|
759
769
|
// Fail closed: Claude Code only blocks a tool call on exit code 2, and
|
|
760
770
|
// treats any other non-zero exit as a non-blocking error that lets the
|
|
761
771
|
// write through. Every failure here (bad stdin, missing config, Dev
|
|
762
772
|
// error, lock denied) must therefore exit 2.
|
|
773
|
+
let payload;
|
|
763
774
|
try {
|
|
764
|
-
|
|
765
|
-
|
|
775
|
+
payload = JSON.parse(await stdinText());
|
|
776
|
+
await recordHookFire(payload).catch(() => {});
|
|
777
|
+
const result = await preHook(payload);
|
|
778
|
+
if (hookRuntime(payload) !== "codex") return emit(result, json, "allowed");
|
|
779
|
+
return;
|
|
766
780
|
} catch (error) {
|
|
781
|
+
if (payload && hookRuntime(payload) === "codex") {
|
|
782
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: `Blocked Figma write: ${error?.message ?? String(error)}` } }));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
767
785
|
throw new CliError(
|
|
768
786
|
error?.code ?? "FIGMA_HOOK_FAILED",
|
|
769
787
|
`Blocked Figma write: ${error?.message ?? String(error)}`,
|
|
@@ -774,11 +792,21 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
774
792
|
}
|
|
775
793
|
if (action === "safety-pre") {
|
|
776
794
|
// A malformed or unavailable safety check must not turn into permission.
|
|
795
|
+
let payload;
|
|
777
796
|
try {
|
|
778
|
-
|
|
779
|
-
|
|
797
|
+
payload = JSON.parse(await stdinText());
|
|
798
|
+
await recordHookFire(payload).catch(() => {});
|
|
799
|
+
const result = await safetyPreHook(payload);
|
|
800
|
+
const nudges = result.nudges ?? [];
|
|
801
|
+
if (nudges.length && hookRuntime(payload) === "codex")
|
|
802
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: nudges.join("\n") } }));
|
|
803
|
+
else for (const nudge of nudges) console.log(nudge);
|
|
780
804
|
return;
|
|
781
805
|
} catch (error) {
|
|
806
|
+
if (payload && hookRuntime(payload) === "codex") {
|
|
807
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: error?.message ?? String(error) } }));
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
782
810
|
throw new CliError(
|
|
783
811
|
error?.code ?? "SAFETY_HOOK_FAILED",
|
|
784
812
|
error?.message ?? String(error),
|
|
@@ -788,12 +816,18 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
788
816
|
}
|
|
789
817
|
}
|
|
790
818
|
if (action === "safety-post") {
|
|
819
|
+
let payload;
|
|
791
820
|
try {
|
|
792
|
-
|
|
821
|
+
payload = JSON.parse(await stdinText());
|
|
822
|
+
await recordHookFire(payload).catch(() => {});
|
|
823
|
+
await safetyPostHook(payload);
|
|
793
824
|
} catch (error) {
|
|
794
825
|
// A post-hook cannot undo a completed command; report state-tracking
|
|
795
826
|
// failure without making Claude treat the completed command as failed.
|
|
796
|
-
|
|
827
|
+
const message = `nodii-dev safety post hook: ${error?.code ?? "ERROR"}: ${error?.message ?? String(error)}`;
|
|
828
|
+
if (payload && hookRuntime(payload) === "codex")
|
|
829
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: message } }));
|
|
830
|
+
else console.error(message);
|
|
797
831
|
}
|
|
798
832
|
return;
|
|
799
833
|
}
|
|
@@ -802,18 +836,30 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
802
836
|
return emit(result, json, result.heartbeated ? `Heartbeat recorded for ${result.key}.` : "No current-branch lane; no heartbeat sent.");
|
|
803
837
|
}
|
|
804
838
|
const payload = JSON.parse(await stdinText());
|
|
839
|
+
await recordHookFire(payload).catch(() => {});
|
|
840
|
+
if (action === "session-start") {
|
|
841
|
+
const context = await sessionStartContext(payload);
|
|
842
|
+
if (context) console.log(context);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
805
845
|
if (action === "workflow-post") {
|
|
806
846
|
const suggestion = workflowSuggestion(payload);
|
|
807
|
-
if (suggestion
|
|
847
|
+
if (suggestion && hookRuntime(payload) === "codex")
|
|
848
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: suggestion } }));
|
|
849
|
+
else if (suggestion) console.log(suggestion);
|
|
808
850
|
return;
|
|
809
851
|
}
|
|
810
|
-
if (
|
|
852
|
+
if (["claude-figma-post", "figma-post"].includes(action)) {
|
|
811
853
|
try {
|
|
812
854
|
return emit(await postHook(payload), json, "logged");
|
|
813
855
|
} catch (error) {
|
|
814
856
|
const outcome = postHookFailure(error);
|
|
815
|
-
|
|
816
|
-
|
|
857
|
+
if (hookRuntime(payload) === "codex")
|
|
858
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: outcome.message } }));
|
|
859
|
+
else {
|
|
860
|
+
console.error(outcome.message);
|
|
861
|
+
if (outcome.exitCode) process.exitCode = outcome.exitCode;
|
|
862
|
+
}
|
|
817
863
|
return;
|
|
818
864
|
}
|
|
819
865
|
}
|
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.26";
|
|
8
8
|
export const configPath = () =>
|
|
9
9
|
process.env.NODII_DEV_CONFIG_PATH ??
|
|
10
10
|
join(homedir(), ".config/nodii-dev/config.json");
|
|
@@ -199,7 +199,7 @@ export async function checkLogin() {
|
|
|
199
199
|
export const encode = (value) => encodeURIComponent(value);
|
|
200
200
|
// Switches never take the next token as their value, so
|
|
201
201
|
// `docs upload --wait rulebook/` keeps rulebook/ as a path.
|
|
202
|
-
const SWITCHES = new Set(["json", "wait", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts", "drafted", "library", "async", "sync", "force"]);
|
|
202
|
+
const SWITCHES = new Set(["json", "wait", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts", "drafted", "library", "async", "sync", "force", "system"]);
|
|
203
203
|
export function parseArgs(argv) {
|
|
204
204
|
const flags = {};
|
|
205
205
|
const args = [];
|