net-snmp 3.27.0 → 3.28.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
@@ -1556,7 +1556,11 @@ an object, possibly empty, and can contain the following fields:
1556
1556
 
1557
1557
  The `callback` parameter is a callback function of the form
1558
1558
  `function (error, notification)`. On an error condition, the `notification`
1559
- parameter is set to `null`. On successful reception of a notification, the error
1559
+ parameter is set to `null`. Where the error was produced while processing a
1560
+ received packet - such as an authorization failure for a community or user
1561
+ not in the receiver's local authorization lists - the error will include an
1562
+ `rinfo` attribute containing the sender socket details of the offending packet.
1563
+ On successful reception of a notification, the error
1560
1564
  parameter is set to `null`, and the `notification` parameter is set as an object
1561
1565
  with the notification PDU details in the `pdu` field and the sender socket details
1562
1566
  in the `rinfo` field. For example:
@@ -1678,6 +1682,11 @@ an object, possibly empty, and can contain the following fields:
1678
1682
  * `mibOptions` - a MIB options object that is passed to the `Mib` instance - see the MIB section
1679
1683
  for further details on this - defaults to the empty object.
1680
1684
 
1685
+ As for the receiver, errors that the agent produces while processing a received
1686
+ packet - such as an authorization failure, or a request the agent cannot serve -
1687
+ include an `rinfo` attribute containing the sender socket details of the offending
1688
+ packet.
1689
+
1681
1690
  The `mib` parameter is optional, and sets the agent's singleton `Mib` instance.
1682
1691
  If not supplied, the agent creates itself a new empty `Mib` singleton. If supplied,
1683
1692
  the `Mib` instance needs to be created and populated as per the [Mib Module](#mib-module)
@@ -1724,7 +1733,9 @@ receiver's community authorization list, the receiver will not accept the notifi
1724
1733
  instead returning a error of class `RequestFailedError` to the supplied callback
1725
1734
  function. Similarly, if a v3 notification is received with a user whose name is
1726
1735
  not in the receiver's user authorization list, the receiver will return a
1727
- `RequestFailedError`. If the `disableAuthorization` option is supplied for the
1736
+ `RequestFailedError`. These errors include an `rinfo` attribute containing the
1737
+ sender socket details of the unauthorized packet, so that its origin can be
1738
+ identified. If the `disableAuthorization` option is supplied for the
1728
1739
  receiver on start-up, then these local authorization list checks are disabled for
1729
1740
  community notifications and noAuthNoPriv user notifications. Note that even with
1730
1741
  this setting, the user list is *still checked* for authNoPriv and authPriv notifications,
@@ -3762,6 +3773,18 @@ Example programs are included under the module's `example` directory.
3762
3773
 
3763
3774
  * Discard SNMPv3 responses that fall outside the USM time window, reported as the new `ResponseInvalidCode.ENotInTimeWindow`
3764
3775
 
3776
+ # Version 3.28.0 - 11/09/2026
3777
+
3778
+ * Add an `rinfo` attribute to errors that a receiver or agent produces while processing a received packet - covering unknown community and unknown user authorization failures, authentication and privacy level mismatches, digest and decryption failures, and unsupported PDU type rejections - so the sender of an offending packet can be identified
3779
+
3780
+ * Replace `.npmignore` with a `files` allowlist in `package.json`, so the published package ships only the module, its libraries, examples and reference files
3781
+
3782
+ # Version 3.28.1 - 12/09/2026
3783
+
3784
+ * Reject table instance OIDs whose row index encoding is malformed, instead of silently decoding them to a different row - a SetRequest naming a non-implied `OctetString` or OID index that claimed a different length than it supplied, or that carried unexpected trailing components, would create a row at the decoded index while answering `NoSuchInstance` for the OID that was requested
3785
+
3786
+ * Decode index parts declared with a fixed `length` without consuming a leading length component, matching the way they are encoded - an index value of `AB` in such a column previously read back as `B`
3787
+
3765
3788
  # License
3766
3789
 
3767
3790
  Copyright (c) 2020 Mark Abrahams <mark@abrahams.co.nz>
package/index.js CHANGED
@@ -3258,6 +3258,17 @@ Listener.formatCallbackData = function (pdu, rinfo) {
3258
3258
  };
3259
3259
  };
3260
3260
 
