@noir-ai/create 1.16.0-beta.1 → 1.17.0-beta.1
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/dist/index.d.ts +8 -2
- package/dist/index.js +58 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/templates/anti-slop.md.tmpl +50 -0
- package/templates/rules-seed.md.tmpl +17 -1
package/dist/index.d.ts
CHANGED
|
@@ -238,8 +238,14 @@ interface WriteOutcome {
|
|
|
238
238
|
*
|
|
239
239
|
* Parent directories are NOT created here — the orchestrator does that once
|
|
240
240
|
* for the whole manifest so a missing dir is a single, attributable failure
|
|
241
|
-
* rather than N silent ones inside the writer.
|
|
242
|
-
|
|
241
|
+
* rather than N silent ones inside the writer.
|
|
242
|
+
*
|
|
243
|
+
* `mode` applies to the temp file at creation and rides the rename onto the
|
|
244
|
+
* target, so an entry executed directly (the SessionStart hook runner) lands
|
|
245
|
+
* with its exec bit on a first write and a rewrite alike. As with core's
|
|
246
|
+
* `atomicWriteFile`, the requested bits are an upper bound (the process umask
|
|
247
|
+
* can narrow them) and Windows ignores them (ACL-based permissions). */
|
|
248
|
+
declare function regenerate(absPath: string, content: string, mode?: number): WriteOutcome;
|
|
243
249
|
/** Re-emit a managed region, delegating to core's `writeManagedRegion`.
|
|
244
250
|
* `regionText` MUST already include the begin/end markers (matches the shape
|
|
245
251
|
* `writeManagedRegion` expects and that `IGNORE_BLOCK`/`CONTEXT_BLOCK`
|
package/dist/index.js
CHANGED
|
@@ -328,6 +328,16 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
328
328
|
}
|
|
329
329
|
]
|
|
330
330
|
}
|
|
331
|
+
],
|
|
332
|
+
SubagentStart: [
|
|
333
|
+
{
|
|
334
|
+
hooks: [
|
|
335
|
+
{
|
|
336
|
+
type: "command",
|
|
337
|
+
command: `"${ctx.root}/.noir/hooks/noir-session-start.mjs"`
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
}
|
|
331
341
|
]
|
|
332
342
|
}
|
|
333
343
|
};
|
|
@@ -337,11 +347,12 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
337
347
|
host,
|
|
338
348
|
content: JSON.stringify(hookEntry, null, 2),
|
|
339
349
|
dedupSubstring: HOOK_DEDUP,
|
|
340
|
-
description: "SessionStart hook entry (user-owned, written once)"
|
|
350
|
+
description: "SessionStart + SubagentStart hook entry (user-owned, written once)"
|
|
341
351
|
});
|
|
342
352
|
entries.push({
|
|
343
353
|
path: ".noir/hooks/noir-session-start.mjs",
|
|
344
354
|
mode: "regenerate",
|
|
355
|
+
fileMode: 493,
|
|
345
356
|
host,
|
|
346
357
|
content: SESSION_START_HOOK_SCRIPT,
|
|
347
358
|
description: "SessionStart hook runner (Noir-owned, re-emitted)"
|
|
@@ -354,13 +365,23 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
354
365
|
template: "router.md.tmpl",
|
|
355
366
|
description: "skill router contract (co-owned managed block)"
|
|
356
367
|
});
|
|
368
|
+
if (rulesEnabled) {
|
|
369
|
+
entries.push({
|
|
370
|
+
path: ".noir/rules/anti-slop.md",
|
|
371
|
+
mode: "regenerate",
|
|
372
|
+
host,
|
|
373
|
+
template: "anti-slop.md.tmpl",
|
|
374
|
+
description: "always-on anti-slop + laziness ruleset (hook-injected, not @imported)"
|
|
375
|
+
});
|
|
376
|
+
}
|
|
357
377
|
}
|
|
358
378
|
return entries;
|
|
359
379
|
}
|
|
360
380
|
var SESSION_START_HOOK_SCRIPT = `#!/usr/bin/env node
|
|
361
|
-
// Noir SessionStart hook \u2014 injects the skill router contract
|
|
362
|
-
// The mutable contract lives in .noir/router.md (a Noir
|
|
363
|
-
// edits outside the markers survive noir sync)
|
|
381
|
+
// Noir SessionStart hook \u2014 injects the skill router contract + anti-slop ruleset
|
|
382
|
+
// at session start. The mutable contract lives in .noir/router.md (a Noir
|
|
383
|
+
// managed block, so user edits outside the markers survive noir sync) and the
|
|
384
|
+
// ruleset in .noir/rules/anti-slop.md (regenerated). This script is a pure runner.
|
|
364
385
|
import { readFileSync, existsSync } from 'node:fs';
|
|
365
386
|
import { join, dirname } from 'node:path';
|
|
366
387
|
import { fileURLToPath } from 'node:url';
|
|
@@ -368,16 +389,23 @@ import { fileURLToPath } from 'node:url';
|
|
|
368
389
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
369
390
|
const ROOT = join(HERE, '..', '..');
|
|
370
391
|
const ROUTER = join(ROOT, '.noir', 'router.md');
|
|
392
|
+
const RULES = join(ROOT, '.noir', 'rules', 'anti-slop.md');
|
|
393
|
+
|
|
394
|
+
function readIfPresent(path) {
|
|
395
|
+
if (!existsSync(path)) return '';
|
|
396
|
+
return readFileSync(path, 'utf8').trim();
|
|
397
|
+
}
|
|
371
398
|
|
|
372
399
|
function main() {
|
|
373
|
-
|
|
374
|
-
|
|
400
|
+
const parts = [readIfPresent(ROUTER), readIfPresent(RULES)].filter((part) => part !== '');
|
|
401
|
+
if (parts.length === 0) {
|
|
402
|
+
// Neither contract present (project not initialized / both removed) \u2014 silent.
|
|
375
403
|
process.stdout.write(JSON.stringify({ hookSpecificOutput: {} }));
|
|
376
404
|
process.exit(0);
|
|
377
405
|
}
|
|
378
|
-
const
|
|
406
|
+
const context = parts.join('\\n\\n');
|
|
379
407
|
process.stdout.write(
|
|
380
|
-
JSON.stringify({ hookSpecificOutput: { additionalContext:
|
|
408
|
+
JSON.stringify({ hookSpecificOutput: { additionalContext: context } }),
|
|
381
409
|
);
|
|
382
410
|
}
|
|
383
411
|
|
|
@@ -801,7 +829,7 @@ import {
|
|
|
801
829
|
import { basename, dirname as dirname3, join as join4 } from "path";
|
|
802
830
|
import { atomicWriteFile, stripManagedBlock, writeManagedRegion } from "@noir-ai/core";
|
|
803
831
|
import { ensureOwnerOnly } from "@noir-ai/core";
|
|
804
|
-
function regenerate(absPath, content) {
|
|
832
|
+
function regenerate(absPath, content, mode) {
|
|
805
833
|
const dir = dirname3(absPath);
|
|
806
834
|
const tmp = join4(
|
|
807
835
|
dir,
|
|
@@ -809,7 +837,7 @@ function regenerate(absPath, content) {
|
|
|
809
837
|
);
|
|
810
838
|
let fd;
|
|
811
839
|
try {
|
|
812
|
-
fd = openSync(tmp, "w");
|
|
840
|
+
fd = openSync(tmp, "w", mode);
|
|
813
841
|
writeSync(fd, content, 0, "utf8");
|
|
814
842
|
closeSync(fd);
|
|
815
843
|
fd = void 0;
|
|
@@ -1230,7 +1258,15 @@ function applyWithConflict(ours, theirs, path) {
|
|
|
1230
1258
|
}
|
|
1231
1259
|
|
|
1232
1260
|
// src/scaffold.ts
|
|
1233
|
-
import {
|
|
1261
|
+
import {
|
|
1262
|
+
chmodSync,
|
|
1263
|
+
existsSync as existsSync6,
|
|
1264
|
+
mkdirSync as mkdirSync3,
|
|
1265
|
+
readFileSync as readFileSync7,
|
|
1266
|
+
renameSync as renameSync2,
|
|
1267
|
+
rmSync as rmSync2,
|
|
1268
|
+
statSync
|
|
1269
|
+
} from "fs";
|
|
1234
1270
|
import { basename as basename2, dirname as dirname5, join as join8 } from "path";
|
|
1235
1271
|
import {
|
|
1236
1272
|
createProjectId,
|
|
@@ -1679,7 +1715,8 @@ async function scaffold(opts) {
|
|
|
1679
1715
|
if (entry.mode === "regenerate") {
|
|
1680
1716
|
const out = await writeWithConflict(abs, entry.path, body, opts, {
|
|
1681
1717
|
memory: conflictMemory,
|
|
1682
|
-
record: recordConflict
|
|
1718
|
+
record: recordConflict,
|
|
1719
|
+
mode: entry.fileMode
|
|
1683
1720
|
});
|
|
1684
1721
|
written.push(...out.written);
|
|
1685
1722
|
skipped.push(...out.skipped);
|
|
@@ -1894,10 +1931,14 @@ async function writeWithConflict(abs, relPath, proposed, opts, internals) {
|
|
|
1894
1931
|
existing = void 0;
|
|
1895
1932
|
}
|
|
1896
1933
|
if (existing === void 0) {
|
|
1897
|
-
regenerate(abs, proposed);
|
|
1934
|
+
regenerate(abs, proposed, internals.mode);
|
|
1898
1935
|
return { written: [relPath], skipped: [], identical: [] };
|
|
1899
1936
|
}
|
|
1900
1937
|
if (existing === proposed) {
|
|
1938
|
+
if (internals.mode !== void 0 && (statSync(abs).mode & 511) !== internals.mode) {
|
|
1939
|
+
chmodSync(abs, internals.mode);
|
|
1940
|
+
return { written: [relPath], skipped: [], identical: [] };
|
|
1941
|
+
}
|
|
1901
1942
|
return { written: [], skipped: [], identical: [relPath] };
|
|
1902
1943
|
}
|
|
1903
1944
|
const MODE = internals.artifactClass ?? "regenerate";
|
|
@@ -1919,21 +1960,21 @@ async function writeWithConflict(abs, relPath, proposed, opts, internals) {
|
|
|
1919
1960
|
internals.record(relPath, MODE, existing, proposed, resolution, sim);
|
|
1920
1961
|
switch (resolution) {
|
|
1921
1962
|
case "replace":
|
|
1922
|
-
regenerate(abs, proposed);
|
|
1963
|
+
regenerate(abs, proposed, internals.mode);
|
|
1923
1964
|
return { written: [relPath], skipped: [], identical: [] };
|
|
1924
1965
|
case "merge": {
|
|
1925
|
-
regenerate(abs, proposed);
|
|
1966
|
+
regenerate(abs, proposed, internals.mode);
|
|
1926
1967
|
return { written: [relPath], skipped: [], identical: [] };
|
|
1927
1968
|
}
|
|
1928
1969
|
case "rename": {
|
|
1929
1970
|
const aside = uniqueAside(abs, relPath, ".local");
|
|
1930
1971
|
renameSync2(abs, aside.abs);
|
|
1931
|
-
regenerate(abs, proposed);
|
|
1972
|
+
regenerate(abs, proposed, internals.mode);
|
|
1932
1973
|
return { written: [relPath], skipped: [aside.rel], identical: [] };
|
|
1933
1974
|
}
|
|
1934
1975
|
case "duplicate": {
|
|
1935
1976
|
const aside = uniqueAside(abs, relPath, ".noir");
|
|
1936
|
-
regenerate(aside.abs, proposed);
|
|
1977
|
+
regenerate(aside.abs, proposed, internals.mode);
|
|
1937
1978
|
return { written: [aside.rel], skipped: [relPath], identical: [] };
|
|
1938
1979
|
}
|
|
1939
1980
|
case "preserve":
|