@davesheffer/hunch 1.12.0 → 1.12.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/cli/index.js CHANGED
@@ -58,6 +58,7 @@ import { isHumanConfirmed } from "../core/strictgate.js";
58
58
  import { appendEvent, readEvents } from "../core/events.js";
59
59
  import { computeStats, formatStats } from "../core/stats.js";
60
60
  import { injectionMode, resetSessionInjections } from "../core/hookcache.js";
61
+ import { recordServed, servedSummary } from "../core/served.js";
61
62
  import { contextHookOutput, denyHookOutput, hookProvider, normalizeHookEvent, stopHookOutput } from "../core/agenthook.js";
62
63
  import { PIPELINE_LOOP, UNVERIFIED_NAG, loadPipelineState, onCommand, onEdit, onPrompt, onSkill, pipelineEnabled, savePipelineState, stopVerdict, } from "../core/pipeline.js";
63
64
  import { draftDuplicateOf, isAcceptedDuplicateAnchor } from "../core/dupdetect.js";
@@ -3718,6 +3719,47 @@ program
3718
3719
  store.close();
3719
3720
  }
3720
3721
  });
3722
+ program
3723
+ .command("served")
3724
+ .description("Delivery receipts: which memory records actually reached an agent, how often, and which never have")
3725
+ .option("--json", "emit the raw ledger summary")
3726
+ .action((opts) => {
3727
+ const { store, root } = storeFor();
3728
+ try {
3729
+ const summary = servedSummary(root);
3730
+ if (opts.json) {
3731
+ console.log(JSON.stringify(summary, null, 2));
3732
+ return;
3733
+ }
3734
+ if (!summary.total) {
3735
+ console.log("No delivery receipts yet — they accrue as the pre-edit hook and subagent grounding fire on this machine.");
3736
+ return;
3737
+ }
3738
+ console.log(`\nHunch — delivery receipts (this machine)\n`);
3739
+ console.log(` ${summary.total} deliveries · ${summary.distinct_records} distinct record(s) · ${summary.distinct_sessions} session(s) · ${summary.first_at?.slice(0, 10)} → ${summary.last_at?.slice(0, 10)}\n`);
3740
+ const titleOf = (row) => {
3741
+ if (row.kind === "decisions")
3742
+ return store.recs("decisions").find((r) => r.id === row.record_id)?.title ?? row.record_id;
3743
+ if (row.kind === "constraints")
3744
+ return store.recs("constraints").find((r) => r.id === row.record_id)?.statement.slice(0, 70) ?? row.record_id;
3745
+ return row.record_id;
3746
+ };
3747
+ console.log(" Most delivered:");
3748
+ for (const row of summary.rows.slice(0, 10)) {
3749
+ console.log(` ${String(row.serves).padStart(4)}× (+${row.refreshes} still-current) ${row.record_id} — ${titleOf(row)}`);
3750
+ }
3751
+ const servedIds = new Set(summary.rows.map((r) => r.record_id));
3752
+ const neverServed = [
3753
+ ...store.recs("constraints").filter((c) => c.status === "active" && !servedIds.has(c.id)).map((c) => c.id),
3754
+ ...store.recs("decisions").filter((d) => d.status === "accepted" && !d.superseded_by && !servedIds.has(d.id)).map((d) => d.id),
3755
+ ];
3756
+ console.log(`\n Never delivered on this machine: ${neverServed.length} in-force record(s)${neverServed.length ? ` — compact candidates start here (${neverServed.slice(0, 5).join(", ")}${neverServed.length > 5 ? ", …" : ""})` : ""}`);
3757
+ console.log(` Prevented violations live in \`hunch stats\` (events ledger); receipts here are the delivery half.\n`);
3758
+ }
3759
+ finally {
3760
+ store.close();
3761
+ }
3762
+ });
3721
3763
  program
3722
3764
  .command("hook")
3723
3765
  .description("Agent-agnostic hook handler: normalizes Claude, VS Code, Cursor, Windsurf, and Antigravity events into Hunch context and strict policy checks. Reads hook JSON on stdin.")
