@tenonhq/dovetail-core 0.0.106 → 0.0.107

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/appUtils.js CHANGED
@@ -38,30 +38,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.createAndAssignUpdateSet = exports.swapScope = exports.buildFiles = exports.summarizeRecord = exports.pushFiles = exports.getAppFileList = exports.groupAppFiles = exports.processMissingFiles = exports.refreshAllFiles = exports.findMissingFiles = exports.syncManifest = exports.processManifest = exports.normalizeManifestKeys = exports.toSafeFolderName = exports.stampMetadataContent = void 0;
40
40
  const path_1 = __importDefault(require("path"));
41
- const fs_1 = __importDefault(require("fs"));
42
41
  const progress_1 = __importDefault(require("progress"));
43
42
  const fUtils = __importStar(require("./FileUtils"));
44
43
  const ConfigManager = __importStar(require("./config"));
45
44
  const constants_1 = require("./constants");
46
45
  const PluginManager_1 = __importDefault(require("./PluginManager"));
47
46
  const FileLogger_1 = require("./FileLogger");
48
- const projectFiles_1 = require("./projectFiles");
47
+ const updateSetConfig_1 = require("./updateSetConfig");
49
48
  const snClient_1 = require("./snClient");
50
49
  const benchmark_1 = require("./benchmark");
51
50
  const Logger_1 = require("./Logger");
52
51
  const genericUtils_1 = require("./genericUtils");
