@tenonhq/dovetail-core 0.0.94 → 0.0.96

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/commander.js CHANGED
@@ -334,10 +334,10 @@ async function initCommands() {
334
334
  }, async (args) => {
335
335
  await (0, dashboardCommand_1.dashboardCommand)(args);
336
336
  })
337
- .command("schema <subcommand>", "Manage ServiceNow table schemas (subcommands: pull)", (cmdArgs) => {
337
+ .command("schema <subcommand>", "Manage ServiceNow table schemas (subcommands: pull, diff, snapshots)", (cmdArgs) => {
338
338
  cmdArgs.positional("subcommand", {
339
339
  describe: "Schema subcommand to run",
340
- choices: ["pull"],
340
+ choices: ["pull", "diff", "snapshots"],
341
341
  });
342
342
  cmdArgs.options({
343
343
  ...sharedOptions,
@@ -349,7 +349,24 @@ async function initCommands() {
349
349
  scope: {
350
350
  alias: "s",
351
351
  type: "string",
352
- describe: "Pull schema for a single scope (default: all scopes from dove.config.js)",
352
+ describe: "Single scope (default: all scopes from dove.config.js)",
353
+ },
354
+ snapshot: {
355
+ type: "string",
356
+ describe: "pull: also save an immutable snapshot with this label (timestamp-only if no label)",
357
+ },
358
+ from: {
359
+ type: "string",
360
+ describe: "diff: baseline ref — a snapshot label, snapshot dir name, or directory path (default: newest snapshot)",
361
+ },
362
+ to: {
363
+ type: "string",
364
+ describe: 'diff: comparison ref — "live" for a fresh pull, or a snapshot ref (default: live)',
365
+ },
366
+ format: {
367
+ type: "string",
368
+ choices: ["text", "json"],
369
+ describe: "diff: output format (default: text)",
353
370
  },
354
371
  });
355
372
  return cmdArgs;
@@ -357,6 +374,12 @@ async function initCommands() {
357
374
  if (args.subcommand === "pull") {
358
375
  await (0, schemaCommand_1.schemaPullCommand)(args);
359
376
  }
377
+ else if (args.subcommand === "diff") {
378
+ await (0, schemaCommand_1.schemaDiffCommand)(args);
379
+ }
380
+ else if (args.subcommand === "snapshots") {
381
+ await (0, schemaCommand_1.schemaSnapshotsCommand)(args);
382
+ }
360
383
  })
361
384
  .command("init-claude", "Install Dovetail Claude Code skills to .claude/commands/", (cmdArgs) => {
362
385
  cmdArgs.options({
@@ -37,32 +37,46 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.schemaPullCommand = schemaPullCommand;
40
+ exports.schemaDiffCommand = schemaDiffCommand;
41
+ exports.schemaSnapshotsCommand = schemaSnapshotsCommand;
40
42
  const dovetail_schema_1 = require("@tenonhq/dovetail-schema");
41
43
  const Logger_1 = require("./Logger");
42
44
  const ConfigManager = __importStar(require("./config"));
43
45
  const path_1 = __importDefault(require("path"));
44
- async function schemaPullCommand(args) {
46
+ const os_1 = __importDefault(require("os"));
47
+ const fs_1 = require("fs");
48
+ function requireCreds() {
45
49
  const { SN_USER = "", SN_PASSWORD = "", SN_INSTANCE = "" } = process.env;
46
50
  if (!SN_USER || !SN_PASSWORD || !SN_INSTANCE) {
47
51
  throw new Error("Missing ServiceNow credentials. Ensure SN_INSTANCE, SN_USER, and SN_PASSWORD are set in your .env file or environment.");
48
52
  }
53
+ return { SN_USER, SN_PASSWORD, SN_INSTANCE };
54
+ }
55
+ function normalizeInstance(instance) {
56
+ return instance.replace(/^https?:\/\//, "").replace(/\/$/, "");
57
+ }
58
+ function resolveScopes(args) {
49
59
  const config = ConfigManager.getConfig();
50
60
  const configScopes = config.scopes ? Object.keys(config.scopes) : [];
51
61
  if (configScopes.length === 0) {
52
62
  throw new Error("No scopes configured in dove.config.js. Add scopes to the 'scopes' object in your configuration.");
53
63
  }
54
- // If --scope flag is provided, filter to that single scope
55
- let scopes = configScopes;
56
64
  if (args.scope) {
57
65
  if (!configScopes.includes(args.scope)) {
58
66
  throw new Error(`Scope "${args.scope}" is not configured in dove.config.js. Available scopes: ${configScopes.join(", ")}`);
59
67
  }
60
- scopes = [args.scope];
68
+ return [args.scope];
61
69
  }
70
+ return configScopes;
71
+ }
72
+ function outputDirFor(args) {
62
73
  const rootDir = ConfigManager.getRootDir();
63
- const outputDir = args.output
64
- ? path_1.default.resolve(args.output)
65
- : path_1.default.join(rootDir, "schema");
74
+ return args.output ? path_1.default.resolve(args.output) : path_1.default.join(rootDir, "schema");
75
+ }
76
+ async function schemaPullCommand(args) {
77
+ const { SN_USER, SN_PASSWORD, SN_INSTANCE } = requireCreds();
78
+ const scopes = resolveScopes(args);
79
+ const outputDir = outputDirFor(args);
66
80
  try {
67
81
  const index = await (0, dovetail_schema_1.pullSchema)({
68
82
  instance: SN_INSTANCE,
@@ -72,6 +86,18 @@ async function schemaPullCommand(args) {
72
86
  scopes,
73
87
  });
74
88
  Logger_1.logger.success(`Schema pull complete! ${index.total_tables} tables across ${index.applications.length} applications written to ${outputDir}`);
89
+ if (args.snapshot !== undefined && args.snapshot !== false) {
90
+ const label = typeof args.snapshot === "string" && args.snapshot.length > 0
91
+ ? args.snapshot
92
+ : null;
93
+ const info = await (0, dovetail_schema_1.writeSnapshot)({
94
+ outputDir,
95
+ index,
96
+ label,
97
+ now: new Date().toISOString(),
98
+ });
99
+ Logger_1.logger.success("Snapshot saved" + (label ? ` (${label})` : "") + ": " + info.dir);
100
+ }
75
101
  }
76
102
  catch (e) {
77
103
  let message;
@@ -83,3 +109,71 @@ async function schemaPullCommand(args) {
83
109
  throw e;
84
110
  }
85
111
  }
112
+ // Resolve a diff ref to a directory of schema files. `live` triggers a fresh
113
+ // pull into a temp dir; everything else resolves to a snapshot/baseline dir.
114
+ async function resolveRefDir(options) {
115
+ const { ref, outputDir, instance, scopes, creds } = options;
116
+ if (ref === "live") {
117
+ const tmpDir = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "dove-schema-live-"));
118
+ await (0, dovetail_schema_1.pullSchema)({
119
+ instance: creds.SN_INSTANCE,
120
+ username: creds.SN_USER,
121
+ password: creds.SN_PASSWORD,
122
+ outputDir: tmpDir,
123
+ scopes,
124
+ });
125
+ return tmpDir;
126
+ }
127
+ return (0, dovetail_schema_1.resolveSnapshotDir)({ ref, outputDir, instance });
128
+ }
129
+ async function schemaDiffCommand(args) {
130
+ const creds = requireCreds();
131
+ const instance = normalizeInstance(creds.SN_INSTANCE);
132
+ const outputDir = outputDirFor(args);
133
+ const scopes = resolveScopes(args);
134
+ const scope = args.scope || null;
135
+ const format = args.format === "json" ? "json" : "text";
136
+ // Default `from` = newest snapshot for this instance; default `to` = live.
137
+ let fromRef = args.from;
138
+ if (!fromRef) {
139
+ const snapshots = await (0, dovetail_schema_1.listSnapshots)({ outputDir, instance });
140
+ if (snapshots.length === 0) {
141
+ throw new Error("No --from given and no snapshots found for " +
142
+ instance +
143
+ ". Create one with `dove schema pull --snapshot <label>`, or pass --from <path>.");
144
+ }
145
+ fromRef = path_1.default.basename(snapshots[0].dir);
146
+ }
147
+ const toRef = args.to || "live";
148
+ const fromDir = await resolveRefDir({ ref: fromRef, outputDir, instance, scopes, creds });
149
+ const toDir = await resolveRefDir({ ref: toRef, outputDir, instance, scopes, creds });
150
+ const from = await (0, dovetail_schema_1.readSchemaTree)({ dir: fromDir, scope });
151
+ const to = await (0, dovetail_schema_1.readSchemaTree)({ dir: toDir, scope });
152
+ const diff = (0, dovetail_schema_1.diffSchemas)({ from, to, fromRef, toRef, scope });
153
+ // Print with console.log so formatDiff's own severity colors survive (the
154
+ // winston logger re-wraps every line in a single color).
155
+ console.log((0, dovetail_schema_1.formatDiff)(diff, { format }));
156
+ // Non-zero exit on breaking drift, for CI gating.
157
+ process.exitCode = diff.exit_code;
158
+ }
159
+ async function schemaSnapshotsCommand(args) {
160
+ const creds = requireCreds();
161
+ const instance = normalizeInstance(creds.SN_INSTANCE);
162
+ const outputDir = outputDirFor(args);
163
+ const snapshots = await (0, dovetail_schema_1.listSnapshots)({ outputDir, instance });
164
+ if (snapshots.length === 0) {
165
+ Logger_1.logger.info("No snapshots for " +
166
+ instance +
167
+ ". Create one with `dove schema pull --snapshot <label>`.");
168
+ return;
169
+ }
170
+ Logger_1.logger.info("Snapshots for " + instance + ":");
171
+ for (const snap of snapshots) {
172
+ console.log(" " +
173
+ path_1.default.basename(snap.dir) +
174
+ " " +
175
+ snap.total_tables +
176
+ " tables" +
177
+ (snap.label ? " [" + snap.label + "]" : ""));
178
+ }
179
+ }
package/dist/snClient.js CHANGED
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.unwrapSNResponse = exports.defaultClient = exports.snClient = exports.processPushResponse = exports.retryOnHttpErr = exports.retryOnErr = void 0;
7
7
  exports.setBenchmarkSink = setBenchmarkSink;
8
8
  exports._resetDovetailApiFallback = _resetDovetailApiFallback;
9
- exports._isUsingLegacyClaudePath = _isUsingLegacyClaudePath;
9
+ exports._isUsingLegacyPath = _isUsingLegacyPath;
10
10
  exports._callDovetailApi = _callDovetailApi;
11
11
  exports.unwrapTableAPIFirstItem = unwrapTableAPIFirstItem;
12
12
  const axios_1 = __importDefault(require("axios"));
@@ -56,28 +56,28 @@ function _getHttpStatus(e) {
56
56
  }
57
57
  return undefined;
58
58
  }
59
- // Dovetail server-side Scripted REST API was renamed from "Claude" (path
60
- // /api/cadso/claude/*) to "Dovetail" (path /api/cadso/dovetail/*). On instances
61
- // where the rename hasn't been imported yet, the new path returns 404. We try
62
- // the dovetail path first; if it 404s we fall back to the legacy claude path
63
- // for the rest of the session, with a one-time deprecation warning.
64
- let _dovetailApiUseLegacyClaudePath = false;
59
+ // Dovetail's core Scripted REST API now lives in the Dovetail scoped
60
+ // application at /api/cadso/dovetail_core/*. Older instances still expose the
61
+ // previous global-scope path /api/cadso/dovetail/*. We try dovetail_core first;
62
+ // if it 404s we fall back to the legacy /api/cadso/dovetail/* path for the rest
63
+ // of the session, with a one-time deprecation warning.
64
+ let _dovetailApiUseLegacyPath = false;
65
65
  function _dovetailEndpoint(op) {
66
- return _dovetailApiUseLegacyClaudePath
67
- ? "api/cadso/claude/" + op
68
- : "api/cadso/dovetail/" + op;
66
+ return _dovetailApiUseLegacyPath
67
+ ? "api/cadso/dovetail/" + op
68
+ : "api/cadso/dovetail_core/" + op;
69
69
  }
70
70
  // Exported for tests only: reset the latched fallback state between test cases.
71
71
  function _resetDovetailApiFallback() {
72
- _dovetailApiUseLegacyClaudePath = false;
72
+ _dovetailApiUseLegacyPath = false;
73
73
  }
74
74
  // Exported for tests only: inspect the latched fallback state.
75
- function _isUsingLegacyClaudePath() {
76
- return _dovetailApiUseLegacyClaudePath;
75
+ function _isUsingLegacyPath() {
76
+ return _dovetailApiUseLegacyPath;
77
77
  }
78
78
  // SN returns 400 with this body when a Scripted REST endpoint does not exist
79
- // at the requested URI. The /dovetail -> /claude fallback above only fires on
80
- // 404, so the same "endpoint missing" condition presents as an opaque 400 to
79
+ // at the requested URI. The dovetail_core -> dovetail fallback above only fires
80
+ // on 404, so the same "endpoint missing" condition presents as an opaque 400 to
81
81
  // callers. Detect it and surface a clear hint.
82
82
  var _SN_MISSING_ENDPOINT_BODY = "Requested URI does not represent any resource";
83
83
  function _summarizeResponseBody(data) {
@@ -101,7 +101,7 @@ function _enrichDovetailApiError(e, attemptedEndpoint) {
101
101
  var body = e.response ? _summarizeResponseBody(e.response.data) : "";
102
102
  var missingEndpoint = status === 400 && body.indexOf(_SN_MISSING_ENDPOINT_BODY) !== -1;
103
103
  var hint = missingEndpoint
104
- ? " — Scripted REST endpoint not found on this instance. Re-import the Dovetail Scripted REST API XML (see Dovetail/docs/dovetail-servicenow-migration.md)."
104
+ ? " — Scripted REST endpoint not found on this instance. Install the Dovetail application's Scripted REST APIs (dovetail_core / dovetail_sync / dovetail_promote)."
105
105
  : "";
106
106
  e.message =
107
107
  "Dovetail REST call failed: " +
@@ -117,11 +117,11 @@ async function _callDovetailApi(op, call) {
117
117
  return await call(endpoint);
118
118
  }
119
119
  catch (e) {
120
- if (!_dovetailApiUseLegacyClaudePath && _getHttpStatus(e) === 404) {
121
- Logger_1.logger.warn("[deprecation] /api/cadso/dovetail/" + op +
122
- " returned 404. Falling back to legacy /api/cadso/claude/" + op +
123
- ". Re-import the Dovetail Scripted REST API XML on your ServiceNow instance to silence this warning.");
124
- _dovetailApiUseLegacyClaudePath = true;
120
+ if (!_dovetailApiUseLegacyPath && _getHttpStatus(e) === 404) {
121
+ Logger_1.logger.warn("[deprecation] /api/cadso/dovetail_core/" + op +
122
+ " returned 404. Falling back to legacy /api/cadso/dovetail/" + op +
123
+ ". Install the Dovetail application's Scripted REST APIs to silence this warning.");
124
+ _dovetailApiUseLegacyPath = true;
125
125
  var legacyEndpoint = _dovetailEndpoint(op);
126
126
  try {
127
127
  return await call(legacyEndpoint);
@@ -290,11 +290,11 @@ const snClient = (baseURL, username, password) => {
290
290
  });
291
291
  const client = (0, axios_rate_limit_1.default)(rawAxios, { maxRPS: 20 });
292
292
  const getAppList = () => {
293
- const endpoint = "api/sinc/sincronia/getAppList";
293
+ const endpoint = "api/cadso/dovetail_sync/getAppList";
294
294
  return client.get(endpoint);
295
295
  };
296
296
  const updateATFfile = (contents, sysId) => {
297
- const endpoint = "api/sinc/sincronia/pushATFfile";
297
+ const endpoint = "api/cadso/dovetail_sync/pushATFfile";
298
298
  try {
299
299
  return client.post(endpoint, { file: contents, sys_id: sysId });
300
300
  }
@@ -350,7 +350,7 @@ const snClient = (baseURL, username, password) => {
350
350
  });
351
351
  };
352
352
  const getCurrentScope = () => {
353
- const endpoint = "api/sinc/sincronia/getCurrentScope";
353
+ const endpoint = "api/cadso/dovetail_sync/getCurrentScope";
354
354
  return client.get(endpoint);
355
355
  };
356
356
  const createUpdateSet = (updateSetName, scopeSysId, description) => {
@@ -390,13 +390,13 @@ const snClient = (baseURL, username, password) => {
390
390
  });
391
391
  };
392
392
  const getMissingFiles = (missingFiles, tableOptions) => {
393
- const endpoint = `api/sinc/sincronia/bulkDownload`;
393
+ const endpoint = `api/cadso/dovetail_sync/bulkDownload`;
394
394
  const tableCount = Object.keys(missingFiles).length;
395
395
  FileLogger_1.fileLogger.debug("Bulk downloading files for " + tableCount + " tables");
396
396
  return client.post(endpoint, { missingFiles, tableOptions });
397
397
  };
398
398
  const getManifest = (scope, config, withFiles = false) => {
399
- const endpoint = `api/sinc/sincronia/getManifest/${scope}`;
399
+ const endpoint = `api/cadso/dovetail_sync/getManifest/${scope}`;
400
400
  const { includes = {}, excludes = {}, tableOptions = {}, scopes = {}, } = config;
401
401
  // Strip _ directive keys before sending to ServiceNow API
402
402
  var cleanIncludes = _stripUnderscoreKeys(includes);
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // Tests for the /api/cadso/dovetail/* -> /api/cadso/claude/* 404 fallback
2
+ // Tests for the /api/cadso/dovetail_core/* -> /api/cadso/dovetail/* 404 fallback
3
3
  // in _callDovetailApi (snClient.ts). Covers: first call hits the dovetail
4
4
  // endpoint, 404 latches the legacy path with a one-time warning, subsequent
5
5
  // calls skip dovetail, and non-404 errors do NOT trigger fallback.
@@ -35,21 +35,21 @@ describe("_callDovetailApi 404 fallback", function () {
35
35
  (0, snClient_1._resetDovetailApiFallback)();
36
36
  jest.clearAllMocks();
37
37
  });
38
- it("first request hits /api/cadso/dovetail/<op>", async function () {
38
+ it("first request hits /api/cadso/dovetail_core/<op>", async function () {
39
39
  var seen = [];
40
40
  var call = jest.fn(async function (endpoint) {
41
41
  seen.push(endpoint);
42
42
  return makeAxiosResponse({ ok: true });
43
43
  });
44
44
  await (0, snClient_1._callDovetailApi)("changeScope", call);
45
- expect(seen).toEqual(["api/cadso/dovetail/changeScope"]);
46
- expect((0, snClient_1._isUsingLegacyClaudePath)()).toBe(false);
45
+ expect(seen).toEqual(["api/cadso/dovetail_core/changeScope"]);
46
+ expect((0, snClient_1._isUsingLegacyPath)()).toBe(false);
47
47
  });
48
- it("on 404, retries against legacy /api/cadso/claude/<op> and warns once", async function () {
48
+ it("on 404, retries against legacy /api/cadso/dovetail/<op> and warns once", async function () {
49
49
  var seen = [];
50
50
  var call = jest.fn(async function (endpoint) {
51
51
  seen.push(endpoint);
52
- if (endpoint.indexOf("api/cadso/dovetail/") === 0) {
52
+ if (endpoint.indexOf("api/cadso/dovetail_core/") === 0) {
53
53
  throw makeAxiosError(404);
54
54
  }
55
55
  return makeAxiosResponse({ ok: true });
@@ -57,20 +57,20 @@ describe("_callDovetailApi 404 fallback", function () {
57
57
  var result = await (0, snClient_1._callDovetailApi)("changeScope", call);
58
58
  expect(result.data).toEqual({ ok: true });
59
59
  expect(seen).toEqual([
60
+ "api/cadso/dovetail_core/changeScope",
60
61
  "api/cadso/dovetail/changeScope",
61
- "api/cadso/claude/changeScope",
62
62
  ]);
63
- expect((0, snClient_1._isUsingLegacyClaudePath)()).toBe(true);
63
+ expect((0, snClient_1._isUsingLegacyPath)()).toBe(true);
64
64
  expect(mockLogger.warn).toHaveBeenCalledTimes(1);
65
65
  expect(mockLogger.warn.mock.calls[0][0]).toContain("[deprecation]");
66
+ expect(mockLogger.warn.mock.calls[0][0]).toContain("/api/cadso/dovetail_core/changeScope");
66
67
  expect(mockLogger.warn.mock.calls[0][0]).toContain("/api/cadso/dovetail/changeScope");
67
- expect(mockLogger.warn.mock.calls[0][0]).toContain("/api/cadso/claude/changeScope");
68
68
  });
69
69
  it("after latching, subsequent calls skip dovetail and warn no more", async function () {
70
70
  var seen = [];
71
71
  var call = jest.fn(async function (endpoint) {
72
72
  seen.push(endpoint);
73
- if (endpoint.indexOf("api/cadso/dovetail/") === 0) {
73
+ if (endpoint.indexOf("api/cadso/dovetail_core/") === 0) {
74
74
  throw makeAxiosError(404);
75
75
  }
76
76
  return makeAxiosResponse({ ok: true });
@@ -81,8 +81,8 @@ describe("_callDovetailApi 404 fallback", function () {
81
81
  await (0, snClient_1._callDovetailApi)("changeScope", call);
82
82
  await (0, snClient_1._callDovetailApi)("changeUpdateSet", call);
83
83
  expect(seen).toEqual([
84
- "api/cadso/claude/changeScope",
85
- "api/cadso/claude/changeUpdateSet",
84
+ "api/cadso/dovetail/changeScope",
85
+ "api/cadso/dovetail/changeUpdateSet",
86
86
  ]);
87
87
  expect(mockLogger.warn).not.toHaveBeenCalled();
88
88
  });
@@ -94,7 +94,7 @@ describe("_callDovetailApi 404 fallback", function () {
94
94
  response: { status: 401 },
95
95
  });
96
96
  expect(call).toHaveBeenCalledTimes(1);
97
- expect((0, snClient_1._isUsingLegacyClaudePath)()).toBe(false);
97
+ expect((0, snClient_1._isUsingLegacyPath)()).toBe(false);
98
98
  expect(mockLogger.warn).not.toHaveBeenCalled();
99
99
  });
100
100
  it("does NOT fall back on 500 (server error)", async function () {
@@ -105,7 +105,7 @@ describe("_callDovetailApi 404 fallback", function () {
105
105
  response: { status: 500 },
106
106
  });
107
107
  expect(call).toHaveBeenCalledTimes(1);
108
- expect((0, snClient_1._isUsingLegacyClaudePath)()).toBe(false);
108
+ expect((0, snClient_1._isUsingLegacyPath)()).toBe(false);
109
109
  expect(mockLogger.warn).not.toHaveBeenCalled();
110
110
  });
111
111
  it("does NOT fall back on a network error with no response", async function () {
@@ -114,7 +114,7 @@ describe("_callDovetailApi 404 fallback", function () {
114
114
  });
115
115
  await expect((0, snClient_1._callDovetailApi)("changeScope", call)).rejects.toThrow("ECONNREFUSED");
116
116
  expect(call).toHaveBeenCalledTimes(1);
117
- expect((0, snClient_1._isUsingLegacyClaudePath)()).toBe(false);
117
+ expect((0, snClient_1._isUsingLegacyPath)()).toBe(false);
118
118
  expect(mockLogger.warn).not.toHaveBeenCalled();
119
119
  });
120
120
  describe("descriptive error enrichment", function () {
@@ -129,7 +129,7 @@ describe("_callDovetailApi 404 fallback", function () {
129
129
  catch (e) {
130
130
  expect(e.response.status).toBe(401);
131
131
  expect(e.message).toContain("HTTP 401");
132
- expect(e.message).toContain("api/cadso/dovetail/changeScope");
132
+ expect(e.message).toContain("api/cadso/dovetail_core/changeScope");
133
133
  expect(e.message).toContain("User Not Authenticated");
134
134
  }
135
135
  });
@@ -148,8 +148,8 @@ describe("_callDovetailApi 404 fallback", function () {
148
148
  catch (e) {
149
149
  expect(e.response.status).toBe(400);
150
150
  expect(e.message).toContain("HTTP 400");
151
- expect(e.message).toContain("api/cadso/dovetail/changeScope");
152
- expect(e.message).toContain("Re-import the Dovetail Scripted REST API XML");
151
+ expect(e.message).toContain("api/cadso/dovetail_core/changeScope");
152
+ expect(e.message).toContain("Install the Dovetail application's Scripted REST APIs");
153
153
  }
154
154
  });
155
155
  it("does NOT add the missing-endpoint hint for unrelated 400s", async function () {
@@ -163,14 +163,14 @@ describe("_callDovetailApi 404 fallback", function () {
163
163
  catch (e) {
164
164
  expect(e.message).toContain("HTTP 400");
165
165
  expect(e.message).toContain("Invalid scope name");
166
- expect(e.message).not.toContain("Re-import the Dovetail Scripted REST API XML");
166
+ expect(e.message).not.toContain("Install the Dovetail application's Scripted REST APIs");
167
167
  }
168
168
  });
169
169
  it("after fallback latches, errors from the legacy path are also enriched", async function () {
170
170
  var calls = [];
171
171
  var call = jest.fn(async function (endpoint) {
172
172
  calls.push(endpoint);
173
- if (endpoint.indexOf("api/cadso/dovetail/") === 0) {
173
+ if (endpoint.indexOf("api/cadso/dovetail_core/") === 0) {
174
174
  throw makeAxiosError(404, {});
175
175
  }
176
176
  throw makeAxiosError(500, { error: { message: "boom" } });
@@ -181,12 +181,12 @@ describe("_callDovetailApi 404 fallback", function () {
181
181
  }
182
182
  catch (e) {
183
183
  expect(calls).toEqual([
184
+ "api/cadso/dovetail_core/changeScope",
184
185
  "api/cadso/dovetail/changeScope",
185
- "api/cadso/claude/changeScope",
186
186
  ]);
187
187
  expect(e.response.status).toBe(500);
188
188
  expect(e.message).toContain("HTTP 500");
189
- expect(e.message).toContain("api/cadso/claude/changeScope");
189
+ expect(e.message).toContain("api/cadso/dovetail/changeScope");
190
190
  expect(e.message).toContain("boom");
191
191
  }
192
192
  });
@@ -38,7 +38,7 @@ function makeAxiosError(overrides) {
38
38
  error.isAxiosError = true;
39
39
  error.config = {
40
40
  method: overrides.method || "post",
41
- url: overrides.url || "api/sinc/sincronia/getManifest/x_cadso_example",
41
+ url: overrides.url || "api/cadso/dovetail_sync/getManifest/x_cadso_example",
42
42
  };
43
43
  error.response = {
44
44
  status: overrides.status,
@@ -74,7 +74,7 @@ describe("unwrapSNResponse — error handling", function () {
74
74
  var axErr = makeAxiosError({
75
75
  status: 500,
76
76
  method: "post",
77
- url: "api/sinc/sincronia/getManifest/x_cadso_automate",
77
+ url: "api/cadso/dovetail_sync/getManifest/x_cadso_automate",
78
78
  data: snBody,
79
79
  statusText: "Internal Server Error",
80
80
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-core",
3
- "version": "0.0.94",
3
+ "version": "0.0.96",
4
4
  "description": "Next-gen file syncer",
5
5
  "license": "GPL-3.0",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@tenonhq/dovetail-clickup": "^0.0.10",
42
- "@tenonhq/dovetail-schema": "^0.0.5",
42
+ "@tenonhq/dovetail-schema": "^0.0.9",
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,339 @@
1
1
  {
2
2
  "$schema": "dovetail-manifest/v1",
3
- "generated_at": "2026-05-28T15:54:22.498Z",
3
+ "generated_at": "2026-05-31T16:52:41.031Z",
4
4
  "events": [
5
+ {
6
+ "$schema": "dovetail-release-event/v1",
7
+ "event_id": "dovetail-servicenow@0.0.9",
8
+ "package": "@tenonhq/dovetail-servicenow",
9
+ "version": "0.0.9",
10
+ "prev_version": "0.0.8",
11
+ "published_at": "2026-05-31T16:52:41.024Z",
12
+ "git": {
13
+ "sha": "6e62bec706d10158b1f1496e3e2246a927c1c0b5",
14
+ "tag": "dovetail-servicenow@0.0.9"
15
+ },
16
+ "semver_bump": "patch",
17
+ "commits": [
18
+ {
19
+ "sha": "6e62bec70",
20
+ "subject": "fix(servicenow): editFlow persistence + copyFlow (dropped from #129 squash-merge) (#130)",
21
+ "pr": 130
22
+ },
23
+ {
24
+ "sha": "d32523991",
25
+ "subject": "chore(release): @tenonhq/dovetail-servicenow@0.0.8 [skip ci]"
26
+ }
27
+ ],
28
+ "exports_delta": null,
29
+ "narrative": null
30
+ },
31
+ {
32
+ "$schema": "dovetail-release-event/v1",
33
+ "event_id": "dovetail-servicenow@0.0.8",
34
+ "package": "@tenonhq/dovetail-servicenow",
35
+ "version": "0.0.8",
36
+ "prev_version": "0.0.7",
37
+ "published_at": "2026-05-31T03:07:57.716Z",
38
+ "git": {
39
+ "sha": "41b87ff39f4dcb7f6fd5bbbd3d3d26446d450bc6",
40
+ "tag": "dovetail-servicenow@0.0.8"
41
+ },
42
+ "semver_bump": "patch",
43
+ "commits": [
44
+ {
45
+ "sha": "41b87ff39",
46
+ "subject": "feat(servicenow): view + test + publish + edit flows/subflows/actions (Dovetail Flow Capability) (#129)",
47
+ "pr": 129
48
+ },
49
+ {
50
+ "sha": "cbd551e00",
51
+ "subject": "chore(release): @tenonhq/dovetail-servicenow@0.0.7 [skip ci]"
52
+ }
53
+ ],
54
+ "exports_delta": null,
55
+ "narrative": null
56
+ },
57
+ {
58
+ "$schema": "dovetail-release-event/v1",
59
+ "event_id": "dovetail-dashboard@0.0.36",
60
+ "package": "@tenonhq/dovetail-dashboard",
61
+ "version": "0.0.36",
62
+ "prev_version": "0.0.35",
63
+ "published_at": "2026-05-30T10:12:42.799Z",
64
+ "git": {
65
+ "sha": "fd04f052c3426e1591fcd3383c5ace29f2d9121f",
66
+ "tag": "dovetail-dashboard@0.0.36"
67
+ },
68
+ "semver_bump": "patch",
69
+ "commits": [
70
+ {
71
+ "sha": "fd04f052c",
72
+ "subject": "feat(dashboard): TODO side panel on claude-plans page (#127)",
73
+ "pr": 127
74
+ },
75
+ {
76
+ "sha": "9901285c6",
77
+ "subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.35 [skip ci]"
78
+ }
79
+ ],
80
+ "exports_delta": null,
81
+ "narrative": null
82
+ },
83
+ {
84
+ "$schema": "dovetail-release-event/v1",
85
+ "event_id": "dovetail-dashboard@0.0.35",
86
+ "package": "@tenonhq/dovetail-dashboard",
87
+ "version": "0.0.35",
88
+ "prev_version": "0.0.34",
89
+ "published_at": "2026-05-30T09:01:34.128Z",
90
+ "git": {
91
+ "sha": "31178f2465068e24d4804cc467976f9ede3c9788",
92
+ "tag": "dovetail-dashboard@0.0.35"
93
+ },
94
+ "semver_bump": "patch",
95
+ "commits": [
96
+ {
97
+ "sha": "31178f246",
98
+ "subject": "fix(todo): apply PR #124 review findings (R-1…R-4) + unblock republish (#125)",
99
+ "pr": 125
100
+ },
101
+ {
102
+ "sha": "55de31527",
103
+ "subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.34 [skip ci]"
104
+ }
105
+ ],
106
+ "exports_delta": null,
107
+ "narrative": null
108
+ },
109
+ {
110
+ "$schema": "dovetail-release-event/v1",
111
+ "event_id": "dovetail-dashboard@0.0.34",
112
+ "package": "@tenonhq/dovetail-dashboard",
113
+ "version": "0.0.34",
114
+ "prev_version": "0.0.33",
115
+ "published_at": "2026-05-30T08:18:23.493Z",
116
+ "git": {
117
+ "sha": "d8151d39e4fa684a3bfee0b9f5bc6f51859bffe4",
118
+ "tag": "dovetail-dashboard@0.0.34"
119
+ },
120
+ "semver_bump": "patch",
121
+ "commits": [
122
+ {
123
+ "sha": "d8151d39e",
124
+ "subject": "feat(todo): TODO MCP package + dashboard priority checklist (#124)",
125
+ "pr": 124
126
+ },
127
+ {
128
+ "sha": "da705804d",
129
+ "subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.33 [skip ci]"
130
+ }
131
+ ],
132
+ "exports_delta": null,
133
+ "narrative": null
134
+ },
135
+ {
136
+ "$schema": "dovetail-release-event/v1",
137
+ "event_id": "dovetail-sawmill@0.0.7",
138
+ "package": "@tenonhq/dovetail-sawmill",
139
+ "version": "0.0.7",
140
+ "prev_version": "0.0.6",
141
+ "published_at": "2026-05-29T21:44:48.823Z",
142
+ "git": {
143
+ "sha": "1e949a30823f064a9dc603455f16cf5b10bc61d3",
144
+ "tag": "dovetail-sawmill@0.0.7"
145
+ },
146
+ "semver_bump": "patch",
147
+ "commits": [
148
+ {
149
+ "sha": "1e949a308",
150
+ "subject": "docs: Dovetail adoption pack (Claude operating guide, onboarding, INDEX, drift fix) (#121)",
151
+ "pr": 121
152
+ },
153
+ {
154
+ "sha": "9332fdf7a",
155
+ "subject": "chore(release): publish 20 packages [skip ci]"
156
+ },
157
+ {
158
+ "sha": "9046da774",
159
+ "subject": "chore(release): publish 19 packages [skip ci]"
160
+ }
161
+ ],
162
+ "exports_delta": null,
163
+ "narrative": null
164
+ },
165
+ {
166
+ "$schema": "dovetail-release-event/v1",
167
+ "event_id": "dovetail-schema@0.0.9",
168
+ "package": "@tenonhq/dovetail-schema",
169
+ "version": "0.0.9",
170
+ "prev_version": "0.0.8",
171
+ "published_at": "2026-05-29T21:30:43.647Z",
172
+ "git": {
173
+ "sha": "dac76cc7b8e6a346aa54a84c7b50c442323301fb",
174
+ "tag": "dovetail-schema@0.0.9"
175
+ },
176
+ "semver_bump": "patch",
177
+ "commits": [
178
+ {
179
+ "sha": "dac76cc7b",
180
+ "subject": "feat(schema): snapshots + structured drift diff for dove schema (#122)",
181
+ "pr": 122
182
+ },
183
+ {
184
+ "sha": "9332fdf7a",
185
+ "subject": "chore(release): publish 20 packages [skip ci]"
186
+ },
187
+ {
188
+ "sha": "9046da774",
189
+ "subject": "chore(release): publish 19 packages [skip ci]"
190
+ }
191
+ ],
192
+ "exports_delta": null,
193
+ "narrative": null
194
+ },
195
+ {
196
+ "$schema": "dovetail-release-event/v1",
197
+ "event_id": "dovetail-core@0.0.95",
198
+ "package": "@tenonhq/dovetail-core",
199
+ "version": "0.0.95",
200
+ "prev_version": "0.0.94",
201
+ "published_at": "2026-05-29T21:30:43.647Z",
202
+ "git": {
203
+ "sha": "dac76cc7b8e6a346aa54a84c7b50c442323301fb",
204
+ "tag": "dovetail-core@0.0.95"
205
+ },
206
+ "semver_bump": "patch",
207
+ "commits": [
208
+ {
209
+ "sha": "dac76cc7b",
210
+ "subject": "feat(schema): snapshots + structured drift diff for dove schema (#122)",
211
+ "pr": 122
212
+ },
213
+ {
214
+ "sha": "da705804d",
215
+ "subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.33 [skip ci]"
216
+ },
217
+ {
218
+ "sha": "92213e1f8",
219
+ "subject": "chore(release): publish 2 packages [skip ci]"
220
+ },
221
+ {
222
+ "sha": "1d43f9122",
223
+ "subject": "chore(release): @tenonhq/dovetail-core@0.0.94 [skip ci]"
224
+ }
225
+ ],
226
+ "exports_delta": null,
227
+ "narrative": null
228
+ },
229
+ {
230
+ "$schema": "dovetail-release-event/v1",
231
+ "event_id": "dovetail-dashboard@0.0.33",
232
+ "package": "@tenonhq/dovetail-dashboard",
233
+ "version": "0.0.33",
234
+ "prev_version": "0.0.32",
235
+ "published_at": "2026-05-29T16:55:37.729Z",
236
+ "git": {
237
+ "sha": "3aaef1ba92364c119ece69369c5cc3379da0ed73",
238
+ "tag": "dovetail-dashboard@0.0.33"
239
+ },
240
+ "semver_bump": "patch",
241
+ "commits": [
242
+ {
243
+ "sha": "3aaef1ba9",
244
+ "subject": "fix: legible inline code in Claude Plans dark mode (#120)",
245
+ "pr": 120
246
+ },
247
+ {
248
+ "sha": "92213e1f8",
249
+ "subject": "chore(release): publish 2 packages [skip ci]"
250
+ }
251
+ ],
252
+ "exports_delta": null,
253
+ "narrative": null
254
+ },
255
+ {
256
+ "$schema": "dovetail-release-event/v1",
257
+ "event_id": "dovetail-claude-plans@0.0.20",
258
+ "package": "@tenonhq/dovetail-claude-plans",
259
+ "version": "0.0.20",
260
+ "prev_version": "0.0.19",
261
+ "published_at": "2026-05-29T02:18:11.229Z",
262
+ "git": {
263
+ "sha": "6a12c10733b64732855a8125c84f0d4846ced256",
264
+ "tag": "dovetail-claude-plans@0.0.20"
265
+ },
266
+ "semver_bump": "patch",
267
+ "commits": [
268
+ {
269
+ "sha": "6a12c1073",
270
+ "subject": "feat: claude-plans version history, feature score, selected-state + deep-link hardening (#118)",
271
+ "pr": 118
272
+ },
273
+ {
274
+ "sha": "578d6fb2a",
275
+ "subject": "chore(release): publish 2 packages [skip ci]"
276
+ }
277
+ ],
278
+ "exports_delta": null,
279
+ "narrative": null
280
+ },
281
+ {
282
+ "$schema": "dovetail-release-event/v1",
283
+ "event_id": "dovetail-dashboard@0.0.32",
284
+ "package": "@tenonhq/dovetail-dashboard",
285
+ "version": "0.0.32",
286
+ "prev_version": "0.0.31",
287
+ "published_at": "2026-05-29T02:18:11.229Z",
288
+ "git": {
289
+ "sha": "6a12c10733b64732855a8125c84f0d4846ced256",
290
+ "tag": "dovetail-dashboard@0.0.32"
291
+ },
292
+ "semver_bump": "patch",
293
+ "commits": [
294
+ {
295
+ "sha": "6a12c1073",
296
+ "subject": "feat: claude-plans version history, feature score, selected-state + deep-link hardening (#118)",
297
+ "pr": 118
298
+ },
299
+ {
300
+ "sha": "578d6fb2a",
301
+ "subject": "chore(release): publish 2 packages [skip ci]"
302
+ }
303
+ ],
304
+ "exports_delta": null,
305
+ "narrative": null
306
+ },
307
+ {
308
+ "$schema": "dovetail-release-event/v1",
309
+ "event_id": "dovetail-core@0.0.94",
310
+ "package": "@tenonhq/dovetail-core",
311
+ "version": "0.0.94",
312
+ "prev_version": "0.0.93",
313
+ "published_at": "2026-05-28T23:46:03.175Z",
314
+ "git": {
315
+ "sha": "8a76deffc38892321c92ae538fee3bc6b8f90d76",
316
+ "tag": "dovetail-core@0.0.94"
317
+ },
318
+ "semver_bump": "patch",
319
+ "commits": [
320
+ {
321
+ "sha": "8a76deffc",
322
+ "subject": "fix: filesystem-safe record folder names (sys_id fallback for Windows) (#117)",
323
+ "pr": 117
324
+ },
325
+ {
326
+ "sha": "578d6fb2a",
327
+ "subject": "chore(release): publish 2 packages [skip ci]"
328
+ },
329
+ {
330
+ "sha": "3faeec43f",
331
+ "subject": "chore(release): @tenonhq/dovetail-core@0.0.93 [skip ci]"
332
+ }
333
+ ],
334
+ "exports_delta": null,
335
+ "narrative": null
336
+ },
5
337
  {
6
338
  "$schema": "dovetail-release-event/v1",
7
339
  "event_id": "dovetail-claude-plans@0.0.19",