sign-off-axi 0.3.0 → 0.3.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/README.md CHANGED
@@ -202,6 +202,8 @@ npm run build:skill # regenerates skills/sign-off-axi/SKILL.md from sr
202
202
  npm pack # builds the publishable sign-off-axi tarball
203
203
  ```
204
204
 
205
+ A release is a merged version-bump PR: CI publishes the new version to npm.
206
+
205
207
  ### Test runs never touch your board
206
208
 
207
209
  With no board named, a bare command uses the nearest `.sign-off/` that `init` set up, looking from the working folder up to its repo root (the folder holding `.git`) or up to HOME, but never HOME's own `.sign-off/`. The working folder's own `.sign-off/` is always used. A folder above it is searched only when that climb reaches a repo root or HOME, so a `.sign-off/` above a folder with neither (outside HOME, no `.git`) is never used, and the account's own home is never used. Otherwise the default board is `~/.sign-off/decisions.json`, under `SIGN_OFF_HOME` when it is set (it takes HOME's place for the default board and for where the climb stops), else under HOME.
package/dist/cli.js CHANGED
@@ -565,7 +565,7 @@ function post(args, cmd = 'post') {
565
565
  const size = statSync(actual);
566
566
  if (!size.isFile() || size.size > MOCK_BYTES)
567
567
  throw fail(`--view ${values.view} must be a file of 5 MB or less`, 'VALIDATION_ERROR');
568
- return storeIncoming(file, on, buildIncoming(values, { type, view: values.view }, filer), filer);
568
+ return storeIncoming(file, on, buildIncoming(values, { type, view: values.view }, filer, file), filer);
569
569
  }
570
570
  const mocks = values.mock?.map((m) => {
571
571
  const at = m.indexOf('=');
@@ -578,7 +578,7 @@ function post(args, cmd = 'post') {
578
578
  throw fail(`--mock ${path} escapes the board folder`, 'VALIDATION_ERROR', 'Keep mock files next to the board file');
579
579
  return { value: m.slice(0, at), path };
580
580
  });
581
- return storeIncoming(file, on, buildIncoming(values, { type, tradeoff: values.tradeoff, mocks }, filer), filer);
581
+ return storeIncoming(file, on, buildIncoming(values, { type, tradeoff: values.tradeoff, mocks }, filer, file), filer);
582
582
  }
583
583
  /** For a program that feeds the board: the --from file, checked like any post, becomes the whole board, items and top-level fields. Answers stay.
584
584
  * With a filer it replaces only that filer's items (and items with no filer it names by key); every other item and top-level field stays. */
@@ -669,7 +669,7 @@ function postMedia(file, on, values, type, filer) {
669
669
  });
670
670
  mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
671
671
  return locked(`${file}.lock`, () => {
672
- const items = existsSync(file) ? readItems(file).items : [];
672
+ const { items, extra } = existsSync(file) ? readItems(file) : { items: [], extra: {} };
673
673
  // Boards in one folder share evidence/, so a number is taken only by creating its folder; one in use is never written into.
674
674
  const current = board(file), store = join(dirname(file), 'evidence'), taken = current.map((d) => Number(/^update-(\d+)$/.exec(d.key)?.[1] ?? 0));
675
675
  const own = explainer && (values.key ?? slug(values.title));
@@ -698,7 +698,7 @@ function postMedia(file, on, values, type, filer) {
698
698
  : { key, type, number, title, about, detail, close, link, media, filer, filed });
699
699
  // The whole board is checked before it is written, so a bad post leaves the board and evidence/ as they were.
700
700
  const check = `${file}.${process.pid}.check`;
701
- writeFileSync(check, JSON.stringify({ schema: 2, items }), { mode: 0o600 });
701
+ writeFileSync(check, JSON.stringify({ ...extra, schema: 2, items }), { mode: 0o600 });
702
702
  try {
703
703
  loadDecisions(check);
704
704
  }
@@ -810,13 +810,14 @@ function recommended(items, flags) {
810
810
  if (d)
811
811
  throw fail(`items[${items.indexOf(d)}] (key ${d.key}) lists options but no recommend_value`, 'VALIDATION_ERROR', `Add "recommend_value": "<value>", the option you would pick: one of ${values}`);
812
812
  }
813
- /** Flags are validated by the same loader as a file: write them as a one-decision file first. */
814
- function buildIncoming(values, extra = {}, filer, file = '') {
813
+ /** Flags are validated by the same loader as a file: write them as a one-decision file first. Items are checked against the board they go to, so its answers_only or readonly holds. */
814
+ function buildIncoming(values, extra, filer, file) {
815
+ const board = existsSync(file) ? readItems(file).extra : {};
815
816
  if (values.from !== undefined) {
816
817
  // An item that names its own filer keeps it.
817
818
  const source = resolve(values.from), raw = readItems(source).items;
818
819
  contractItems(raw, values.from, file);
819
- const incoming = loadDecisions(source);
820
+ const incoming = loadDecisions(source, board);
820
821
  recommended(raw, false);
821
822
  return { incoming: incoming.map((d) => ({ filer, ...d })), raw: raw.map((i) => ({ filer, ...i })) };
822
823
  }
@@ -825,7 +826,7 @@ function buildIncoming(values, extra = {}, filer, file = '') {
825
826
  try {
826
827
  const { title, key = slug(title), about, detail, recommend: recommend_value, default: default_if_silent, close } = values;
827
828
  writeFileSync(source, JSON.stringify({ schema: 2, items: [{ key, title, about, detail, options: optionsOf(values), recommend_value, default_if_silent, close, ...extra, filer }] }));
828
- const incoming = loadDecisions(source), raw = readItems(source).items;
829
+ const incoming = loadDecisions(source, board), raw = readItems(source).items;
829
830
  recommended(raw, true);
830
831
  return { incoming, raw };
831
832
  }
package/dist/store.js CHANGED
@@ -56,8 +56,9 @@ export function writeItems(path, items, extra) {
56
56
  writeFileSync(tmp, `${JSON.stringify({ ...(extra ?? old?.extra), schema: 2, items }, null, 2)}\n`, { mode: 0o600 });
57
57
  renameSync(tmp, path);
58
58
  }
59
- export function loadDecisions(path) {
60
- const name = basename(path), { schema, items, extra } = readItems(path), list = schema === 1 ? 'decisions' : 'items';
59
+ /** `board`: the top-level fields of the board the items are posted to, which decide in place of the file's own. */
60
+ export function loadDecisions(path, board) {
61
+ const name = basename(path), { schema, items, extra: own } = readItems(path), list = schema === 1 ? 'decisions' : 'items', extra = board ?? own;
61
62
  const fed = extra.readonly === true || extra.answers_only === true;
62
63
  const seen = new Map();
63
64
  return items.map((d, i) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sign-off-axi",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "One review board for evidence, quick yes/no decisions and retro changes.",
5
5
  "type": "module",
6
6
  "bin": { "sign-off-axi": "dist/cli.js" },