@nimiplatform/nimi-coding 0.2.7 → 0.2.8

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/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to `@nimiplatform/nimi-coding` are tracked here.
4
4
 
5
5
  This project follows semantic versioning for published npm releases.
6
6
 
7
+ ## 0.2.8
8
+
9
+ - Fixed Windows audit-sweep execution for JavaScript auditor entrypoints by
10
+ invoking `.cjs`, `.js`, and `.mjs` binaries through the active Node runtime
11
+ before passing CLI arguments to Codex or Claude auditors.
12
+ - Hardened post-update proof lineage selection by ordering worker prompts with
13
+ nanosecond file timestamps and explicit dispatch-time mtimes, so rapid
14
+ remediation dispatches do not collapse into ambiguous or stale prompt
15
+ lineage.
16
+ - Made README example alignment tolerant of CRLF checkouts without relaxing the
17
+ documented topic command shape.
18
+
7
19
  ## 0.2.7
8
20
 
9
21
  - Added delegated projection admissions for spec-authority sweeps so a host
package/cli/constants.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION = "0.2.7";
1
+ export const VERSION = "0.2.8";
2
2
  export const PACKAGE_NAME = "@nimiplatform/nimi-coding";
3
3
  export const BOOTSTRAP_CONTRACT_ID = "nimicoding.bootstrap";
4
4
  export const BOOTSTRAP_CONTRACT_VERSION = 1;
@@ -0,0 +1,24 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
6
+ const cliPath = path.join(repoRoot, "bin", "nimicoding.mjs");
7
+
8
+ for (const args of [["--help"], ["--version"]]) {
9
+ const result = spawnSync(process.execPath, [cliPath, ...args], {
10
+ cwd: repoRoot,
11
+ encoding: "utf8",
12
+ env: { ...process.env, NIMICODING_LANG: "en", NO_COLOR: "1" },
13
+ });
14
+ if (result.error) {
15
+ process.stderr.write(`${args.join(" ")} smoke failed: ${result.error.message}\n`);
16
+ process.exit(1);
17
+ }
18
+ if (result.status !== 0) {
19
+ if (result.stdout) process.stdout.write(result.stdout);
20
+ if (result.stderr) process.stderr.write(result.stderr);
21
+ process.stderr.write(`${args.join(" ")} smoke exited with ${result.status ?? "unknown"}\n`);
22
+ process.exit(result.status ?? 1);
23
+ }
24
+ }
@@ -0,0 +1,17 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
6
+ const result = spawnSync(process.execPath, ["--test"], {
7
+ cwd: repoRoot,
8
+ stdio: "inherit",
9
+ env: { ...process.env, NIMICODING_LANG: "en", NO_COLOR: "1" },
10
+ });
11
+
12
+ if (result.error) {
13
+ process.stderr.write(`node --test failed: ${result.error.message}\n`);
14
+ process.exit(1);
15
+ }
16
+
17
+ process.exit(result.status ?? 1);
@@ -192,6 +192,16 @@ function terminateProcess(child, signal) {
192
192
  }
193
193
  }
194
194
 