3261
+ // Returns a callback that annotates errors with the origin of the packet being
3262
+ // processed, so consumers can identify the source of unauthorized or invalid messages
3263
+ Listener.rinfoErrorCallback = function (target, rinfo) {
3264
+ return function (error, data) {
3265
+ if ( error && rinfo && ! error.rinfo ) {
3266
+ error.rinfo = rinfo;
3267
+ }
3268
+ target.callback (error, data);
3269
+ };
3270
+ };
3271
+
3261
3272
  Listener.processIncoming = function (buffer, authorizer, callback) {
3262
3273
  var message = Message.createFromBuffer (buffer);
3263
3274
  var community;
@@ -3564,12 +3575,13 @@ Receiver.prototype.getAuthorizer = function () {
3564
3575
 
3565
3576
  Receiver.prototype.onMsg = function (socket, buffer, rinfo) {
3566
3577
 
3578
+ const callback = Listener.rinfoErrorCallback (this, rinfo);
3567
3579
  let message;
3568
3580
 
3569
3581
  try {
3570
- message = Listener.processIncoming (buffer, this.authorizer, this.callback);
3582
+ message = Listener.processIncoming (buffer, this.authorizer, callback);
3571
3583
  } catch (error) {
3572
- this.callback (new ProcessingError ("Failure to process incoming message", error, rinfo, buffer));
3584
+ callback (new ProcessingError ("Failure to process incoming message", error, rinfo, buffer));
3573
3585
  return;
3574
3586
  }
3575
3587
 
@@ -3586,13 +3598,13 @@ Receiver.prototype.onMsg = function (socket, buffer, rinfo) {
3586
3598
  // The only GetRequest PDUs supported are those used for SNMPv3 discovery
3587
3599
  if ( message.pdu.type == PduType.GetRequest ) {
3588
3600
  if ( message.version != Version3 ) {
3589
- this.callback (new RequestInvalidError ("Only SNMPv3 discovery GetRequests are supported"));
3601
+ callback (new RequestInvalidError ("Only SNMPv3 discovery GetRequests are supported"));
3590
3602
  return;
3591
3603
  } else if ( message.hasAuthentication() ) {
3592
- this.callback (new RequestInvalidError ("Only discovery (noAuthNoPriv) GetRequests are supported but this message has authentication"));
3604
+ callback (new RequestInvalidError ("Only discovery (noAuthNoPriv) GetRequests are supported but this message has authentication"));
3593
3605
  return;
3594
3606
  } else if ( ! message.isReportable () ) {
3595
- this.callback (new RequestInvalidError ("Only discovery GetRequests are supported and this message does not have the reportable flag set"));
3607
+ callback (new RequestInvalidError ("Only discovery GetRequests are supported and this message does not have the reportable flag set"));
3596
3608
  return;
3597
3609
  }
3598
3610
  let reportMessage = message.createReportResponseMessage (this.engine, this.context, UsmErrorType.UNKNOWN_ENGINE_ID);
@@ -3603,16 +3615,16 @@ Receiver.prototype.onMsg = function (socket, buffer, rinfo) {
3603
3615
  // Inform/trap processing
3604
3616
  // debug (JSON.stringify (message.pdu, null, 2));
3605
3617
  if ( message.pdu.type == PduType.Trap || message.pdu.type == PduType.TrapV2 ) {
3606
- this.callback (null, this.formatCallbackData (message, rinfo) );
3618
+ callback (null, this.formatCallbackData (message, rinfo) );
3607
3619
  } else if ( message.pdu.type == PduType.InformRequest ) {
3608
3620
  message.pdu.type = PduType.GetResponse;
3609
3621
  message.buffer = null;
3610
3622
  message.setReportable (false);
3611
3623
  this.listener.send (message, rinfo, socket);
3612
3624
  message.pdu.type = PduType.InformRequest;
3613
- this.callback (null, this.formatCallbackData (message, rinfo) );
3625
+ callback (null, this.formatCallbackData (message, rinfo) );
3614
3626
  } else {
3615
- this.callback (new RequestInvalidError ("Unexpected PDU type " + message.pdu.type + " (" + PduType[message.pdu.type] + ")"));
3627
+ callback (new RequestInvalidError ("Unexpected PDU type " + message.pdu.type + " (" + PduType[message.pdu.type] + ")"));
3616
3628
  }
3617
3629
  };
3618
3630
 
@@ -4695,41 +4707,73 @@ Mib.prototype.getTableRowInstanceFromRow = function (provider, row) {
4695
4707
  return rowIndex;
4696
4708
  };
4697
4709
 
4710
+ // Splices exactly `length` components off the front of an index address,
4711
+ // rejecting encodings that claim more components than they supply.
4712
+ Mib.spliceIndexValue = function (addressRemaining, length, oid) {
4713
+ var value = addressRemaining.splice (0, length);
4714
+ if ( value.length !== length ) {
4715
+ throw new RangeError ("Index encoding in \"" + oid + "\" claims " + length +
4716
+ " components but supplies " + value.length);
4717
+ }
4718
+ return value;
4719
+ };
4720
+
4721
+ // Determines how many OID components an index part occupies, consuming a
4722
+ // leading length component for variable-length parts that are neither
4723
+ // implied nor of a fixed length.
4724
+ Mib.getIndexPartLength = function (indexPart, addressRemaining, oid) {
4725
+ var length;
4726
+ if ( indexPart.implied ) {
4727
+ return addressRemaining.length;
4728
+ }
4729
+ if ( indexPart.length ) {
4730
+ return indexPart.length;
4731
+ }
4732
+ length = Number (addressRemaining.shift ());
4733
+ if ( ! Number.isInteger (length) || length < 0 ) {
4734
+ throw new RangeError ("Index encoding in \"" + oid + "\" has a missing or invalid length component");
4735
+ }
4736
+ return length;
4737
+ };
4738
+
4698
4739
  Mib.getRowIndexFromOid = function (oid, index) {
4699
- var addressRemaining = oid.split (".");
4740
+ var addressRemaining = oid === "" ? [] : oid.split (".");
4700
4741
  var length = 0;
4701
4742
  var values = [];
4702
4743
  var value;
4703
4744
  for ( var indexPart of index ) {
4704
4745
  switch ( indexPart.type ) {
4705
4746
  case ObjectType.OID:
4706
- if ( indexPart.implied ) {
4707
- length = addressRemaining.length;
4708
- } else {
4709
- length = addressRemaining.shift ();
4710
- }
4711
- value = addressRemaining.splice (0, length);
4747
+ length = Mib.getIndexPartLength (indexPart, addressRemaining, oid);
4748
+ value = Mib.spliceIndexValue (addressRemaining, length, oid);
4712
4749
  values.push (value.join ("."));
4713
4750
  break;
4714
4751
  case ObjectType.IpAddress:
4715
4752
  length = 4;
4716
- value = addressRemaining.splice (0, length);
4753
+ value = Mib.spliceIndexValue (addressRemaining, length, oid);
4717
4754
  values.push (value.join ("."));
4718
4755
  break;
4719
4756
  case ObjectType.OctetString:
4720
- if ( indexPart.implied ) {
4721
- length = addressRemaining.length;
4722
- } else {
4723
- length = addressRemaining.shift ();
4724
- }
4725
- value = addressRemaining.splice (0, length);
4757
+ length = Mib.getIndexPartLength (indexPart, addressRemaining, oid);
4758
+ value = Mib.spliceIndexValue (addressRemaining, length, oid);
4726
4759
  value = value.map (c => String.fromCharCode(c)).join ("");
4727
4760
  values.push (value);
4728
4761
  break;
4729
4762
  default:
4730
- values.push (parseInt (addressRemaining.shift ()) );
4763
+ if ( addressRemaining.length === 0 ) {
4764
+ throw new RangeError ("Index encoding in \"" + oid + "\" is missing a component");
4765
+ }
4766
+ value = parseInt (addressRemaining.shift ());
4767
+ if ( Number.isNaN (value) ) {
4768
+ throw new RangeError ("Index encoding in \"" + oid + "\" has a non-numeric component");
4769
+ }
4770
+ values.push (value);
4731
4771
  }
4732
4772
  }
4773
+ if ( addressRemaining.length > 0 ) {
4774
+ throw new RangeError ("Index encoding in \"" + oid + "\" has " + addressRemaining.length +
4775
+ " unexpected trailing component(s)");
4776
+ }
4733
4777
  return values;
4734
4778
  };
4735
4779
 
@@ -5160,12 +5204,13 @@ Agent.prototype.tableRowStatusHandlerInternal = function (createRequest) {
5160
5204
 
5161
5205
  Agent.prototype.onMsg = function (socket, buffer, rinfo) {
5162
5206
 
5207
+ const callback = Listener.rinfoErrorCallback (this, rinfo);
5163
5208
  let message;
5164
5209
 
5165
5210
  try {
5166
- message = Listener.processIncoming (buffer, this.authorizer, this.callback);
5211
+ message = Listener.processIncoming (buffer, this.authorizer, callback);
5167
5212
  } catch (error) {
5168
- this.callback (new ProcessingError ("Failure to process incoming message", error, rinfo, buffer));
5213
+ callback (new ProcessingError ("Failure to process incoming message", error, rinfo, buffer));
5169
5214
  return;
5170
5215
  }
5171
5216
 
@@ -5200,7 +5245,7 @@ Agent.prototype.onMsg = function (socket, buffer, rinfo) {
5200
5245
  } else if ( message.pdu.type == PduType.GetBulkRequest ) {
5201
5246
  this.getBulkRequest (socket, message, rinfo);
5202
5247
  } else {
5203
- this.callback (new RequestInvalidError ("Unexpected PDU type " +
5248
+ callback (new RequestInvalidError ("Unexpected PDU type " +
5204
5249
  message.pdu.type + " (" + PduType[message.pdu.type] + ")"));
5205
5250
  }
5206
5251
  };
@@ -5284,7 +5329,15 @@ Agent.prototype.tryCreateInstance = function (varbind, requestType) {
5284
5329
  subOid = Mib.getSubOidFromBaseOid (oid, provider.oid);
5285
5330
  subAddr = subOid.split(".");
5286
5331
  column = parseInt(subAddr.shift(), 10);
5287
- row = Mib.getRowIndexFromOid(subAddr.join("."), provider.tableIndex);
5332
+ try {
5333
+ row = Mib.getRowIndexFromOid(subAddr.join("."), provider.tableIndex);
5334
+ } catch (error) {
5335
+ // The OID is not a valid index encoding for this table, so it names no
5336
+ // row and there is nothing to create. Leave the varbind to be answered
5337
+ // as NoSuchInstance.
5338
+ debug (error);
5339
+ return undefined;
5340
+ }
5288
5341
  rowStatusColumn = provider.tableColumns.reduce( (acc, current) => current.rowStatus ? current.number : acc, null );
5289
5342
 
5290
5343
  if ( requestType === PduType.SetRequest &&
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "net-snmp",
3
- "version": "3.27.0",
3
+ "version": "3.28.1",
4
4
  "description": "JavaScript implementation of the Simple Network Management Protocol (SNMP)",
5
5
  "author": "Mark Abrahams <mark@abrahams.co.nz>",
6
6
  "license": "MIT",
7
7
  "main": "index.js",
8
+ "files": [
9
+ "index.js",
10
+ "lib/",
11
+ "example/",
12
+ "ref/",
13
+ "CONTRIBUTING.md",
14
+ "README.cn.md",
15
+ "!example/test.js"
16
+ ],
8
17
  "directories": {
9
18
  "example": "example"
10
19
  },
@@ -1,54 +0,0 @@
1
- ---
2
- name: git-commit
3
- description: Creates a git commit summarizing all current working tree changes (staged and unstaged). Use when the user asks to commit, save progress, or invoke /git-commit.
4
- ---
5
-
6
- # Git Commit
7
-
8
- ## When to use
9
-
10
- - When the user asks to commit current changes.
11
- - When invoked via `/git-commit`.
12
-
13
- ## Arguments
14
-
15
- - `dry-run` — perform steps 1–3 only (gather context, identify files, draft commit message) and then **ask the user** whether to proceed with the actual commit or cancel. Do **not** stage or commit until the user confirms.
16
-
17
- ## Workflow
18
-
19
- 1. **Gather context** — run these commands in parallel:
20
- - `git status` — to see all tracked modifications and untracked files.
21
- - `git diff` and `git diff --cached` — to see unstaged and staged changes.
22
- - `git log --oneline -10` — to match the repository's commit message style.
23
-
24
- 2. **Stage all relevant changes** — add all modified and untracked files that are part of the current work. Use specific file paths rather than `git add -A`. Do **not** stage files that likely contain secrets (`.env`, credentials, etc.) — warn the user if any are present.
25
-
26
- 3. **Draft the commit message** — analyse the diff and write a concise commit message:
27
- - One summary line (imperative mood, under 72 characters) describing the *why* / *what* of the change.
28
- - If the change is non-trivial, add a blank line followed by bullet points elaborating key changes.
29
- - **Don't pad small commits**: if the summary line already captures the change, omit the body entirely. Most single-purpose commits need only the summary line.
30
- - **Stay high-level**: never reference code-level details like variable names, function names, method calls, or file paths in the commit message. Describe *what changed for the user or system*, not *which functions were modified*.
31
- - Match the style and conventions visible in the recent git log.
32
-
33
- 4. **Commit** — create the commit using a heredoc for the message:
34
- ```bash
35
- git commit -m "$(cat <<'EOF'
36
- <summary line>
37
-
38
- <optional body>
39
- EOF
40
- )"
41
- ```
42
-
43
- 5. **Verify** — run `git status` after the commit to confirm it succeeded and the working tree is clean (or shows only intentionally unstaged files).
44
-
45
- 6. **Show the commit** — run `git log -1` and display the **entire** commit message (summary line and body) to the user so they can review it. Do not truncate or abbreviate — if the message has bullet points or a multi-line body, include all of it in your response.
46
-
47
- ## Rules
48
-
49
- - Never amend an existing commit unless the user explicitly asks.
50
- - Never push to the remote unless the user explicitly asks.
51
- - Never use `--no-verify` or skip pre-commit hooks.
52
- - If a pre-commit hook fails, fix the issue, re-stage, and create a **new** commit (do not amend).
53
- - Never stage `.env`, credential files, or other secrets.
54
- - Never add `Co-Authored-By`, `Authored-By`, `Made-with` or any other attribution trailer to commit messages.
@@ -1,97 +0,0 @@
1
- ---
2
- name: prepare-release
3
- description: Prepare a release by running lint + tests, bumping the package.json version, and appending a version summary to README.md. Use when the user asks to "prepare a release", "cut a release", or invokes /prepare-release. Stops immediately on lint/test failure so the user can fix issues before continuing.
4
- ---
5
-
6
- # Prepare Release
7
-
8
- ## When to use
9
-
10
- - User asks to prepare / cut a release.
11
- - User invokes `/prepare-release`.
12
- - Immediately before the release commit — this skill stages no files and creates no commit; hand off to `/git-commit` afterwards.
13
-
14
- ## Release model
15
-
16
- The release content is the union of **(a)** commits since the last tag and **(b)** uncommitted user-facing changes in the working tree. Both are valid sources; the typical flow is that the fix being released sits uncommitted alongside the version bump and README update, and the user then commits them all together in a single release commit.
17
-
18
- Consequences:
19
- - Do **not** object to uncommitted changes in the working tree — they are expected and are part of what is being released. Treat them as release content, not as a blocker.
20
- - Do **not** require at least one committed change since the last tag. The release may consist entirely of working-tree changes that have not yet been committed.
21
- - When drafting the README bullets in step 5, summarise the user-facing changes visible across **both** sources (committed commits since the last tag **and** the diff of the working tree before this skill began modifying it).
22
- - The only changes this skill itself creates are in `package.json`, `package-lock.json`, and `README.md`. Everything else in the working tree was already the user's in-progress release content.
23
-
24
- ## Arguments
25
-
26
- - `patch` (default) — bumps the patch component, e.g. `3.26.1` → `3.26.2`.
27
- - `minor` — bumps the minor component and resets patch, e.g. `3.26.1` → `3.27.0`.
28
- - `major` — bumps the major component and resets minor/patch, e.g. `3.26.1` → `4.0.0`.
29
-
30
- If the user supplies anything else, stop and ask for clarification.
31
-
32
- ## Workflow
33
-
34
- Run steps in order. **If any step fails, stop immediately, report the failure, and do not proceed.** Do not modify `package.json`, `package-lock.json`, or `README.md` until lint and tests pass.
35
-
36
- ### 1. Pre-flight checks (parallel)
37
-
38
- - `git status --short` — capture the working tree state. Per the release model above, treat uncommitted changes as part of the release content, not as a blocker. Still surface the list to the user so they can spot anything that looks unintended (e.g. a modified file they don't remember touching) before the eventual release commit.
39
- - `git rev-parse --abbrev-ref HEAD` — confirm the current branch. If it is not `master`, warn the user and ask whether to continue.
40
- - `git log --oneline $(git describe --tags --abbrev=0 2>/dev/null || git log --format=%H | tail -1)..HEAD` — collect commits since the last tag (or since the beginning if no tags exist). These feed the README summary in step 5 alongside the working-tree diff.
41
- - `git diff` and `git diff --cached` — capture the uncommitted user-facing changes. These also feed the README summary in step 5.
42
- - Read current version from `package.json`.
43
-
44
- Only stop at pre-flight if **both** the commit list and the uncommitted diff are empty — in that case there is genuinely nothing to release.
45
-
46
- ### 2. Lint
47
-
48
- Run `npm run lint`. **Stop on non-zero exit.** Report the linter output verbatim and do not continue.
49
-
50
- ### 3. Tests
51
-
52
- Run `npm test`. **Stop on non-zero exit.**
53
-
54
- Exception: the `Subagent` tests in `test/subagent.test.js` require a locally-running AgentX master (`snmpd`) and will fail with `ECONNREFUSED` if one is not present. If the only failures are Subagent `ECONNREFUSED` failures, report them and ask the user whether to proceed (they may have intentionally skipped running snmpd). All other failures are hard stops.
55
-
56
- ### 4. Bump version
57
-
58
- Run `npm version <patch|minor|major> --no-git-tag-version`. This updates both `package.json` and `package-lock.json` atomically without creating a commit or tag. Capture the new version string for the README entry.
59
-
60
- If the argument was omitted, default to `patch`.
61
-
62
- ### 5. Append README version summary
63
-
64
- - Read `README.md`.
65
- - Locate the `# License` heading near the end.
66
- - Insert a new version section **immediately before** `# License`, separated by a blank line above and below, in exactly this format:
67
-
68
- ```
69
- # Version X.Y.Z - DD/MM/YYYY
70
-
71
- * <concise user-facing summary of change 1>
72
-
73
- * <concise user-facing summary of change 2>
74
- ```
75
-
76
- Notes:
77
- - Date is **today's date** in `DD/MM/YYYY` (day/month/year) — use the date from the environment, not a hardcoded value.
78
- - Each bullet begins with a single space, then `*`, then a space. There is a blank line between bullets (match the style of the existing entries).
79
- - Bullets describe **user-visible changes** — not refactors, lint fixes, test additions, or internal tidy-ups. Write them like release notes, not commit subjects. Draw from **both** the commit list gathered in step 1 and the uncommitted working-tree diff; merge/reword where helpful so each line stands on its own.
80
- - If there is only one user-visible change, include just one bullet.
81
- - Do not touch any other part of `README.md`.
82
-
83
- ### 6. Summary
84
-
85
- Report to the user, concisely:
86
- - New version number.
87
- - Bullets added to `README.md`.
88
- - The full list of uncommitted files now staged for the release commit — both the pre-existing working-tree changes and the three files this skill modified (`package.json`, `package-lock.json`, `README.md`). Flag anything that looks unexpected.
89
-
90
- Do **not** run `git add`, `git commit`, `git tag`, or `npm publish` — those are the user's decision. The next step is typically `/git-commit`.
91
-
92
- ## Things to watch for
93
-
94
- - If `npm version` fails because the working tree is dirty, it has not been run with `--no-git-tag-version` correctly — re-check the command.
95
- - If the README insertion point (`# License`) cannot be found, stop and ask the user — do not guess.
96
- - If the lint or test commands are missing from `package.json`, stop and report.
97
- - Never bypass a failing check with `--no-verify`, `SKIP=`, or similar; the user must fix the underlying issue.
@@ -1,111 +0,0 @@
1
- ---
2
- name: propose-action
3
- description: Review a GitHub issue or PR by number and propose a concrete action plan — either a reasoned recommendation not to action it, or the code and test changes required to action it. Use when the user asks to "propose an action" for an issue/PR, "look at issue/PR #N", or invokes /propose-action with an issue or PR number. Ends by asking whether to proceed, proceed with modifications, or abandon.
4
- ---
5
-
6
- # Propose Action
7
-
8
- ## When to use
9
-
10
- - The user asks to review a GitHub issue or PR and recommend what to do about it.
11
- - The user invokes `/propose-action <number>`.
12
-
13
- This skill **plans only** for steps 1–5 — it does not edit code, run tests, commit, push, or comment on the issue/PR during the proposal phase. Implementation happens after the user approves the plan in step 5's closing question. Once the user has approved and you have carried out the agreed actions, step 6 produces a ready-to-paste reply to the issue/PR aligned with what was actually done — but never posts it.
14
-
15
- ## Arguments
16
-
17
- - `<number>` (required) — the GitHub issue or PR number. Bare integer, or `#N`, or a full GitHub URL are all acceptable forms.
18
-
19
- If the number is missing, ambiguous, or does not resolve to an issue/PR in the current repository, stop and ask the user to clarify.
20
-
21
- ## Workflow
22
-
23
- ### 1. Identify the target
24
-
25
- - Determine the current repository: `gh repo view --json nameWithOwner -q .nameWithOwner`.
26
- - Resolve the number to either an issue or a PR. GitHub numbers them in the same namespace, so try PR first, then issue:
27
- - `gh pr view <number> --json number,title,state,author,body,headRefName,baseRefName,isDraft,mergeable,additions,deletions,changedFiles,labels,comments,reviews`
28
- - If that fails, `gh issue view <number> --json number,title,state,author,body,labels,comments,assignees`
29
- - Record whether the target is an **issue** or a **PR** — the two branches below differ.
30
-
31
- ### 2a. If the target is an issue
32
-
33
- Gather context in parallel:
34
- - The issue body and all comments (from the `gh issue view` JSON above).
35
- - `gh issue view <number> --json linkedPullRequests` — check for linked PRs that may already address it.
36
- - Identify the files or subsystems the issue points at. Search the repo (`Grep`, `Glob`, `Read`) for the relevant code. Do not guess — read the actual files the issue describes.
37
- - If the issue references specific OIDs, error messages, function names, or SNMP behaviours, locate them in the source.
38
-
39
- ### 2b. If the target is a PR
40
-
41
- Gather context in parallel:
42
- - PR body, comments, and reviews (from the `gh pr view` JSON above).
43
- - `gh pr diff <number>` — the full diff.
44
- - `gh pr view <number> --json files -q '.files[].path'` — changed files.
45
- - Read the **current** version of each changed file on the base branch so you understand what the PR is replacing, not just what it adds.
46
- - If the PR closes or references an issue (via `Fixes #N`, `Closes #N`, etc.), also fetch that issue for context.
47
- - Check CI status if available: `gh pr checks <number>`.
48
-
49
- ### 3. Form a judgement
50
-
51
- Decide which of these recommendations fits, and be willing to recommend **against** actioning:
52
-
53
- - **Do not action** — the issue is invalid, already fixed, out of scope, based on a misunderstanding, a duplicate, or the PR is the wrong approach / would regress behaviour / conflicts with project direction. Explain *why* clearly, citing the code or prior commits that support the conclusion.
54
- - **Action as-is** — the issue is valid and the fix is clear, or the PR is correct and should be merged as submitted.
55
- - **Action with modifications** — the issue is valid but the obvious fix is wrong; or the PR has the right idea but needs specific changes before it can be merged.
56
- - **Needs more information** — the report is plausible but underspecified. List the exact questions to ask the reporter / author before any code change is possible.
57
-
58
- Base the judgement on what the code actually does today, not on what the issue/PR claims. If the two disagree, surface the disagreement.
59
-
60
- ### 4. Draft the proposal
61
-
62
- Structure the written proposal in this order. Keep it concrete — name files, line numbers, function names, and the specific behaviours being changed.
63
-
64
- 1. **Target** — `Issue #N: <title>` or `PR #N: <title>`, plus one sentence on its state (open/closed/draft/merged, CI status for PRs).
65
- 2. **Summary of the report** — one short paragraph in your own words describing what the reporter is asking for or proposing. Do not just quote the body.
66
- 3. **Findings** — what the code currently does, whether the report is accurate, and any related context (linked issues/PRs, prior commits, existing tests covering this area). Cite `file:line` locations.
67
- 4. **Recommendation** — one of the four options from step 3, stated plainly in a sentence or two.
68
- 5. **Proposed changes** (omit if the recommendation is *do not action* or *needs more information*):
69
- - **Code** — for each file to change, list the edit as a bullet: `path/to/file.js:<line-range> — <what changes and why>`. For non-trivial edits, include a short before/after sketch (a few lines, not a full diff).
70
- - **Tests** — list tests to **add**, **modify**, or **delete**, each with the file and a one-line description of what the test asserts. If no test changes are needed, say so explicitly and justify (e.g. "covered by existing test at `test/x.test.js:42`").
71
- - **Docs / README** — note any README or inline-doc updates required. Release-note bullets are handled by `/prepare-release`; do not pre-empt them here.
72
- 6. **Risks and unknowns** — anything that could make the plan wrong: behaviours you could not verify, edge cases the tests would not catch, compatibility concerns, or assumptions about the reporter's environment.
73
- 7. **Out of scope** — closely-related issues you noticed while investigating but are **not** proposing to fix in this action. Keeps the scope honest.
74
-
75
- ### 5. Closing question
76
-
77
- End the response with exactly one question, offering three choices:
78
-
79
- > Proceed with this plan as-is, proceed with modifications (tell me what to change), or abandon it?
80
-
81
- Do not start implementing until the user answers. If the user says "proceed", begin implementing the plan in the next turn. If the user asks for modifications, revise the proposal and ask the same question again. If the user says to abandon, stop and skip step 6.
82
-
83
- ### 6. Ready-to-paste issue/PR reply
84
-
85
- After the agreed actions have been carried out (code edits, tests, docs, or a reasoned decision not to action), produce a single ready-to-paste reply for the issue/PR. This is the **last** thing you output, rendered in a fenced block or delimited by `---` so the user can copy it cleanly.
86
-
87
- The reply must be aligned with **what actually happened**, not with the original plan — if the user asked for modifications during implementation, reflect the modified outcome. If a step was skipped or failed, say so honestly; do not describe work that was not done.
88
-
89
- Tailor the content to the recommendation from step 3:
90
-
91
- - **Action as-is / with modifications** — acknowledge the report, give the user a short, concrete summary of the fix (with the key code change or snippet inline if it helps them adopt it), point at the files/sections that changed (e.g. "updated in `README.md` under *Scalar providers*"), and note when it will ship (e.g. "will land in the next release" — do not invent a version number unless `package.json` has already been bumped). If there's a workaround the reporter can apply before the release, include it.
92
- - **Do not action** — explain the reasoning in the reporter's terms, citing the code or prior history that supports the decision. Be respectful: the reporter put effort into the report. Offer an alternative path if one exists (a different API, a related issue, a config change). If the issue should be closed, say so; do not close it yourself.
93
- - **Needs more information** — list the specific questions you need answered, each as its own bullet. Explain briefly *why* each is needed so the reporter can see what would unblock progress.
94
-
95
- Style:
96
-
97
- - Match the tone of prior replies in this repository if they are visible in the issue/PR thread — don't suddenly adopt a different register.
98
- - Write as the maintainer would: first person, conversational, no corporate voice, no marketing copy, no emoji unless the repo's existing replies use them.
99
- - Do not include a signature, do not add attribution trailers (no "generated by", no `Co-Authored-By`), do not tag the reporter more than once.
100
- - Keep it focused — one screen of text is almost always enough. If the reply needs code blocks, prefer short, complete snippets over long diffs.
101
-
102
- Hand the reply to the user with one sentence above it saying where to paste it (e.g. "Ready-to-paste reply for issue #297:"). Do **not** run `gh issue comment` / `gh pr comment` / `gh issue close` / `gh pr merge` — the user posts it.
103
-
104
- ## Rules
105
-
106
- - **Never** edit files, run tests, or modify git state during steps 1–5. Planning only.
107
- - **Never** post a comment on the issue/PR, add labels, request reviews, merge, or close — not in step 6, not ever within this skill. Those are the user's decision.
108
- - **Never** fabricate line numbers or function names — if you cite `file:line`, you must have read it.
109
- - If `gh` is not authenticated or the number does not exist, stop and report the error verbatim.
110
- - If the issue/PR is in a different repository than the current working directory, stop and confirm with the user before proceeding — the plan would target the wrong codebase.
111
- - Keep the proposal focused. A plan that touches ten files for a one-line bug is a signal to re-read the issue, not to write a bigger plan.
@@ -1,68 +0,0 @@
1
- ---
2
- name: publish-release
3
- description: Publish the currently-committed release by pushing master to origin, publishing to npm, tagging the commit with the package.json version, and pushing the tag. Use when the user asks to "publish the release", "push and publish", or invokes /publish-release. Stops immediately on any failure.
4
- ---
5
-
6
- # Publish Release
7
-
8
- ## When to use
9
-
10
- - After `/prepare-release` and the release commit have both been completed — this skill assumes the release commit (version bump + README entry + fix) is already `HEAD` on `master`.
11
- - User asks to publish / ship / release to npm.
12
- - User invokes `/publish-release`.
13
-
14
- Run after `/prepare-release` + `/git-commit`, never in place of them. This skill does not run lint, tests, or modify any files.
15
-
16
- ## Workflow
17
-
18
- Run steps in order. **If any step fails, stop immediately, report the failure, and do not proceed.** There is no rollback — each step is externally observable (a push, a publish, a tag on origin), so a mid-flight failure leaves the release in a partial state the user will need to reason about. Be explicit about which steps succeeded and which did not.
19
-
20
- ### 1. Pre-flight checks (parallel)
21
-
22
- - `git rev-parse --abbrev-ref HEAD` — confirm the current branch is `master`. If not, stop and ask the user.
23
- - `git status --short` — confirm the working tree is clean. If not, stop — the release commit must already exist as `HEAD`, with nothing else pending.
24
- - Read the current version from `package.json` (this becomes the tag name).
25
- - `git tag --list <version>` — confirm the tag does not already exist locally. If it does, stop.
26
- - `git ls-remote --tags origin refs/tags/<version>` — confirm the tag does not already exist on the remote. If it does, stop.
27
- - `git log -1 --format=%s` — capture the subject of the HEAD commit for the summary.
28
-
29
- ### 2. Push master to origin
30
-
31
- Run `git push origin master`. **Stop on non-zero exit.** Report the output.
32
-
33
- ### 3. Publish to npm
34
-
35
- Run `npm publish`. **Stop on non-zero exit.**
36
-
37
- Notes:
38
- - If the registry prompts for a one-time password (2FA OTP), relay the prompt to the user and wait for their input. Do not attempt to bypass 2FA.
39
- - If the registry rejects with `403` or `You do not have permission`, stop and surface the full error — the user may need to `npm login` or check their credentials.
40
- - If the registry rejects with `You cannot publish over the previously published versions`, stop — the version was not bumped correctly upstream of this skill.
41
-
42
- ### 4. Tag the release
43
-
44
- Tags in this repo are bare version numbers (no `v` prefix) — verified against the existing tag list.
45
-
46
- Run `git tag <version>` where `<version>` is the exact value read from `package.json` in step 1 (e.g., `3.26.2`, not `v3.26.2`).
47
-
48
- **Stop on non-zero exit.**
49
-
50
- ### 5. Push the tag
51
-
52
- Run `git push origin <version>`. **Stop on non-zero exit.**
53
-
54
- ### 6. Summary
55
-
56
- Report to the user, concisely:
57
- - The version that was published.
58
- - Confirmation of: master pushed, npm publish succeeded, tag created, tag pushed.
59
- - The release commit subject (from pre-flight).
60
- - The npm package URL, if it can be derived from `package.json` name (`https://www.npmjs.com/package/<name>`).
61
-
62
- ## Things to watch for
63
-
64
- - If step 2 (push master) succeeds but step 3 (npm publish) fails, the commit is on GitHub but not on npm. Do not tag — the user must either resolve the npm issue and re-run publish, or roll back the push.
65
- - If step 3 (npm publish) succeeds but step 4 (tag) or step 5 (tag push) fails, the package is on npm but not tagged. Surface this clearly — the user needs to manually create / push the tag, and an un-tagged published release is a fix-it-now situation, not something to leave for later.
66
- - Never use `--force`, `--no-verify`, or any flag that bypasses checks, even to recover from a mid-flight failure. Always ask the user.
67
- - Never create or push tags for versions other than the one in `package.json`.
68
- - Never run this skill on a branch other than `master` without explicit user confirmation.
@@ -1 +0,0 @@
1
- github: [markabrahams]
package/.travis.yml DELETED
@@ -1,13 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - lts/*
4
- - 20
5
- - 18
6
- - 16
7
- - 14
8
- - 12
9
- - 10
10
- - 8
11
-
12
- script:
13
- - npm run lint