@@ -3809,28 +3851,67 @@ program
3809
3851
  // A delegated agent starts with NONE of the parent session's grounding:
3810
3852
  // session orientation never fired inside it and only per-edit PreToolUse
3811
3853
  // follows it in — so read-only agents (Explore/Plan) could work fully
3812
- // blind. Give it the invariants that must survive delegation. Public
3813
- // store only; once per agent type per session.
3854
+ // blind. Slice by what the agent TYPE is about to do (dec_a788cc039b):
3855
+ // explorers get the indexed shape, planners get live decisions + what
3856
+ // was already rejected, everyone else gets the invariant digest. Public
3857
+ // store only; cheap reads.
3814
3858
  const s = new HunchStore(paths);
3815
3859
  try {
3816
- const sevRank = { blocking: 0, warning: 1, advisory: 2 };
3817
- const constraints = s.advisoryRecs("constraints")
3818
- .filter((c) => c.status === "active")
3819
- .sort((a, b) => sevRank[a.severity] - sevRank[b.severity]);
3820
- if (!constraints.length)
3821
- return;
3822
- const L = [`🧠 Hunch — delegated agent grounding: ${constraints.length} invariant(s) in force in this repo.`];
3823
- for (const c of constraints.slice(0, 8)) {
3824
- const flat = c.statement.replace(/\s+/g, " ").trim();
3825
- const claim = flat.length > 140 ? `${flat.slice(0, 139).trimEnd()}…` : flat;
3826
- L.push(`- [${c.severity}] ${claim}${c.scope.length ? ` (scope: ${c.scope.slice(0, 3).join(", ")})` : ""}`);
3860
+ const clip1 = (text, max) => {
3861
+ const flat = text.replace(/\s+/g, " ").trim();
3862
+ return flat.length > max ? `${flat.slice(0, max - 1).trimEnd()}…` : flat;
3863
+ };
3864
+ const type = (evt.agent_type ?? "").toLowerCase();
3865
+ const L = [];
3866
+ const served = [];
3867
+ if (/explore|search|investigat/.test(type)) {
3868
+ // Orient from the graph, not grep rounds: the component map IS the shape.
3869
+ const components = s.advisoryRecs("components").filter((c) => c.status === "active");
3870
+ if (!components.length)
3871
+ return;
3872
+ L.push(`🧠 Hunch — repo shape for a delegated explorer: ${components.length} component(s).`);
3873
+ for (const c of components.slice(0, 12)) {
3874
+ L.push(`- ${c.name}${c.paths.length ? ` (${c.paths.slice(0, 2).join(", ")})` : ""}${c.responsibility ? ` — ${clip1(c.responsibility, 90)}` : ""}`);
3875
+ served.push({ kind: "components", record_id: c.id });
3876
+ }
3877
+ if (components.length > 12)
3878
+ L.push(`…and ${components.length - 12} more — hunch_structure() for the full map.`);
3879
+ L.push("Orient: hunch_structure(target) · hunch_why(target) · hunch_context(task).");
3880
+ }
3881
+ else if (/plan|architect|design/.test(type)) {
3882
+ // A plan drafted blind re-proposes what the graph already rejected.
3883
+ const decisions = s.advisoryRecs("decisions")
3884
+ .filter((d) => d.status === "accepted")
3885
+ .sort((a, b) => (a.date < b.date ? 1 : -1));
3886
+ if (!decisions.length)
3887
+ return;
3888
+ L.push(`🧠 Hunch — live decisions for a delegated planner (${decisions.length} in force; plans must not re-propose the rejected).`);
3889
+ for (const d of decisions.slice(0, 6)) {
3890
+ L.push(`- ${d.title} (${d.id})${d.alternatives_rejected.length ? ` — rejected: ${clip1(d.alternatives_rejected[0], 80)}` : ""}`);
3891
+ served.push({ kind: "decisions", record_id: d.id });
3892
+ }
3893
+ L.push("Before finalizing a plan: hunch_why(target) · hunch_current_decision(topic) · hunch_check_constraints(scope).");
3894
+ }
3895
+ else {
3896
+ const sevRank = { blocking: 0, warning: 1, advisory: 2 };
3897
+ const constraints = s.advisoryRecs("constraints")
3898
+ .filter((c) => c.status === "active")
3899
+ .sort((a, b) => sevRank[a.severity] - sevRank[b.severity]);
3900
+ if (!constraints.length)
3901
+ return;
3902
+ L.push(`🧠 Hunch — delegated agent grounding: ${constraints.length} invariant(s) in force in this repo.`);
3903
+ for (const c of constraints.slice(0, 8)) {
3904
+ L.push(`- [${c.severity}] ${clip1(c.statement, 140)}${c.scope.length ? ` (scope: ${c.scope.slice(0, 3).join(", ")})` : ""}`);
3905
+ served.push({ kind: "constraints", record_id: c.id });
3906
+ }
3907
+ if (constraints.length > 8)
3908
+ L.push(`…and ${constraints.length - 8} more — hunch_check_constraints(scope) for your files.`);
3909
+ L.push("Before editing: hunch_check_constraints(scope) · hunch_why(target). Orient: hunch_context(task).");
3827
3910
  }
3828
- if (constraints.length > 8)
3829
- L.push(`…and ${constraints.length - 8} more — hunch_check_constraints(scope) for your files.`);
3830
- L.push("Before editing: hunch_check_constraints(scope) · hunch_why(target). Orient: hunch_context(task).");
3831
3911
  // No dedup here: the hook event carries the PARENT session id, but each
3832
3912
  // spawned agent is a fresh empty context — deduping would ground the
3833
3913
  // first Explore and silently starve every later one.
3914
+ recordServed(root, served.map((r) => ({ ...r, event: "served", target: `(subagent:${evt.agent_type ?? "any"})`, session_id: evt.session_id })));
3834
3915
  emitContext(provider, "SubagentStart", L.join("\n"));
3835
3916
  }
3836
3917
  finally {
@@ -3999,10 +4080,21 @@ program
3999
4080
  // Identical grounding already shown this session → one-line delta instead of
4000
4081
  // the full 10-16KB block. Any record change re-sends the full text; the
4001
4082
  // strict-gate deny path above never routes through this (dec_244397d920).
4083
+ // Delivery receipts (dec_925f4bcaad): the ledger of what actually reached
4084
+ // an agent. A full injection is a serve; a delta one-liner attests the
4085
+ // earlier serve is still standing. Never throws, never blocks.
4086
+ const receipts = (event) => recordServed(root, [
4087
+ ...ctx.constraints.map((c) => ({ event, kind: "constraints", record_id: c.id, target, session_id: evt.session_id })),
4088
+ ...ctx.decisions.map((d) => ({ event, kind: "decisions", record_id: d.id, target, session_id: evt.session_id })),
4089
+ ...ctx.bugs.map((b) => ({ event, kind: "bugs", record_id: b.id, target, session_id: evt.session_id })),
4090
+ ...ctx.findings.map((f) => ({ event, kind: "findings", record_id: f.id, target, session_id: evt.session_id })),
4091
+ ]);
4002
4092
  if (injectionMode(evt.session_id, `pre:${target}`, text) === "delta") {
4093
+ receipts("refreshed");
4003
4094
  emitContext(provider, "PreToolUse", `Hunch grounding for ${target}: unchanged this session (${ctx.decisions.length} decision(s), ${ctx.constraints.length} invariant(s) shown earlier — still current; hunch_why("${target}") to re-expand).`);
4004
4095
  return;
4005
4096
  }
4097
+ receipts("served");
4006
4098
  emitContext(provider, "PreToolUse", text);
4007
4099
  }
4008
4100
  catch {
@@ -28,16 +28,40 @@ function fencedRanges(text) {
28
28
  ranges.push([open.start, text.length]);
29
29
  return ranges;
30
30
  }
31
+ /** Character ranges covered by inline code spans (`…`), same rationale as
32
+ * fencedRanges: prose quoting a marker in backticks is showing an example.
33
+ * CommonMark-lite: an opener run pairs with the next run of the SAME length
34
+ * on the same line; unpaired runs never open a span. */
35
+ function inlineSpanRanges(text) {
36
+ const ranges = [];
37
+ let offset = 0;
38
+ for (const line of text.split("\n")) {
39
+ let pending = null;
40
+ const runs = /`+/g;
41
+ let m;
42
+ while ((m = runs.exec(line))) {
43
+ if (!pending)
44
+ pending = { len: m[0].length, start: m.index };
45
+ else if (m[0].length === pending.len) {
46
+ ranges.push([offset + pending.start, offset + m.index + m[0].length - 1]);
47
+ pending = null;
48
+ }
49
+ }
50
+ offset += line.length + 1;
51
+ }
52
+ return ranges;
53
+ }
31
54
  /** Parse every hunch:topic marker out of a markdown document. Markers inside
32
- * fenced code blocks are examples, not declarations, and are skipped. */
55
+ * fenced code blocks or inline code spans are examples, not declarations,
56
+ * and are skipped. */
33
57
  export function parseDocAnchors(text) {
34
58
  const out = [];
35
- const fences = fencedRanges(text);
59
+ const skip = [...fencedRanges(text), ...inlineSpanRanges(text)];
36
60
  MARKER.lastIndex = 0;
37
61
  let m;
38
62
  while ((m = MARKER.exec(text))) {
39
63
  const at = m.index;
40
- if (fences.some(([s, e]) => at >= s && at <= e))
64
+ if (skip.some(([s, e]) => at >= s && at <= e))
41
65
  continue;
42
66
  out.push({ topic: m[1], pin: m[2] ?? null, line: text.slice(0, at).split("\n").length });
43
67
  }
@@ -20,7 +20,11 @@ import { join, extname } from "node:path";
20
20
  import { parseDocAnchors } from "./docanchors.js";
21
21
  import { currentForTopic } from "./topics.js";
22
22
  import { compareCodeUnits } from "./canonicalOrder.js";
23
- export const STALE_MARKER = /\b(proposed|not yet implemented|no code yet)\b/i;
23
+ // Self-referential status declarations only. A bare "proposed" is too loose:
24
+ // the auto-generated grounding block in AGENTS.md legitimately DESCRIBES the
25
+ // proposed decision status ("candidate/proposed rules") and was graded stale
26
+ // for it — a false alarm in the machinery that polices false alarms.
27
+ export const STALE_MARKER = /\b(?:status|state)\s*[:\-—]\s*(?:proposed|draft)\b|\bnot yet implemented\b|\bno code yet\b/i;
24
28
  export const SRC_REF = /\bsrc\/[A-Za-z0-9_\-/]+\.ts\b/g;
25
29
  const SKIP_DIRS = new Set(["node_modules", ".git", ".hunch", ".hunch-private", "dist", "vscode-extension", "site"]);
26
30
  /** Bounded walk for repo markdown (root + docs/, depth-limited; heavy/irrelevant trees skipped). */
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Delivery receipts (roadmap dec_925f4bcaad): a machine-local ledger of which
3
+ * memory records were actually DELIVERED to an agent, when, and into what.
4
+ *
5
+ * This is observed telemetry, not derived state: it cannot be reconstructed
6
+ * from the JSON store, so it must NOT live in the reindex-rebuilt SQLite index
7
+ * (con_a87360128b's derived layer is dropped and rebuilt at will). It gets its
8
+ * own database under .hunch-cache/ — gitignored, per-machine, append-only —
9
+ * the same family as hookcache's session state, not the store's.
10
+ *
11
+ * Failure posture inherits the hook's: recording a receipt must never cost a
12
+ * delivery. Every entry point swallows every error; a lost receipt is noise,
13
+ * a blocked edit is a broken product.
14
+ */
15
+ import { createRequire } from "node:module";
16
+ import { mkdirSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ /** Load node:sqlite while swallowing ONLY its ExperimentalWarning — the same
19
+ * discipline as src/store/db.ts: this module rides the hook into every CLI
20
+ * invocation, and Hunch's stderr reaches humans, hooks, and MCP clients. A
21
+ * bare top-level import printed the warning on every command and failed the
22
+ * release gate's clean-stderr contract. */
23
+ function loadSqlite() {
24
+ const require = createRequire(import.meta.url);
25
+ const realEmit = process.emitWarning.bind(process);
26
+ process.emitWarning = ((warning, ...rest) => {
27
+ if (String(warning).includes("SQLite is an experimental feature"))
28
+ return;
29
+ realEmit(warning, ...rest);
30
+ });
31
+ try {
32
+ return require("node:sqlite");
33
+ }
34
+ finally {
35
+ process.emitWarning = realEmit;
36
+ }
37
+ }
38
+ let sqlite = null;
39
+ function openServedDb(root) {
40
+ sqlite ??= loadSqlite();
41
+ const dir = join(root, ".hunch-cache");
42
+ mkdirSync(dir, { recursive: true });
43
+ const db = new sqlite.DatabaseSync(join(dir, "served.db"));
44
+ db.exec(`CREATE TABLE IF NOT EXISTS served (
45
+ at TEXT NOT NULL,
46
+ session TEXT,
47
+ event TEXT NOT NULL,
48
+ kind TEXT NOT NULL,
49
+ record_id TEXT NOT NULL,
50
+ target TEXT NOT NULL
51
+ );
52
+ CREATE INDEX IF NOT EXISTS served_record ON served (record_id);`);
53
+ return db;
54
+ }
55
+ /** Append delivery receipts. Never throws — a receipt must never cost a delivery. */
56
+ export function recordServed(root, entries) {
57
+ if (!entries.length)
58
+ return;
59
+ try {
60
+ const db = openServedDb(root);
61
+ try {
62
+ const at = new Date().toISOString();
63
+ const insert = db.prepare("INSERT INTO served (at, session, event, kind, record_id, target) VALUES (?, ?, ?, ?, ?, ?)");
64
+ for (const entry of entries) {
65
+ insert.run(at, entry.session_id ?? null, entry.event, entry.kind, entry.record_id, entry.target);
66
+ }
67
+ }
68
+ finally {
69
+ db.close();
70
+ }
71
+ }
72
+ catch {
73
+ /* unwritable cache dir / locked db — the delivery already happened; drop the receipt */
74
+ }
75
+ }
76
+ /** The ledger, aggregated per record. Never throws; an unreadable ledger reads as empty. */
77
+ export function servedSummary(root) {
78
+ const empty = { total: 0, distinct_records: 0, distinct_sessions: 0, first_at: null, last_at: null, rows: [] };
79
+ try {
80
+ const db = openServedDb(root);
81
+ try {
82
+ const totals = db.prepare("SELECT COUNT(*) AS total, COUNT(DISTINCT record_id) AS records, COUNT(DISTINCT session) AS sessions, MIN(at) AS first_at, MAX(at) AS last_at FROM served").get();
83
+ const rows = db.prepare(`SELECT record_id, kind,
84
+ SUM(CASE WHEN event = 'served' THEN 1 ELSE 0 END) AS serves,
85
+ SUM(CASE WHEN event = 'refreshed' THEN 1 ELSE 0 END) AS refreshes,
86
+ MAX(at) AS last_at
87
+ FROM served GROUP BY record_id, kind ORDER BY serves DESC, refreshes DESC`).all();
88
+ return {
89
+ total: totals?.total ?? 0,
90
+ distinct_records: totals?.records ?? 0,
91
+ distinct_sessions: totals?.sessions ?? 0,
92
+ first_at: totals?.first_at ?? null,
93
+ last_at: totals?.last_at ?? null,
94
+ rows,
95
+ };
96
+ }
97
+ finally {
98
+ db.close();
99
+ }
100
+ }
101
+ catch {
102
+ return empty;
103
+ }
104
+ }
105
+ //# sourceMappingURL=served.js.map
@@ -49,6 +49,13 @@ const flushNote = (flush, home, mode) => flush === "pushed" ? ` (committed + pus
49
49
  ? " (committed to the overlay repo — push deferred: offline, no upstream, or merge conflict; the next capture or `hunch private --sync` retries)"
50
50
  : " (auto-committed to .hunch/ — rides your next push)"
51
51
  : "";
52
+ /** When an overlay exists, a PUBLIC write deserves one visible line: a record that
53
+ * lands in the committed store publishes on the next push, and an agent writing
54
+ * strategy/competitive content there is a leak nobody notices until it ships
55
+ * (2026-08-09: 15 roadmap records caught pre-push only by a release sweep). */
56
+ const publicHomeNote = (home, hasPrivate) => home === "public" && hasPrivate
57
+ ? "\nℹ Landed in the COMMITTED PUBLIC store (publishes with the repo). For sensitive/strategy content, re-record with private:true — the overlay store."
58
+ : "";
52
59
  // Read-side token budgets: every tool result is injected into a Claude Code
53
60
  // session, so an uncapped list pollutes the context window. Cap each list to its
54
61
  // highest-signal head (records are pre-sorted by severity/confidence) and tell the
@@ -903,7 +910,7 @@ export function buildServerWithRootControl(initialRoot) {
903
910
  // record commits+pushes its overlay repo; a public one commits .hunch/ in THIS repo
904
911
  // (commit only — it rides the user's next push, never auto-pushing their code branch).
905
912
  const flush = flushCapture(store, hunchPaths(root).hunch, !!decision.private, `hunch: capture ${id}`, startupTeamRoute ?? undefined);
906
- const flushed = flushNote(flush, home, store.mode);
913
+ const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate);
907
914
  // Capture-session gate (staged deprecation, §9.3): the token was consumed
908
915
  // above (it also decides the provenance tier). No token still writes
909
916
  // (non-breaking) but lands as agent_recorded with a nudge toward /capture.
@@ -980,7 +987,7 @@ export function buildServerWithRootControl(initialRoot) {
980
987
  if (home === "public" && !store.autoCommit)
981
988
  refreshExistingGrounding(root, store); // overlay rules never render into committed grounding
982
989
  const flush = flushCapture(store, hunchPaths(root).hunch, !!input.private, `hunch: capture ${rec.id}`, startupTeamRoute ?? undefined);
983
- const flushed = flushNote(flush, home, store.mode);
990
+ const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate);
984
991
  const enforce = rec.severity === "blocking"
985
992
  ? "blocks a DIRECT edit to its scope at strict firmness, and fails a PR whose diff touches that scope (CI guard); blast-radius hits and lower firmness stay advisory"
986
993
  : "flags violating edits and PRs (advisory)";
@@ -1056,7 +1063,7 @@ export function buildServerWithRootControl(initialRoot) {
1056
1063
  store.putCapture("findings", rec, !!finding.private);
1057
1064
  store.reindex();
1058
1065
  const flush = flushCapture(store, hunchPaths(root).hunch, !!finding.private, `hunch: capture ${id}`, startupTeamRoute ?? undefined);
1059
- const flushed = flushNote(flush, home, store.mode);
1066
+ const flushed = flushNote(flush, home, store.mode) + publicHomeNote(home, store.hasPrivate);
1060
1067
  const where = finding.private
1061
1068
  ? ` [PRIVATE overlay — not committed to this repo]${flushed}`
1062
1069
  : home === "private" ? ` [SHARED store — one source of truth for the whole team]${flushed}` : flushed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "1.12.0",
3
+ "version": "1.12.1",
4
4
  "mcpName": "io.github.davesheffer/hunch",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
@@ -52,6 +52,8 @@
52
52
  "node": ">=22.13.0"
53
53
  },
54
54
  "scripts": {
55
+ "version": "node tooling/sync-version-pins.mjs && git add plugin/.mcp.json server.json",
56
+ "sync-version-pins": "node tooling/sync-version-pins.mjs",
55
57
  "clean": "node --input-type=commonjs -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
56
58
  "build": "npm run clean && tsc -p tsconfig.json",
57
59
  "dev": "tsx src/cli/index.ts",
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://hunch-pi.vercel.app",
10
- "version": "1.12.0",
10
+ "version": "1.12.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@davesheffer/hunch",
16
- "version": "1.12.0",
16
+ "version": "1.12.1",
17
17
  "runtimeHint": "npx",
18
18
  "packageArguments": [
19
19
  {