53
- const getUpdateSetConfig = () => {
54
- const configPath = (0, projectFiles_1.getUpdateSetsConfigPath)();
55
- try {
56
- if (fs_1.default.existsSync(configPath)) {
57
- return JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
58
- }
59
- }
60
- catch (e) {
61
- Logger_1.logger.warn(`Failed to parse update set config at ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
62
- }
63
- return {};
64
- };
52
+ // Delegate to the shared reader so the push hot-path and the
53
+ // createUpdateSet/switchUpdateSet writers stay on one parse contract.
54
+ const getUpdateSetConfig = () => (0, updateSetConfig_1.readUpdateSetConfig)();
65
55
  // Strip the ServiceNow instance host from an absolute _record_link, leaving the
66
56
  // instance-relative path. The CADSO metadata endpoint returns an absolute URL
67
57
  // (https://<instance>.service-now.com/<table>.do?sys_id=...); persisting that
@@ -762,9 +752,11 @@ const pushFiles = async (recs) => {
762
752
  const hasUpdateSets = Object.keys(updateSetConfig).length > 0;
763
753
  if (hasUpdateSets) {
764
754
  const activeScopes = Object.entries(updateSetConfig)
765
- .map(([scope, us]) => `${scope} -> ${us.name}`)
755
+ .map(([scope, us]) => `${scope} -> ${us.name} (${us.sys_id})`)
766
756
  .join(", ");
767
- Logger_1.logger.info(`Update set routing active: ${activeScopes}`);
757
+ // Name the source file and show the sys_id so the operator can see exactly
758
+ // where captures land — push routes by this file, not the active set (#182).
759
+ Logger_1.logger.info(`Update set routing (from .dove-update-sets.json): ${activeScopes}`);
768
760
  }
769
761
  // Pre-resolve read-only table sets for every scope present in this batch.
770
762
  // Cheaper than re-resolving per record. Tables flagged in _readOnlyTables
@@ -971,6 +963,20 @@ const createAndAssignUpdateSet = async (updateSetName = "", scope) => {
971
963
  else {
972
964
  await client.createCurrentUpdateSetUserPref(updateSetSysId, userSysId);
973
965
  }
966
+ // Route this scope's captures to the new set. pushFiles() routes by
967
+ // .dove-update-sets.json, not the active set, so without this a `dove push
968
+ // --updateSet` could create+activate a set yet capture into a stale one
969
+ // (TenonHQ/Dovetail#182). Only possible when the scope name is known.
970
+ if (scope) {
971
+ const routed = (0, updateSetConfig_1.writeUpdateSetRouting)({
972
+ scope,
973
+ sysId: updateSetSysId,
974
+ name: updateSetName,
975
+ });
976
+ if (routed) {
977
+ Logger_1.logger.info(`Push routing updated: ${scope} -> ${updateSetName} (${updateSetSysId})`);
978
+ }
979
+ }
974
980
  return {
975
981
  name: updateSetName,
976
982
  id: updateSetSysId,
package/dist/snClient.js CHANGED
@@ -334,6 +334,15 @@ const snClient = (baseURL, username, password) => {
334
334
  },
335
335
  });
336
336
  };
337
+ const getScopeById = (scopeSysId) => {
338
+ const endpoint = "api/now/table/sys_scope";
339
+ return client.get(endpoint, {
340
+ params: {
341
+ sysparm_query: `sys_id=${scopeSysId}`,
342
+ sysparm_fields: "scope",
343
+ },
344
+ });
345
+ };
337
346
  const getUserSysId = (userName = process.env.SN_USER) => {
338
347
  const endpoint = "api/now/table/sys_user";
339
348
  return client.get(endpoint, {
@@ -504,6 +513,7 @@ const snClient = (baseURL, username, password) => {
504
513
  getAppList,
505
514
  updateRecord,
506
515
  getScopeId,
516
+ getScopeById,
507
517
  getUserSysId,
508
518
  getCurrentAppUserPrefSysId,
509
519
  updateCurrentAppUserPref,
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * TenonHQ/Dovetail#182: readUpdateSetConfig / writeUpdateSetRouting
4
+ *
5
+ * The push-routing file (.dove-update-sets.json) is the source of truth for
6
+ * `dove push`. These tests pin the read/merge/write contract that keeps it in
7
+ * sync with createUpdateSet / switchUpdateSet.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ var mockFsStore = {};
11
+ jest.mock("fs", function () {
12
+ return {
13
+ existsSync: jest.fn(function (p) {
14
+ return p in mockFsStore;
15
+ }),
16
+ readFileSync: jest.fn(function (p) {
17
+ if (p in mockFsStore)
18
+ return mockFsStore[p];
19
+ throw new Error("ENOENT: " + p);
20
+ }),
21
+ writeFileSync: jest.fn(function (p, data) {
22
+ mockFsStore[p] = data;
23
+ }),
24
+ };
25
+ });
26
+ var CONFIG_PATH = "/repo/.dove-update-sets.json";
27
+ jest.mock("../projectFiles", function () {
28
+ return {
29
+ getUpdateSetsConfigPath: jest.fn(function () {
30
+ return CONFIG_PATH;
31
+ }),
32
+ };
33
+ });
34
+ jest.mock("../Logger", function () {
35
+ return {
36
+ logger: {
37
+ info: jest.fn(),
38
+ error: jest.fn(),
39
+ warn: jest.fn(),
40
+ debug: jest.fn(),
41
+ success: jest.fn(),
42
+ },
43
+ };
44
+ });
45
+ const updateSetConfig_1 = require("../updateSetConfig");
46
+ const Logger_1 = require("../Logger");
47
+ describe("updateSetConfig", function () {
48
+ beforeEach(function () {
49
+ for (var k in mockFsStore)
50
+ delete mockFsStore[k];
51
+ jest.clearAllMocks();
52
+ });
53
+ describe("readUpdateSetConfig", function () {
54
+ test("returns {} when the file is absent", function () {
55
+ expect((0, updateSetConfig_1.readUpdateSetConfig)()).toEqual({});
56
+ });
57
+ test("parses an existing routing file", function () {
58
+ mockFsStore[CONFIG_PATH] = JSON.stringify({
59
+ x_cadso_core: { sys_id: "abc", name: "Set A" },
60
+ });
61
+ expect((0, updateSetConfig_1.readUpdateSetConfig)()).toEqual({
62
+ x_cadso_core: { sys_id: "abc", name: "Set A" },
63
+ });
64
+ });
65
+ test("returns {} and warns on unparseable JSON", function () {
66
+ mockFsStore[CONFIG_PATH] = "{ not json";
67
+ expect((0, updateSetConfig_1.readUpdateSetConfig)()).toEqual({});
68
+ expect(Logger_1.logger.warn).toHaveBeenCalled();
69
+ });
70
+ test("returns {} and warns when the file is a non-object (array)", function () {
71
+ mockFsStore[CONFIG_PATH] = JSON.stringify(["nope"]);
72
+ expect((0, updateSetConfig_1.readUpdateSetConfig)()).toEqual({});
73
+ expect(Logger_1.logger.warn).toHaveBeenCalled();
74
+ });
75
+ });
76
+ describe("writeUpdateSetRouting", function () {
77
+ test("writes a fresh routing entry and returns the path", function () {
78
+ var written = (0, updateSetConfig_1.writeUpdateSetRouting)({
79
+ scope: "x_cadso_core",
80
+ sysId: "new123",
81
+ name: "New Set",
82
+ });
83
+ expect(written).toBe(CONFIG_PATH);
84
+ expect(JSON.parse(mockFsStore[CONFIG_PATH])).toEqual({
85
+ x_cadso_core: { sys_id: "new123", name: "New Set" },
86
+ });
87
+ });
88
+ test("merges without clobbering other scopes", function () {
89
+ mockFsStore[CONFIG_PATH] = JSON.stringify({
90
+ x_cadso_automate: { sys_id: "keep", name: "Keep Me" },
91
+ });
92
+ (0, updateSetConfig_1.writeUpdateSetRouting)({
93
+ scope: "x_cadso_core",
94
+ sysId: "new123",
95
+ name: "New Set",
96
+ });
97
+ expect(JSON.parse(mockFsStore[CONFIG_PATH])).toEqual({
98
+ x_cadso_automate: { sys_id: "keep", name: "Keep Me" },
99
+ x_cadso_core: { sys_id: "new123", name: "New Set" },
100
+ });
101
+ });
102
+ test("overwrites the same scope's stale entry", function () {
103
+ mockFsStore[CONFIG_PATH] = JSON.stringify({
104
+ x_cadso_core: { sys_id: "old", name: "Old Set" },
105
+ });
106
+ (0, updateSetConfig_1.writeUpdateSetRouting)({
107
+ scope: "x_cadso_core",
108
+ sysId: "fresh",
109
+ name: "Fresh Set",
110
+ });
111
+ expect(JSON.parse(mockFsStore[CONFIG_PATH])).toEqual({
112
+ x_cadso_core: { sys_id: "fresh", name: "Fresh Set" },
113
+ });
114
+ });
115
+ test("returns null and writes nothing when scope is missing", function () {
116
+ var written = (0, updateSetConfig_1.writeUpdateSetRouting)({ sysId: "x", name: "y" });
117
+ expect(written).toBeNull();
118
+ expect(mockFsStore[CONFIG_PATH]).toBeUndefined();
119
+ });
120
+ test("returns null when sys_id or name is blank", function () {
121
+ expect((0, updateSetConfig_1.writeUpdateSetRouting)({ scope: "x_cadso_core", sysId: " ", name: "y" })).toBeNull();
122
+ expect((0, updateSetConfig_1.writeUpdateSetRouting)({ scope: "x_cadso_core", sysId: "x", name: "" })).toBeNull();
123
+ expect(mockFsStore[CONFIG_PATH]).toBeUndefined();
124
+ });
125
+ });
126
+ });
@@ -49,6 +49,7 @@ const commands_1 = require("./commands");
49
49
  const chalk_1 = __importDefault(require("chalk"));
50
50
  const dovetail_clickup_1 = require("@tenonhq/dovetail-clickup");
51
51
  const clickupCommands_1 = require("./clickupCommands");
52
+ const updateSetConfig_1 = require("./updateSetConfig");
52
53
  /**
53
54
  * Shows the current scope
54
55
  */
@@ -233,6 +234,26 @@ async function createUpdateSetCommand(args) {
233
234
  if (scope) {
234
235
  Logger_1.logger.info(`Scope: ${scope}`);
235
236
  }
237
+ // Keep .dove-update-sets.json in sync with the set we just activated.
238
+ // `dove push` routes captures by this file (per scope), not by the
239
+ // instance's active update set — so without this write the new set would
240
+ // stay empty and pushes would land in whatever stale set the file still
241
+ // points at (TenonHQ/Dovetail#182).
242
+ if (scope) {
243
+ const routed = (0, updateSetConfig_1.writeUpdateSetRouting)({
244
+ scope,
245
+ sysId: updateSetSysId,
246
+ name,
247
+ });
248
+ if (routed) {
249
+ Logger_1.logger.info(chalk_1.default.green(`✓ Push routing updated: ${scope} -> ${name} (${updateSetSysId})`));
250
+ }
251
+ }
252
+ else {
253
+ Logger_1.logger.warn("No scope provided, so .dove-update-sets.json was not updated. " +
254
+ "`dove push` routes captures by scope via that file — pass -s <scope> " +
255
+ "so pushes target this set, or edit .dove-update-sets.json directly.");
256
+ }
236
257
  // If created from a ClickUp task, offer to post a comment back
237
258
  if (args._clickupTaskId) {
238
259
  try {
@@ -292,6 +313,22 @@ async function switchUpdateSetCommand(args) {
292
313
  if (applicationName) {
293
314
  Logger_1.logger.info(`Scope: ${applicationName}`);
294
315
  }
316
+ // Keep .dove-update-sets.json in sync so `dove push` routes captures to the
317
+ // set we just switched to (it routes by this file per scope, not by the
318
+ // instance's active set — TenonHQ/Dovetail#182). `applicationScope` is the
319
+ // scope's sys_id, so resolve it back to the scope name the file is keyed on.
320
+ const scopeName = args.scope || (await resolveScopeName(applicationScope));
321
+ if (scopeName) {
322
+ const routed = (0, updateSetConfig_1.writeUpdateSetRouting)({ scope: scopeName, sysId, name });
323
+ if (routed) {
324
+ Logger_1.logger.info(chalk_1.default.green(`✓ Push routing updated: ${scopeName} -> ${name} (${sysId})`));
325
+ }
326
+ }
327
+ else {
328
+ Logger_1.logger.warn("Could not resolve the scope name, so .dove-update-sets.json was not " +
329
+ "updated. `dove push` routes captures by scope via that file — pass " +
330
+ "--scope <scope> so pushes target this set.");
331
+ }
295
332
  }
296
333
  catch (e) {
297
334
  Logger_1.logger.error("Failed to switch update set");
@@ -489,6 +526,30 @@ async function getActiveUpdateSetName(client, scope) {
489
526
  return null;
490
527
  }
491
528
  }
529
+ /**
530
+ * Resolves a scope's sys_id back to its scope name (e.g. "x_cadso_core"), used
531
+ * to key the .dove-update-sets.json push-routing entry. Returns undefined when
532
+ * the input is missing or the lookup fails so the caller can warn instead.
533
+ */
534
+ async function resolveScopeName(applicationSysId) {
535
+ if (!applicationSysId || applicationSysId.trim() === "")
536
+ return undefined;
537
+ try {
538
+ const client = (0, snClient_1.defaultClient)();
539
+ const rows = await (0, snClient_1.unwrapSNResponse)(client.getScopeById(applicationSysId));
540
+ if (rows && rows.length > 0) {
541
+ const resolved = rows[0].scope;
542
+ if (resolved && typeof resolved === "string" && resolved.trim() !== "") {
543
+ return resolved;
544
+ }
545
+ }
546
+ }
547
+ catch (e) {
548
+ Logger_1.logger.debug("Could not resolve scope name from application sys_id: " +
549
+ (e instanceof Error ? e.message : String(e)));
550
+ }
551
+ return undefined;
552
+ }
492
553
  /**
493
554
  * Helper function to switch to a scope
494
555
  */
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readUpdateSetConfig = readUpdateSetConfig;
7
+ exports.writeUpdateSetRouting = writeUpdateSetRouting;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const projectFiles_1 = require("./projectFiles");
10
+ const Logger_1 = require("./Logger");
11
+ /**
12
+ * Read .dove-update-sets.json (or its legacy .sinc- sibling). Returns {} when
13
+ * the file is absent or unparseable, so callers never have to guard for null.
14
+ */
15
+ function readUpdateSetConfig() {
16
+ const configPath = (0, projectFiles_1.getUpdateSetsConfigPath)();
17
+ try {
18
+ if (fs_1.default.existsSync(configPath)) {
19
+ const parsed = JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
20
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
21
+ return parsed;
22
+ }
23
+ Logger_1.logger.warn(`Update set config at ${configPath} is not an object; ignoring it.`);
24
+ }
25
+ }
26
+ catch (e) {
27
+ Logger_1.logger.warn(`Failed to parse update set config at ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
28
+ }
29
+ return {};
30
+ }
31
+ /**
32
+ * Merge a single scope's update set into .dove-update-sets.json so that
33
+ * `dove push` routes that scope's records to the set the operator just created
34
+ * or switched to.
35
+ *
36
+ * Without this, `dove createUpdateSet` / `dove switchUpdateSet` only flip the
37
+ * instance's *active* update set, while `dove push` keeps routing via this file
38
+ * — so a freshly "activated" set can stay empty and the work lands in whatever
39
+ * stale set the file still points at (TenonHQ/Dovetail#182).
40
+ *
41
+ * Returns the path written, or null when any input is missing (no scope name,
42
+ * sys_id, or set name to route by) so the caller can warn instead.
43
+ */
44
+ function writeUpdateSetRouting(opts) {
45
+ const scope = opts.scope ? opts.scope.trim() : "";
46
+ const sysId = opts.sysId ? opts.sysId.trim() : "";
47
+ const name = opts.name ? opts.name.trim() : "";
48
+ if (!scope || !sysId || !name)
49
+ return null;
50
+ const configPath = (0, projectFiles_1.getUpdateSetsConfigPath)();
51
+ const config = readUpdateSetConfig();
52
+ config[scope] = { sys_id: sysId, name };
53
+ fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
54
+ return configPath;
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-core",
3
- "version": "0.0.106",
3
+ "version": "0.0.107",
4
4
  "description": "Next-gen file syncer",
5
5
  "license": "GPL-3.0",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "node": ">=22"
23
23
  },
24
24
  "devDependencies": {
25
- "@tenonhq/dovetail-types": "~0.0.19",
25
+ "@tenonhq/dovetail-types": "~0.0.20",
26
26
  "@types/dotenv": "^6.1.1",
27
27
  "@types/inquirer": "^8.2.10",
28
28
  "@types/jest": "^29.5.5",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@tenonhq/dovetail-clickup": "~0.0.12",
42
- "@tenonhq/dovetail-schema": "~0.0.11",
42
+ "@tenonhq/dovetail-schema": "~0.0.12",
43
43
  "axios": "^1.5.1",
44
44
  "axios-cookiejar-support": "^4.0.7",
45
45
  "axios-rate-limit": "^1.3.0",
@@ -1,7 +1,196 @@
1
1
  {
2
2
  "$schema": "dovetail-manifest/v1",
3
- "generated_at": "2026-06-06T02:17:14.103Z",
3
+ "generated_at": "2026-06-16T01:18:07.747Z",
4
4
  "events": [
5
+ {
6
+ "$schema": "dovetail-release-event/v1",
7
+ "event_id": "dovetail-babel-plugin@0.0.11",
8
+ "package": "@tenonhq/dovetail-babel-plugin",
9
+ "version": "0.0.11",
10
+ "prev_version": "0.0.10",
11
+ "published_at": "2026-06-16T01:18:07.728Z",
12
+ "git": {
13
+ "sha": "edabc263bd89f08b5c7a075f270c2172e82da4da",
14
+ "tag": "dovetail-babel-plugin@0.0.11"
15
+ },
16
+ "semver_bump": "patch",
17
+ "commits": [
18
+ {
19
+ "sha": "edabc263b",
20
+ "subject": "chore(deps): bump @babel/core from 7.29.0 to 7.29.6 (#181)",
21
+ "pr": 181
22
+ },
23
+ {
24
+ "sha": "de9d77971",
25
+ "subject": "chore(release): publish 14 packages [skip ci]"
26
+ }
27
+ ],
28
+ "exports_delta": null,
29
+ "narrative": null
30
+ },
31
+ {
32
+ "$schema": "dovetail-release-event/v1",
33
+ "event_id": "dovetail-babel-plugin-remove-modules@0.0.11",
34
+ "package": "@tenonhq/dovetail-babel-plugin-remove-modules",
35
+ "version": "0.0.11",
36
+ "prev_version": "0.0.10",
37
+ "published_at": "2026-06-16T01:18:07.728Z",
38
+ "git": {
39
+ "sha": "edabc263bd89f08b5c7a075f270c2172e82da4da",
40
+ "tag": "dovetail-babel-plugin-remove-modules@0.0.11"
41
+ },
42
+ "semver_bump": "patch",
43
+ "commits": [
44
+ {
45
+ "sha": "edabc263b",
46
+ "subject": "chore(deps): bump @babel/core from 7.29.0 to 7.29.6 (#181)",
47
+ "pr": 181
48
+ },
49
+ {
50
+ "sha": "de9d77971",
51
+ "subject": "chore(release): publish 14 packages [skip ci]"
52
+ }
53
+ ],
54
+ "exports_delta": null,
55
+ "narrative": null
56
+ },
57
+ {
58
+ "$schema": "dovetail-release-event/v1",
59
+ "event_id": "dovetail-babel-preset-servicenow@0.0.11",
60
+ "package": "@tenonhq/dovetail-babel-preset-servicenow",
61
+ "version": "0.0.11",
62
+ "prev_version": "0.0.10",
63
+ "published_at": "2026-06-16T01:18:07.728Z",
64
+ "git": {
65
+ "sha": "edabc263bd89f08b5c7a075f270c2172e82da4da",
66
+ "tag": "dovetail-babel-preset-servicenow@0.0.11"
67
+ },
68
+ "semver_bump": "patch",
69
+ "commits": [
70
+ {
71
+ "sha": "edabc263b",
72
+ "subject": "chore(deps): bump @babel/core from 7.29.0 to 7.29.6 (#181)",
73
+ "pr": 181
74
+ },
75
+ {
76
+ "sha": "de9d77971",
77
+ "subject": "chore(release): publish 14 packages [skip ci]"
78
+ }
79
+ ],
80
+ "exports_delta": null,
81
+ "narrative": null
82
+ },
83
+ {
84
+ "$schema": "dovetail-release-event/v1",
85
+ "event_id": "dovetail-servicenow@0.0.21",
86
+ "package": "@tenonhq/dovetail-servicenow",
87
+ "version": "0.0.21",
88
+ "prev_version": "0.0.20",
89
+ "published_at": "2026-06-15T17:59:30.698Z",
90
+ "git": {
91
+ "sha": "51e4e4592d491cd337f5bc1aa93d04f582f555a1",
92
+ "tag": "dovetail-servicenow@0.0.21"
93
+ },
94
+ "semver_bump": "patch",
95
+ "commits": [
96
+ {
97
+ "sha": "51e4e4592",
98
+ "subject": "feat(mcp): per-call instance retarget for servicenow_query_table (#180)",
99
+ "pr": 180
100
+ },
101
+ {
102
+ "sha": "c5d9106c4",
103
+ "subject": "chore(release): publish 2 packages [skip ci]"
104
+ }
105
+ ],
106
+ "exports_delta": null,
107
+ "narrative": null
108
+ },
109
+ {
110
+ "$schema": "dovetail-release-event/v1",
111
+ "event_id": "dovetail-mcp@0.0.16",
112
+ "package": "@tenonhq/dovetail-mcp",
113
+ "version": "0.0.16",
114
+ "prev_version": "0.0.15",
115
+ "published_at": "2026-06-15T17:59:30.698Z",
116
+ "git": {
117
+ "sha": "51e4e4592d491cd337f5bc1aa93d04f582f555a1",
118
+ "tag": "dovetail-mcp@0.0.16"
119
+ },
120
+ "semver_bump": "patch",
121
+ "commits": [
122
+ {
123
+ "sha": "51e4e4592",
124
+ "subject": "feat(mcp): per-call instance retarget for servicenow_query_table (#180)",
125
+ "pr": 180
126
+ },
127
+ {
128
+ "sha": "c5d9106c4",
129
+ "subject": "chore(release): publish 2 packages [skip ci]"
130
+ }
131
+ ],
132
+ "exports_delta": null,
133
+ "narrative": null
134
+ },
135
+ {
136
+ "$schema": "dovetail-release-event/v1",
137
+ "event_id": "dovetail-servicenow@0.0.20",
138
+ "package": "@tenonhq/dovetail-servicenow",
139
+ "version": "0.0.20",
140
+ "prev_version": "0.0.19",
141
+ "published_at": "2026-06-14T00:08:21.397Z",
142
+ "git": {
143
+ "sha": "41295667a526f32eb7a5763ec1b416c19ef49bee",
144
+ "tag": "dovetail-servicenow@0.0.20"
145
+ },
146
+ "semver_bump": "patch",
147
+ "commits": [
148
+ {
149
+ "sha": "41295667a",
150
+ "subject": "chore(servicenow): release 0.0.20 — ship create-table fix to npm (#179)",
151
+ "pr": 179
152
+ }
153
+ ],
154
+ "exports_delta": null,
155
+ "narrative": null
156
+ },
157
+ {
158
+ "$schema": "dovetail-release-event/v1",
159
+ "event_id": "dovetail-mcp@0.0.15",
160
+ "package": "@tenonhq/dovetail-mcp",
161
+ "version": "0.0.15",
162
+ "prev_version": "0.0.14",
163
+ "published_at": "2026-06-14T00:08:21.397Z",
164
+ "git": {
165
+ "sha": "41295667a526f32eb7a5763ec1b416c19ef49bee",
166
+ "tag": "dovetail-mcp@0.0.15"
167
+ },
168
+ "semver_bump": "patch",
169
+ "commits": [],
170
+ "exports_delta": null,
171
+ "narrative": null
172
+ },
173
+ {
174
+ "$schema": "dovetail-release-event/v1",
175
+ "event_id": "dovetail-eslint-plugin@0.0.13",
176
+ "package": "@tenonhq/dovetail-eslint-plugin",
177
+ "version": "0.0.13",
178
+ "prev_version": "0.0.12",
179
+ "published_at": "2026-06-14T00:02:00.641Z",
180
+ "git": {
181
+ "sha": "ff9d18fd2c1a7305862cefdc942bbbf35ee155e8",
182
+ "tag": "dovetail-eslint-plugin@0.0.13"
183
+ },
184
+ "semver_bump": "patch",
185
+ "commits": [
186
+ {
187
+ "sha": "ff9d18fd2",
188
+ "subject": "docs(eslint-plugin): tighten overview wording"
189
+ }
190
+ ],
191
+ "exports_delta": null,
192
+ "narrative": null
193
+ },
5
194
  {
6
195
  "$schema": "dovetail-release-event/v1",
7
196
  "event_id": "dovetail-claude-plans@0.0.23",
@@ -1183,230 +1372,6 @@
1183
1372
  ],
1184
1373
  "exports_delta": null,
1185
1374
  "narrative": null
1186
- },
1187
- {
1188
- "$schema": "dovetail-release-event/v1",
1189
- "event_id": "dovetail-schema@0.0.9",
1190
- "package": "@tenonhq/dovetail-schema",
1191
- "version": "0.0.9",
1192
- "prev_version": "0.0.8",
1193
- "published_at": "2026-05-29T21:30:43.647Z",
1194
- "git": {
1195
- "sha": "dac76cc7b8e6a346aa54a84c7b50c442323301fb",
1196
- "tag": "dovetail-schema@0.0.9"
1197
- },
1198
- "semver_bump": "patch",
1199
- "commits": [
1200
- {
1201
- "sha": "dac76cc7b",
1202
- "subject": "feat(schema): snapshots + structured drift diff for dove schema (#122)",
1203
- "pr": 122
1204
- },
1205
- {
1206
- "sha": "9332fdf7a",
1207
- "subject": "chore(release): publish 20 packages [skip ci]"
1208
- },
1209
- {
1210
- "sha": "9046da774",
1211
- "subject": "chore(release): publish 19 packages [skip ci]"
1212
- }
1213
- ],
1214
- "exports_delta": null,
1215
- "narrative": null
1216
- },
1217
- {
1218
- "$schema": "dovetail-release-event/v1",
1219
- "event_id": "dovetail-core@0.0.95",
1220
- "package": "@tenonhq/dovetail-core",
1221
- "version": "0.0.95",
1222
- "prev_version": "0.0.94",
1223
- "published_at": "2026-05-29T21:30:43.647Z",
1224
- "git": {
1225
- "sha": "dac76cc7b8e6a346aa54a84c7b50c442323301fb",
1226
- "tag": "dovetail-core@0.0.95"
1227
- },
1228
- "semver_bump": "patch",
1229
- "commits": [
1230
- {
1231
- "sha": "dac76cc7b",
1232
- "subject": "feat(schema): snapshots + structured drift diff for dove schema (#122)",
1233
- "pr": 122
1234
- },
1235
- {
1236
- "sha": "da705804d",
1237
- "subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.33 [skip ci]"
1238
- },
1239
- {
1240
- "sha": "92213e1f8",
1241
- "subject": "chore(release): publish 2 packages [skip ci]"
1242
- },
1243
- {
1244
- "sha": "1d43f9122",
1245
- "subject": "chore(release): @tenonhq/dovetail-core@0.0.94 [skip ci]"
1246
- }
1247
- ],
1248
- "exports_delta": null,
1249
- "narrative": null
1250
- },
1251
- {
1252
- "$schema": "dovetail-release-event/v1",
1253
- "event_id": "dovetail-dashboard@0.0.33",
1254
- "package": "@tenonhq/dovetail-dashboard",
1255
- "version": "0.0.33",
1256
- "prev_version": "0.0.32",
1257
- "published_at": "2026-05-29T16:55:37.729Z",
1258
- "git": {
1259
- "sha": "3aaef1ba92364c119ece69369c5cc3379da0ed73",
1260
- "tag": "dovetail-dashboard@0.0.33"
1261
- },
1262
- "semver_bump": "patch",
1263
- "commits": [
1264
- {
1265
- "sha": "3aaef1ba9",
1266
- "subject": "fix: legible inline code in Claude Plans dark mode (#120)",
1267
- "pr": 120
1268
- },
1269
- {
1270
- "sha": "92213e1f8",
1271
- "subject": "chore(release): publish 2 packages [skip ci]"
1272
- }
1273
- ],
1274
- "exports_delta": null,
1275
- "narrative": null
1276
- },
1277
- {
1278
- "$schema": "dovetail-release-event/v1",
1279
- "event_id": "dovetail-claude-plans@0.0.20",
1280
- "package": "@tenonhq/dovetail-claude-plans",
1281
- "version": "0.0.20",
1282
- "prev_version": "0.0.19",
1283
- "published_at": "2026-05-29T02:18:11.229Z",
1284
- "git": {
1285
- "sha": "6a12c10733b64732855a8125c84f0d4846ced256",
1286
- "tag": "dovetail-claude-plans@0.0.20"
1287
- },
1288
- "semver_bump": "patch",
1289
- "commits": [
1290
- {
1291
- "sha": "6a12c1073",
1292
- "subject": "feat: claude-plans version history, feature score, selected-state + deep-link hardening (#118)",
1293
- "pr": 118
1294
- },
1295
- {
1296
- "sha": "578d6fb2a",
1297
- "subject": "chore(release): publish 2 packages [skip ci]"
1298
- }
1299
- ],
1300
- "exports_delta": null,
1301
- "narrative": null
1302
- },
1303
- {
1304
- "$schema": "dovetail-release-event/v1",
1305
- "event_id": "dovetail-dashboard@0.0.32",
1306
- "package": "@tenonhq/dovetail-dashboard",
1307
- "version": "0.0.32",
1308
- "prev_version": "0.0.31",
1309
- "published_at": "2026-05-29T02:18:11.229Z",
1310
- "git": {
1311
- "sha": "6a12c10733b64732855a8125c84f0d4846ced256",
1312
- "tag": "dovetail-dashboard@0.0.32"
1313
- },
1314
- "semver_bump": "patch",
1315
- "commits": [
1316
- {
1317
- "sha": "6a12c1073",
1318
- "subject": "feat: claude-plans version history, feature score, selected-state + deep-link hardening (#118)",
1319
- "pr": 118
1320
- },
1321
- {
1322
- "sha": "578d6fb2a",
1323
- "subject": "chore(release): publish 2 packages [skip ci]"
1324
- }
1325
- ],
1326
- "exports_delta": null,
1327
- "narrative": null
1328
- },
1329
- {
1330
- "$schema": "dovetail-release-event/v1",
1331
- "event_id": "dovetail-core@0.0.94",
1332
- "package": "@tenonhq/dovetail-core",
1333
- "version": "0.0.94",
1334
- "prev_version": "0.0.93",
1335
- "published_at": "2026-05-28T23:46:03.175Z",
1336
- "git": {
1337
- "sha": "8a76deffc38892321c92ae538fee3bc6b8f90d76",
1338
- "tag": "dovetail-core@0.0.94"
1339
- },
1340
- "semver_bump": "patch",
1341
- "commits": [
1342
- {
1343
- "sha": "8a76deffc",
1344
- "subject": "fix: filesystem-safe record folder names (sys_id fallback for Windows) (#117)",
1345
- "pr": 117
1346
- },
1347
- {
1348
- "sha": "578d6fb2a",
1349
- "subject": "chore(release): publish 2 packages [skip ci]"
1350
- },
1351
- {
1352
- "sha": "3faeec43f",
1353
- "subject": "chore(release): @tenonhq/dovetail-core@0.0.93 [skip ci]"
1354
- }
1355
- ],
1356
- "exports_delta": null,
1357
- "narrative": null
1358
- },
1359
- {
1360
- "$schema": "dovetail-release-event/v1",
1361
- "event_id": "dovetail-claude-plans@0.0.19",
1362
- "package": "@tenonhq/dovetail-claude-plans",
1363
- "version": "0.0.19",
1364
- "prev_version": "0.0.18",
1365
- "published_at": "2026-05-28T15:54:22.483Z",
1366
- "git": {
1367
- "sha": "0247ef6e75093c95359413725977df07bd5bff70",
1368
- "tag": "dovetail-claude-plans@0.0.19"
1369
- },
1370
- "semver_bump": "patch",
1371
- "commits": [
1372
- {
1373
- "sha": "0247ef6e7",
1374
- "subject": "feat(dashboard): Prompt Lints filters + merged-PR filter for Claude Plans (#116)",
1375
- "pr": 116
1376
- },
1377
- {
1378
- "sha": "5f14a3421",
1379
- "subject": "chore(release): @tenonhq/dovetail-claude-plans@0.0.18 [skip ci]"
1380
- }
1381
- ],
1382
- "exports_delta": null,
1383
- "narrative": null
1384
- },
1385
- {
1386
- "$schema": "dovetail-release-event/v1",
1387
- "event_id": "dovetail-dashboard@0.0.31",
1388
- "package": "@tenonhq/dovetail-dashboard",
1389
- "version": "0.0.31",
1390
- "prev_version": "0.0.30",
1391
- "published_at": "2026-05-28T15:54:22.483Z",
1392
- "git": {
1393
- "sha": "0247ef6e75093c95359413725977df07bd5bff70",
1394
- "tag": "dovetail-dashboard@0.0.31"
1395
- },
1396
- "semver_bump": "patch",
1397
- "commits": [
1398
- {
1399
- "sha": "0247ef6e7",
1400
- "subject": "feat(dashboard): Prompt Lints filters + merged-PR filter for Claude Plans (#116)",
1401
- "pr": 116
1402
- },
1403
- {
1404
- "sha": "c35d549c4",
1405
- "subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.30 [skip ci]"
1406
- }
1407
- ],
1408
- "exports_delta": null,
1409
- "narrative": null
1410
1375
  }
1411
1376
  ]
1412
1377
  }
@@ -62,6 +62,12 @@ Launches at `http://localhost:3456` (configurable via `DASHBOARD_PORT` in `.env`
62
62
 
63
63
  The dashboard reads scopes from `dove.config.js` and stores selections in `.dove-update-sets.json`.
64
64
 
65
+ ### How `dove push` chooses an update set (precedence)
66
+
67
+ `dove push` routes each record's capture by the per-scope map in **`.dove-update-sets.json`**, keyed on the record's scope — **not** by the instance's currently-active update set. When a scope has an entry, the push captures into that set via `pushWithUpdateSet`; when it has none, the push falls back to a plain update that lands in whatever set is active on the instance.
68
+
69
+ `createUpdateSet`, `switchUpdateSet`, and `push --updateSet` now write the affected scope's entry into `.dove-update-sets.json` as part of activating the set, so the routing file, the active set, and the push destination stay consistent. Each prints the resolved destination, e.g. `Push routing updated: x_cadso_core -> FEAT-123 (sys_id)`, and every push prints `Update set routing (from .dove-update-sets.json): <scope> -> <name> (<sys_id>)` so you can see exactly where captures land. If you edit `.dove-update-sets.json` by hand, that entry wins until you switch sets again.
70
+
65
71
  ### Multi-Scope Update Set Monitoring
66
72
 
67
73
  When using `npx dove watchAllScopes`, update set status is automatically checked every 2 minutes. It warns if any scope is using the DEFAULT update set (a common mistake that puts changes in the wrong place).