195
+ function executableInvocation(executable, args) {
196
+ if (
197
+ process.platform === "win32" &&
198
+ [".cjs", ".js", ".mjs"].includes(path.extname(executable).toLowerCase())
199
+ ) {
200
+ return { command: process.execPath, args: [executable, ...args] };
201
+ }
202
+ return { command: executable, args };
203
+ }
204
+
195
205
  const CLAUDE_AUDIT_OUTPUT_SCHEMA = JSON.stringify({
196
206
  type: "object",
197
207
  properties: {
@@ -210,7 +220,7 @@ function runClaudeExec({ projectRoot, claudeBin, rawOutputPath, prompt, timeoutM
210
220
  let timedOut = false;
211
221
  let settled = false;
212
222
  let killTimer = null;
213
- const child = spawn(claudeBin, [
223
+ const invocation = executableInvocation(claudeBin, [
214
224
  "-p",
215
225
  "--output-format", "json",
216
226
  "--permission-mode", "bypassPermissions",
@@ -219,7 +229,8 @@ function runClaudeExec({ projectRoot, claudeBin, rawOutputPath, prompt, timeoutM
219
229
  "--no-session-persistence",
220
230
  "--add-dir", projectRoot,
221
231
  "--json-schema", CLAUDE_AUDIT_OUTPUT_SCHEMA,
222
- ], {
232
+ ]);
233
+ const child = spawn(invocation.command, invocation.args, {
223
234
  cwd: projectRoot,
224
235
  stdio: ["pipe", "pipe", "pipe"],
225
236
  detached: process.platform !== "win32",
@@ -102,13 +102,23 @@ function terminateProcess(child, signal) {
102
102
  }
103
103
  }
104
104
 
105
+ function executableInvocation(executable, args) {
106
+ if (
107
+ process.platform === "win32" &&
108
+ [".cjs", ".js", ".mjs"].includes(path.extname(executable).toLowerCase())
109
+ ) {
110
+ return { command: process.execPath, args: [executable, ...args] };
111
+ }
112
+ return { command: executable, args };
113
+ }
114
+
105
115
  function runCodexExec({ projectRoot, codexBin, rawOutputPath, prompt, timeoutMs }) {
106
116
  return new Promise((resolve) => {
107
117
  const boundedTimeoutMs = Number.isInteger(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_CODEX_TIMEOUT_MS;
108
118
  let timedOut = false;
109
119
  let settled = false;
110
120
  let killTimer = null;
111
- const child = spawn(codexBin, [
121
+ const invocation = executableInvocation(codexBin, [
112
122
  "exec",
113
123
  "-C",
114
124
  projectRoot,
@@ -117,7 +127,8 @@ function runCodexExec({ projectRoot, codexBin, rawOutputPath, prompt, timeoutMs
117
127
  "--output-last-message",
118
128
  rawOutputPath,
119
129
  "-",
120
- ], {
130
+ ]);
131
+ const child = spawn(invocation.command, invocation.args, {
121
132
  cwd: projectRoot,
122
133
  stdio: ["pipe", "pipe", "pipe"],
123
134
  detached: process.platform !== "win32",
@@ -183,15 +183,31 @@ async function latestWorkerPromptPacketId(topicDir, packetEntries) {
183
183
  if (!packetIds.has(packetId)) continue;
184
184
  const promptPath = path.join(topicDir, entry.name);
185
185
  try {
186
- const promptStat = await stat(promptPath);
187
- prompts.push({ packetId, mtimeMs: promptStat.mtimeMs, promptRefName: entry.name });
186
+ const promptStat = await stat(promptPath, { bigint: true });
187
+ prompts.push({
188
+ packetId,
189
+ mtimeNs: promptStat.mtimeNs,
190
+ ctimeNs: promptStat.ctimeNs,
191
+ birthtimeNs: promptStat.birthtimeNs,
192
+ promptRefName: entry.name,
193
+ });
188
194
  } catch {
189
195
  return null;
190
196
  }
191
197
  }
192
198
  if (prompts.length === 0) return null;
193
- prompts.sort((left, right) => right.mtimeMs - left.mtimeMs || left.promptRefName.localeCompare(right.promptRefName));
194
- if (prompts.length > 1 && prompts[0].mtimeMs === prompts[1].mtimeMs) return { ambiguous: true };
199
+ prompts.sort((left, right) => (
200
+ Number(right.mtimeNs - left.mtimeNs)
201
+ || Number(right.ctimeNs - left.ctimeNs)
202
+ || Number(right.birthtimeNs - left.birthtimeNs)
203
+ || left.promptRefName.localeCompare(right.promptRefName)
204
+ ));
205
+ if (
206
+ prompts.length > 1
207
+ && prompts[0].mtimeNs === prompts[1].mtimeNs
208
+ && prompts[0].ctimeNs === prompts[1].ctimeNs
209
+ && prompts[0].birthtimeNs === prompts[1].birthtimeNs
210
+ ) return { ambiguous: true };
195
211
  return prompts[0];
196
212
  }
197
213
 
@@ -1,4 +1,4 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { readdir, readFile, utimes, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import YAML from "yaml";
4
4
 
@@ -35,6 +35,22 @@ import {
35
35
  export function promptFilename(packetId, role) {
36
36
  return `prompt-${packetId}-${role}.md`;
37
37
  }
38
+
39
+ async function nextPromptMtime(topicDir, role) {
40
+ let rolePromptCount = 0;
41
+ try {
42
+ const entries = await readdir(topicDir, { withFileTypes: true });
43
+ rolePromptCount = entries.filter((entry) => (
44
+ entry.isFile()
45
+ && entry.name.startsWith("prompt-")
46
+ && entry.name.endsWith(`-${role}.md`)
47
+ )).length;
48
+ } catch {
49
+ rolePromptCount = 0;
50
+ }
51
+ return new Date(Date.now() + rolePromptCount + 1);
52
+ }
53
+
38
54
  export async function dispatchTopicPacket(projectRoot, input, packetId, role) {
39
55
  const loaded = await loadTopicPacket(projectRoot, input, packetId);
40
56
  if (!loaded.ok) return loaded;
@@ -49,8 +65,10 @@ export async function dispatchTopicPacket(projectRoot, input, packetId, role) {
49
65
  if (!["candidate", "admitted", "preflight", "dispatched"].includes(loaded.packet.status))
50
66
  return { ok: false, error: `Packet is not dispatchable from status ${loaded.packet.status}` };
51
67
  const promptPath = path.join(loaded.topicDir, promptFilename(packetId, role));
68
+ const promptMtime = await nextPromptMtime(loaded.topicDir, role);
52
69
  return (
53
70
  await writeFile(promptPath, buildDispatchPrompt(loaded.packet, loaded.topicId, role), "utf8"),
71
+ await utimes(promptPath, promptMtime, promptMtime),
54
72
  (loaded.packet.status = "dispatched"),
55
73
  await writeFile(loaded.packetPath, packetMarkdown(loaded.packet), "utf8"),
56
74
  role === "worker" &&
@@ -1,6 +1,6 @@
1
1
  version: 1
2
2
  initialized_by: "@nimiplatform/nimi-coding"
3
- cli_version: "0.2.7"
3
+ cli_version: "0.2.8"
4
4
  bootstrap_contract: "nimicoding.bootstrap"
5
5
  bootstrap_contract_version: 1
6
6
  profile: default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimiplatform/nimi-coding",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "private": false,
5
5
  "description": "AI-native coding governance toolkit for bootstrapping .nimi/** into arbitrary projects.",
6
6
  "license": "MIT",
@@ -58,8 +58,8 @@
58
58
  "LICENSE"
59
59
  ],
60
60
  "scripts": {
61
- "test": "NIMICODING_LANG=en NO_COLOR=1 node --test",
62
- "check:ci": "pnpm test && pnpm check:pack && node ./bin/nimicoding.mjs --help >/dev/null && node ./bin/nimicoding.mjs --version >/dev/null",
61
+ "test": "node ./cli/dev/run-suite.mjs",
62
+ "check:ci": "pnpm test && pnpm check:pack && node ./cli/dev/run-cli-smoke.mjs",
63
63
  "check:pack": "npm pack --dry-run"
64
64
  }
65
65
  }