@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 +12 -0
- package/cli/constants.mjs +1 -1
- package/cli/dev/run-cli-smoke.mjs +24 -0
- package/cli/dev/run-suite.mjs +17 -0
- package/cli/lib/audit-sweep-runtime/claude-auditor.mjs +13 -2
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +13 -2
- package/cli/lib/authority-convergence.mjs +20 -4
- package/cli/lib/topic-execution.mjs +19 -1
- package/config/bootstrap.yaml +1 -1
- package/package.json +3 -3
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
|
@@ -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
|
|
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
|
|
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({
|
|
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) =>
|
|
194
|
-
|
|
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" &&
|
package/config/bootstrap.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimiplatform/nimi-coding",
|
|
3
|
-
"version": "0.2.
|
|
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": "
|
|
62
|
-
"check:ci": "pnpm test && pnpm check:pack && node ./
|
|
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
|
}
|