@delfini/cli 0.2.1 → 0.3.0

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/index.cjs CHANGED
@@ -1802,26 +1802,28 @@ var require_picomatch2 = __commonJS({
1802
1802
  // src/index.ts
1803
1803
  var index_exports = {};
1804
1804
  __export(index_exports, {
1805
- DOC_SCOPE_RELATIVE_PATH: () => DOC_SCOPE_RELATIVE_PATH,
1806
- DOC_SCOPE_VERSION: () => DOC_SCOPE_VERSION,
1807
- DocScopeCorruptError: () => DocScopeCorruptError,
1808
- DocScopeValidationError: () => DocScopeValidationError,
1809
- DocScopeVersionMismatchError: () => DocScopeVersionMismatchError,
1805
+ ConfigCorruptError: () => ConfigCorruptError,
1806
+ ConfigValidationError: () => ConfigValidationError,
1807
+ ConfigVersionMismatchError: () => ConfigVersionMismatchError,
1808
+ DELFINI_CONFIG_RELATIVE_PATH: () => DELFINI_CONFIG_RELATIVE_PATH,
1809
+ DELFINI_CONFIG_VERSION: () => DELFINI_CONFIG_VERSION,
1810
1810
  InstallToolNotSupportedError: () => InstallToolNotSupportedError,
1811
+ LEGACY_DOC_SCOPE_RELATIVE_PATH: () => LEGACY_DOC_SCOPE_RELATIVE_PATH,
1811
1812
  PROMPT_TOKEN_BUDGET: () => PROMPT_TOKEN_BUDGET,
1812
1813
  RepoRootNotFoundError: () => RepoRootNotFoundError,
1813
1814
  appendToGitignore: () => appendToGitignore,
1814
- deleteDocScope: () => deleteDocScope,
1815
- docScopeExists: () => docScopeExists,
1815
+ configExists: () => configExists,
1816
+ deleteConfig: () => deleteConfig,
1816
1817
  ensureTraceDir: () => ensureTraceDir,
1817
1818
  expandDocScope: () => expandDocScope,
1818
1819
  getRepoRoot: () => getRepoRoot,
1819
1820
  main: () => main,
1820
- readDocScope: () => readDocScope,
1821
+ readConfig: () => readConfig,
1821
1822
  runDiffStatus: () => runDiffStatus,
1822
1823
  runInstall: () => runInstall,
1823
1824
  runLocalFinalize: () => runLocalFinalize,
1824
1825
  runLocalPrepare: () => runLocalPrepare,
1826
+ writeConfig: () => writeConfig,
1825
1827
  writeDocScope: () => writeDocScope,
1826
1828
  writeRetryAttemptFile: () => writeRetryAttemptFile,
1827
1829
  writeTraceFile: () => writeTraceFile
@@ -1829,7 +1831,7 @@ __export(index_exports, {
1829
1831
  module.exports = __toCommonJS(index_exports);
1830
1832
  init_cjs_shims();
1831
1833
 
1832
- // src/doc-scope.ts
1834
+ // src/config.ts
1833
1835
  init_cjs_shims();
1834
1836
  var import_node_fs = require("fs");
1835
1837
  var import_node_path = __toESM(require("path"), 1);
@@ -6478,6 +6480,18 @@ function classifyEntry(entry) {
6478
6480
  return "dir";
6479
6481
  return lastSegment.includes(".") ? "file" : "dir";
6480
6482
  }
6483
+ function isFileInDocScope(filePath, scope) {
6484
+ const file = posixNormalize(toPosix(filePath).replace(/^\/+/, ""));
6485
+ if (file === "" || file === ".")
6486
+ return false;
6487
+ const entries = normalizeDocScope(scope);
6488
+ for (const entry of entries) {
6489
+ const pattern = classifyEntry(entry) === "dir" ? `${entry}/**` : entry;
6490
+ if ((0, import_picomatch.default)(pattern, { dot: false, nocase: true })(file))
6491
+ return true;
6492
+ }
6493
+ return false;
6494
+ }
6481
6495
  function toPosix(p) {
6482
6496
  return p.split("\\").join("/");
6483
6497
  }
@@ -6511,7 +6525,10 @@ function posixNormalize(input) {
6511
6525
 
6512
6526
  // ../drift-engine/dist/diff-filter.js
6513
6527
  init_cjs_shims();
6514
- function filterDiff(diff) {
6528
+ function filterDiff(diff, options = {}) {
6529
+ const builtins = options.builtins ?? true;
6530
+ const ignorePaths = options.ignorePaths ?? [];
6531
+ const hasIgnore = ignorePaths.length > 0;
6515
6532
  const droppedPaths = [];
6516
6533
  const droppedHunks = [];
6517
6534
  const files = parseDiffIntoFiles(diff);
@@ -6520,6 +6537,14 @@ function filterDiff(diff) {
6520
6537
  keptParts.push(files.preamble);
6521
6538
  }
6522
6539
  for (const file of files.files) {
6540
+ if (hasIgnore && isFileInDocScope(file.path, ignorePaths)) {
6541
+ droppedPaths.push({ path: file.path, reason: "ignored" });
6542
+ continue;
6543
+ }
6544
+ if (!builtins) {
6545
+ keptParts.push(file.rawSlice);
6546
+ continue;
6547
+ }
6523
6548
  const pathReason = classifyPath(file.path);
6524
6549
  if (pathReason !== null) {
6525
6550
  droppedPaths.push({ path: file.path, reason: pathReason });
@@ -7046,42 +7071,48 @@ async function resolveBaseRef(git, explicitBase, stderr) {
7046
7071
  }
7047
7072
  }
7048
7073
 
7049
- // src/doc-scope.ts
7050
- var DOC_SCOPE_RELATIVE_PATH = ".claude/skills/delfini/doc-scope.json";
7051
- var DOC_SCOPE_VERSION = 1;
7052
- var DOC_SCOPE_VERSION_MISMATCH_MESSAGE = "your doc-scope.json is for a newer @delfini/cli; please upgrade.";
7074
+ // src/config.ts
7075
+ var DELFINI_CONFIG_RELATIVE_PATH = ".claude/skills/delfini/delfini-config.json";
7076
+ var LEGACY_DOC_SCOPE_RELATIVE_PATH = ".claude/skills/delfini/doc-scope.json";
7077
+ var DELFINI_CONFIG_VERSION = 1;
7078
+ var CONFIG_VERSION_MISMATCH_MESSAGE = "your delfini-config.json is for a newer @delfini/cli; please upgrade.";
7053
7079
  var REPO_ROOT_REL = ".";
7054
- var DocScopeVersionMismatchError = class extends Error {
7055
- code = "DOC_SCOPE_VERSION_MISMATCH";
7056
- constructor(message = DOC_SCOPE_VERSION_MISMATCH_MESSAGE) {
7080
+ var ConfigVersionMismatchError = class extends Error {
7081
+ code = "CONFIG_VERSION_MISMATCH";
7082
+ constructor(message = CONFIG_VERSION_MISMATCH_MESSAGE) {
7057
7083
  super(message);
7058
- this.name = "DocScopeVersionMismatchError";
7084
+ this.name = "ConfigVersionMismatchError";
7059
7085
  }
7060
7086
  };
7061
- var DocScopeCorruptError = class extends Error {
7062
- code = "DOC_SCOPE_CORRUPT";
7087
+ var ConfigCorruptError = class extends Error {
7088
+ code = "CONFIG_CORRUPT";
7063
7089
  constructor(message) {
7064
7090
  super(message);
7065
- this.name = "DocScopeCorruptError";
7091
+ this.name = "ConfigCorruptError";
7066
7092
  }
7067
7093
  };
7068
- var DocScopeValidationError = class extends Error {
7069
- code = "DOC_SCOPE_VALIDATION";
7094
+ var ConfigValidationError = class extends Error {
7095
+ code = "CONFIG_VALIDATION";
7070
7096
  constructor(message) {
7071
7097
  super(message);
7072
- this.name = "DocScopeValidationError";
7098
+ this.name = "ConfigValidationError";
7073
7099
  }
7074
7100
  };
7075
- var docScopeSchemaV1 = external_exports.object({
7101
+ var configSchemaV1 = external_exports.object({
7076
7102
  version: external_exports.literal(1),
7077
- doc_scope: external_exports.array(external_exports.string().min(1))
7103
+ doc_scope: external_exports.array(external_exports.string().min(1)),
7104
+ ignore_code_scope: external_exports.array(external_exports.string().min(1)).optional()
7078
7105
  });
7079
7106
  var versionProbeSchema = external_exports.object({
7080
7107
  version: external_exports.number().int().positive()
7081
7108
  });
7082
- async function readDocScope(repoRoot) {
7109
+ async function readConfig(repoRoot) {
7083
7110
  const root = repoRoot ?? await getRepoRoot();
7084
- const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7111
+ const primary = await readConfigFile(import_node_path.default.join(root, DELFINI_CONFIG_RELATIVE_PATH));
7112
+ if (primary !== null) return primary;
7113
+ return readConfigFile(import_node_path.default.join(root, LEGACY_DOC_SCOPE_RELATIVE_PATH));
7114
+ }
7115
+ async function readConfigFile(target) {
7085
7116
  let raw;
7086
7117
  try {
7087
7118
  raw = await import_node_fs.promises.readFile(target, "utf8");
@@ -7093,54 +7124,96 @@ async function readDocScope(repoRoot) {
7093
7124
  try {
7094
7125
  parsed = JSON.parse(raw);
7095
7126
  } catch (err) {
7096
- throw new DocScopeCorruptError(
7097
- `${DOC_SCOPE_RELATIVE_PATH} is malformed: ${err.message}`
7127
+ throw new ConfigCorruptError(
7128
+ `${import_node_path.default.basename(target)} is malformed: ${err.message}`
7098
7129
  );
7099
7130
  }
7100
7131
  const probe = versionProbeSchema.safeParse(parsed);
7101
- if (probe.success && probe.data.version > DOC_SCOPE_VERSION) {
7102
- throw new DocScopeVersionMismatchError();
7132
+ if (probe.success && probe.data.version > DELFINI_CONFIG_VERSION) {
7133
+ throw new ConfigVersionMismatchError();
7103
7134
  }
7104
- const result = docScopeSchemaV1.safeParse(parsed);
7135
+ const result = configSchemaV1.safeParse(parsed);
7105
7136
  if (!result.success) {
7106
- throw new DocScopeCorruptError(
7107
- `${DOC_SCOPE_RELATIVE_PATH} is malformed: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
7137
+ throw new ConfigCorruptError(
7138
+ `${import_node_path.default.basename(target)} is malformed: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
7108
7139
  );
7109
7140
  }
7110
- return result.data;
7141
+ return {
7142
+ version: DELFINI_CONFIG_VERSION,
7143
+ doc_scope: result.data.doc_scope,
7144
+ ignore_code_scope: result.data.ignore_code_scope ?? []
7145
+ };
7111
7146
  }
7112
- async function writeDocScope(paths, options) {
7147
+ async function writeConfig(update, options) {
7113
7148
  const root = options?.repoRoot ?? await getRepoRoot();
7149
+ const existing = await readConfig(root);
7150
+ let docScope = existing?.doc_scope ?? [];
7151
+ let ignoreCodeScope = existing?.ignore_code_scope ?? [];
7152
+ if (update.doc_scope !== void 0) {
7153
+ docScope = validateAndNormalize(update.doc_scope, "doc_scope", { requireNonEmpty: true });
7154
+ }
7155
+ if (update.ignore_code_scope !== void 0) {
7156
+ ignoreCodeScope = validateAndNormalize(update.ignore_code_scope, "ignore_code_scope", {
7157
+ requireNonEmpty: false
7158
+ });
7159
+ }
7160
+ if (docScope.length === 0) {
7161
+ throw new ConfigValidationError(
7162
+ `${DELFINI_CONFIG_RELATIVE_PATH}: doc_scope requires at least one path`
7163
+ );
7164
+ }
7165
+ const payload = {
7166
+ version: DELFINI_CONFIG_VERSION,
7167
+ doc_scope: docScope
7168
+ };
7169
+ if (ignoreCodeScope.length > 0) {
7170
+ payload.ignore_code_scope = ignoreCodeScope;
7171
+ }
7172
+ const target = import_node_path.default.join(root, DELFINI_CONFIG_RELATIVE_PATH);
7173
+ await import_node_fs.promises.mkdir(import_node_path.default.dirname(target), { recursive: true });
7174
+ await import_node_fs.promises.writeFile(target, `${JSON.stringify(payload, null, 2)}
7175
+ `, "utf8");
7176
+ await removeLegacyDocScope(root);
7177
+ }
7178
+ async function writeDocScope(paths, options) {
7114
7179
  if (!Array.isArray(paths) || paths.length === 0) {
7115
- throw new DocScopeValidationError("at least one path is required");
7180
+ throw new ConfigValidationError("at least one path is required");
7116
7181
  }
7182
+ await writeConfig({ doc_scope: paths }, options);
7183
+ }
7184
+ function validateAndNormalize(paths, field, opts) {
7117
7185
  const errors = [];
7118
7186
  for (const entry of paths) {
7119
7187
  const err = validateDocScopeEntry(entry, REPO_ROOT_REL);
7120
7188
  if (err !== null) errors.push(err);
7121
7189
  }
7122
7190
  if (errors.length > 0) {
7123
- throw new DocScopeValidationError(
7124
- `${DOC_SCOPE_RELATIVE_PATH}: invalid path(s):
7191
+ throw new ConfigValidationError(
7192
+ `${DELFINI_CONFIG_RELATIVE_PATH}: invalid ${field} path(s):
7125
7193
  ${errors.map((e) => ` - ${e}`).join("\n")}`
7126
7194
  );
7127
7195
  }
7128
7196
  const normalised = normalizeDocScope(paths);
7129
- if (normalised.length === 0) {
7130
- throw new DocScopeValidationError(
7131
- `${DOC_SCOPE_RELATIVE_PATH}: every entry collapses to an empty scope after normalisation (e.g. '.', './', 'docs/..') \u2014 provide at least one concrete path`
7197
+ if (opts.requireNonEmpty && normalised.length === 0) {
7198
+ throw new ConfigValidationError(
7199
+ `${DELFINI_CONFIG_RELATIVE_PATH}: every ${field} entry collapses to an empty scope after normalisation (e.g. '.', './', 'docs/..') \u2014 provide at least one concrete path`
7132
7200
  );
7133
7201
  }
7134
- const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7135
- await import_node_fs.promises.mkdir(import_node_path.default.dirname(target), { recursive: true });
7136
- const payload = { version: DOC_SCOPE_VERSION, doc_scope: normalised };
7137
- const json = `${JSON.stringify(payload, null, 2)}
7138
- `;
7139
- await import_node_fs.promises.writeFile(target, json, "utf8");
7202
+ return normalised;
7203
+ }
7204
+ async function removeLegacyDocScope(root) {
7205
+ const legacy = import_node_path.default.join(root, LEGACY_DOC_SCOPE_RELATIVE_PATH);
7206
+ try {
7207
+ await import_node_fs.promises.unlink(legacy);
7208
+ } catch (err) {
7209
+ if (!isNoEntError(err)) throw err;
7210
+ }
7140
7211
  }
7141
- async function docScopeExists(repoRoot) {
7212
+ async function configExists(repoRoot) {
7142
7213
  const root = repoRoot ?? await getRepoRoot();
7143
- const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7214
+ return await isFile(import_node_path.default.join(root, DELFINI_CONFIG_RELATIVE_PATH)) || await isFile(import_node_path.default.join(root, LEGACY_DOC_SCOPE_RELATIVE_PATH));
7215
+ }
7216
+ async function isFile(target) {
7144
7217
  try {
7145
7218
  const st = await import_node_fs.promises.stat(target);
7146
7219
  return st.isFile();
@@ -7148,13 +7221,14 @@ async function docScopeExists(repoRoot) {
7148
7221
  return false;
7149
7222
  }
7150
7223
  }
7151
- async function deleteDocScope(repoRoot) {
7224
+ async function deleteConfig(repoRoot) {
7152
7225
  const root = repoRoot ?? await getRepoRoot();
7153
- const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7154
- try {
7155
- await import_node_fs.promises.unlink(target);
7156
- } catch (err) {
7157
- if (!isNoEntError(err)) throw err;
7226
+ for (const rel of [DELFINI_CONFIG_RELATIVE_PATH, LEGACY_DOC_SCOPE_RELATIVE_PATH]) {
7227
+ try {
7228
+ await import_node_fs.promises.unlink(import_node_path.default.join(root, rel));
7229
+ } catch (err) {
7230
+ if (!isNoEntError(err)) throw err;
7231
+ }
7158
7232
  }
7159
7233
  }
7160
7234
  async function expandDocScope(paths, repoRoot) {
@@ -7413,24 +7487,24 @@ function sanitiseScope(paths) {
7413
7487
  return paths.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
7414
7488
  }
7415
7489
  async function applyDocScope(repoRoot, logger, provideDocScope) {
7416
- const target = (0, import_node_path3.join)(repoRoot, DOC_SCOPE_RELATIVE_PATH);
7490
+ const target = (0, import_node_path3.join)(repoRoot, DELFINI_CONFIG_RELATIVE_PATH);
7417
7491
  if (provideDocScope) {
7418
7492
  const paths2 = sanitiseScope(await provideDocScope());
7419
7493
  if (paths2.length === 0) {
7420
- log(logger, `doc-scope.json \u2192 ${target} (no paths provided, no change)`);
7494
+ log(logger, `delfini-config.json \u2192 ${target} (no paths provided, no change)`);
7421
7495
  return;
7422
7496
  }
7423
7497
  await persistDocScope(repoRoot, logger, target, paths2);
7424
7498
  return;
7425
7499
  }
7426
- if (await docScopeExists(repoRoot)) {
7427
- log(logger, `doc-scope.json \u2192 ${target} (already configured, no change)`);
7500
+ if (await configExists(repoRoot)) {
7501
+ log(logger, `delfini-config.json \u2192 ${target} (already configured, no change)`);
7428
7502
  return;
7429
7503
  }
7430
7504
  if (!process.stdin.isTTY) {
7431
7505
  log(
7432
7506
  logger,
7433
- `doc-scope.json \u2192 ${target} (non-interactive shell: scope prompt skipped, no change)`
7507
+ `delfini-config.json \u2192 ${target} (non-interactive shell: scope prompt skipped, no change)`
7434
7508
  );
7435
7509
  return;
7436
7510
  }
@@ -7438,7 +7512,7 @@ async function applyDocScope(repoRoot, logger, provideDocScope) {
7438
7512
  if (paths.length === 0) {
7439
7513
  log(
7440
7514
  logger,
7441
- `doc-scope.json \u2192 ${target} (no paths provided, no change \u2014 first /delfini run will prompt)`
7515
+ `delfini-config.json \u2192 ${target} (no paths provided, no change \u2014 first /delfini run will prompt)`
7442
7516
  );
7443
7517
  return;
7444
7518
  }
@@ -7458,12 +7532,12 @@ async function promptDocScope() {
7458
7532
  async function persistDocScope(repoRoot, logger, target, paths) {
7459
7533
  try {
7460
7534
  await writeDocScope(paths, { repoRoot });
7461
- log(logger, `doc-scope.json \u2192 ${target} (wrote ${paths.length} path(s))`);
7535
+ log(logger, `delfini-config.json \u2192 ${target} (wrote ${paths.length} path(s))`);
7462
7536
  } catch (err) {
7463
- if (err instanceof DocScopeValidationError) {
7537
+ if (err instanceof ConfigValidationError) {
7464
7538
  log(
7465
7539
  logger,
7466
- `doc-scope.json \u2192 ${target} (skipped \u2014 ${err.message}). Fix the path(s) and re-run \`delfini install\`, edit the file directly, or set the scope on the first /delfini run.`
7540
+ `delfini-config.json \u2192 ${target} (skipped \u2014 ${err.message}). Fix the path(s) and re-run \`delfini install\`, edit the file directly, or set the scope on the first /delfini run.`
7467
7541
  );
7468
7542
  return;
7469
7543
  }
@@ -7889,13 +7963,15 @@ async function runLocalPrepare(options = {}) {
7889
7963
  );
7890
7964
  }
7891
7965
  const repoRoot = options.repoRoot ?? await getRepoRoot();
7892
- const scopePaths = await resolveScopePaths(options.scope, repoRoot);
7966
+ const config = await readConfig(repoRoot);
7967
+ const scopePaths = resolveScopePaths(options.scope, config);
7893
7968
  if (scopePaths === null) {
7894
7969
  stderr.write(
7895
- "No doc-scope configured. Pass `--scope <paths>` or run the skill\nfirst-run setup to create `.claude/skills/delfini/doc-scope.json`.\n"
7970
+ "No doc-scope configured. Pass `--scope <paths>` or run the skill\nfirst-run setup to create `.claude/skills/delfini/delfini-config.json`.\n"
7896
7971
  );
7897
7972
  return 2;
7898
7973
  }
7974
+ const ignoreCodeScope = resolveIgnoreCodeScope(options.ignoreCodeScope, config);
7899
7975
  const expansion = await expandDocScope(scopePaths, repoRoot);
7900
7976
  for (const missing of expansion.missingPaths) {
7901
7977
  stderr.write(formatMissingPathWarning(missing));
@@ -7905,8 +7981,12 @@ async function runLocalPrepare(options = {}) {
7905
7981
  const rawDiff = await computeDiff(git, repoRoot, baseRef, diffSource);
7906
7982
  let diff = rawDiff;
7907
7983
  let filterResult = null;
7908
- if (options.enableDiffPreFilter === true) {
7909
- filterResult = filterDiff(rawDiff);
7984
+ const enableBuiltins = options.enableDiffPreFilter === true;
7985
+ if (enableBuiltins || ignoreCodeScope.length > 0) {
7986
+ filterResult = filterDiff(rawDiff, {
7987
+ builtins: enableBuiltins,
7988
+ ignorePaths: ignoreCodeScope
7989
+ });
7910
7990
  diff = filterResult.keptDiff;
7911
7991
  }
7912
7992
  const docs = await readDocs(expansion.files, repoRoot, stderr);
@@ -7955,7 +8035,7 @@ async function runLocalPrepare(options = {}) {
7955
8035
  `);
7956
8036
  return 0;
7957
8037
  }
7958
- async function resolveScopePaths(scopeOption, repoRoot) {
8038
+ function resolveScopePaths(scopeOption, config) {
7959
8039
  if (scopeOption !== void 0) {
7960
8040
  const normalised = normaliseScopeOption(scopeOption);
7961
8041
  if (normalised.length === 0) {
@@ -7963,11 +8043,16 @@ async function resolveScopePaths(scopeOption, repoRoot) {
7963
8043
  }
7964
8044
  return normalised;
7965
8045
  }
7966
- const persisted = await readDocScope(repoRoot);
7967
- if (persisted === null) {
8046
+ if (config === null) {
7968
8047
  return null;
7969
8048
  }
7970
- return persisted.doc_scope;
8049
+ return config.doc_scope;
8050
+ }
8051
+ function resolveIgnoreCodeScope(ignoreOption, config) {
8052
+ if (ignoreOption !== void 0) {
8053
+ return normaliseScopeOption(ignoreOption);
8054
+ }
8055
+ return config?.ignore_code_scope ?? [];
7971
8056
  }
7972
8057
  function normaliseScopeOption(scope) {
7973
8058
  const raw = Array.isArray(scope) ? scope : scope.split(",");
@@ -8167,7 +8252,7 @@ function readPackageJson() {
8167
8252
  }
8168
8253
  async function main(argv) {
8169
8254
  const program = new import_commander.Command();
8170
- program.name("delfini").description("Delfini Skill CLI \u2014 deterministic, never calls an LLM.").version(pkg.version, "-V, --version", "print the @delfini/cli version").option("--reset-scope", "delete the persisted doc-scope.json").exitOverride();
8255
+ program.name("delfini").description("Delfini Skill CLI \u2014 deterministic, never calls an LLM.").version(pkg.version, "-V, --version", "print the @delfini/cli version").option("--reset-scope", "delete the persisted delfini-config.json (and any legacy doc-scope.json)").exitOverride();
8171
8256
  program.action(async (opts) => {
8172
8257
  if (opts.resetScope) {
8173
8258
  await handleResetScope();
@@ -8177,7 +8262,7 @@ async function main(argv) {
8177
8262
  "Scaffold .claude/skills/delfini/SKILL.md + CLAUDE.md auto-invoke + .gitignore append"
8178
8263
  ).option("--tool <agent>", "Coding agent target (only 'CLAUDE' supported in V1)", "CLAUDE").option("--auto-invoke", "append the CLAUDE.md auto-invoke block without prompting").option("--no-auto-invoke", "strip the CLAUDE.md auto-invoke block without prompting").option(
8179
8264
  "--scope <paths>",
8180
- "Seed doc-scope.json with these paths (space- or comma-separated; overwrites any existing scope) without prompting. Omit to be prompted interactively on a TTY."
8265
+ "Seed delfini-config.json doc_scope with these paths (space- or comma-separated; overwrites any existing scope) without prompting. Omit to be prompted interactively on a TTY."
8181
8266
  ).action(
8182
8267
  async (targetPath, opts) => {
8183
8268
  const confirmAutoInvoke = opts.autoInvoke === void 0 ? void 0 : () => Promise.resolve(opts.autoInvoke);
@@ -8187,7 +8272,10 @@ async function main(argv) {
8187
8272
  );
8188
8273
  program.command("local-prepare").description(
8189
8274
  "Compute diff + doc-scope + prompt + token-budget gate; write .delfini-trace/"
8190
- ).option("--scope <paths>", "Comma-separated doc-scope paths (overrides doc-scope.json)").option("--base <ref>", "Diff base ref (default: git merge-base HEAD origin/main)").option(
8275
+ ).option("--scope <paths>", "Comma-separated doc-scope paths (overrides delfini-config.json doc_scope)").option(
8276
+ "--ignore-code-scope <paths>",
8277
+ "Comma-separated code paths whose changes are ignored for analysis (overrides delfini-config.json ignore_code_scope). Each entry is a directory, file, or glob."
8278
+ ).option("--base <ref>", "Diff base ref (default: git merge-base HEAD origin/main)").option(
8191
8279
  "--diff-source <source>",
8192
8280
  "Which diff to analyse: 'local' (default), 'committed', or 'both'",
8193
8281
  "local"
@@ -8216,6 +8304,7 @@ async function main(argv) {
8216
8304
  async (opts) => {
8217
8305
  const exitCode = await runLocalPrepare({
8218
8306
  scope: opts.scope,
8307
+ ignoreCodeScope: opts.ignoreCodeScope,
8219
8308
  base: opts.base,
8220
8309
  diffSource: opts.diffSource,
8221
8310
  relevanceThreshold: opts.relevanceThreshold,
@@ -8245,7 +8334,7 @@ async function main(argv) {
8245
8334
  }
8246
8335
  async function handleResetScope() {
8247
8336
  try {
8248
- await deleteDocScope();
8337
+ await deleteConfig();
8249
8338
  } catch (err) {
8250
8339
  if (err instanceof RepoRootNotFoundError) {
8251
8340
  return;
@@ -8255,26 +8344,28 @@ async function handleResetScope() {
8255
8344
  }
8256
8345
  // Annotate the CommonJS export names for ESM import in node:
8257
8346
  0 && (module.exports = {
8258
- DOC_SCOPE_RELATIVE_PATH,
8259
- DOC_SCOPE_VERSION,
8260
- DocScopeCorruptError,
8261
- DocScopeValidationError,
8262
- DocScopeVersionMismatchError,
8347
+ ConfigCorruptError,
8348
+ ConfigValidationError,
8349
+ ConfigVersionMismatchError,
8350
+ DELFINI_CONFIG_RELATIVE_PATH,
8351
+ DELFINI_CONFIG_VERSION,
8263
8352
  InstallToolNotSupportedError,
8353
+ LEGACY_DOC_SCOPE_RELATIVE_PATH,
8264
8354
  PROMPT_TOKEN_BUDGET,
8265
8355
  RepoRootNotFoundError,
8266
8356
  appendToGitignore,
8267
- deleteDocScope,
8268
- docScopeExists,
8357
+ configExists,
8358
+ deleteConfig,
8269
8359
  ensureTraceDir,
8270
8360
  expandDocScope,
8271
8361
  getRepoRoot,
8272
8362
  main,
8273
- readDocScope,
8363
+ readConfig,
8274
8364
  runDiffStatus,
8275
8365
  runInstall,
8276
8366
  runLocalFinalize,
8277
8367
  runLocalPrepare,
8368
+ writeConfig,
8278
8369
  writeDocScope,
8279
8370
  writeRetryAttemptFile,
8280
8371
  writeTraceFile
package/dist/index.d.cts CHANGED
@@ -1,36 +1,78 @@
1
1
  export { main } from './cli.cjs';
2
2
 
3
- declare const DOC_SCOPE_RELATIVE_PATH = ".claude/skills/delfini/doc-scope.json";
4
- declare const DOC_SCOPE_VERSION: 1;
5
- interface DocScope {
3
+ declare const DELFINI_CONFIG_RELATIVE_PATH = ".claude/skills/delfini/delfini-config.json";
4
+ declare const LEGACY_DOC_SCOPE_RELATIVE_PATH = ".claude/skills/delfini/doc-scope.json";
5
+ declare const DELFINI_CONFIG_VERSION: 1;
6
+ interface DelfiniConfig {
6
7
  version: 1;
7
8
  doc_scope: string[];
9
+ /** Always present after `readConfig` (defaulted to `[]` when absent on disk). */
10
+ ignore_code_scope: string[];
8
11
  }
9
- interface DocScopeWriteOptions {
12
+ interface ConfigWriteOptions {
10
13
  repoRoot?: string;
11
14
  }
15
+ /** Partial update applied on top of any existing config by `writeConfig`. */
16
+ interface ConfigUpdate {
17
+ /** Replace `doc_scope`. Must be non-empty after normalisation. */
18
+ doc_scope?: string[];
19
+ /** Replace `ignore_code_scope`. May be empty (means "ignore nothing"). */
20
+ ignore_code_scope?: string[];
21
+ }
12
22
  interface DocScopeExpansionResult {
13
23
  /** Absolute paths to files matched by the scope entries. Sorted, deduped. */
14
24
  files: string[];
15
25
  /** Original entries from `paths` that resolved to nothing on disk. */
16
26
  missingPaths: string[];
17
27
  }
18
- declare class DocScopeVersionMismatchError extends Error {
19
- readonly code: "DOC_SCOPE_VERSION_MISMATCH";
28
+ declare class ConfigVersionMismatchError extends Error {
29
+ readonly code: "CONFIG_VERSION_MISMATCH";
20
30
  constructor(message?: string);
21
31
  }
22
- declare class DocScopeCorruptError extends Error {
23
- readonly code: "DOC_SCOPE_CORRUPT";
32
+ declare class ConfigCorruptError extends Error {
33
+ readonly code: "CONFIG_CORRUPT";
24
34
  constructor(message: string);
25
35
  }
26
- declare class DocScopeValidationError extends Error {
27
- readonly code: "DOC_SCOPE_VALIDATION";
36
+ declare class ConfigValidationError extends Error {
37
+ readonly code: "CONFIG_VALIDATION";
28
38
  constructor(message: string);
29
39
  }
30
- declare function readDocScope(repoRoot?: string): Promise<DocScope | null>;
31
- declare function writeDocScope(paths: string[], options?: DocScopeWriteOptions): Promise<void>;
32
- declare function docScopeExists(repoRoot?: string): Promise<boolean>;
33
- declare function deleteDocScope(repoRoot?: string): Promise<void>;
40
+ /**
41
+ * Read the effective Delfini config, or null if none is configured.
42
+ *
43
+ * Resolution order: `delfini-config.json` first, then a legacy
44
+ * `doc-scope.json` fallback (read-only — migration to the new filename happens
45
+ * on the next `writeConfig`). `ignore_code_scope` is defaulted to `[]` when
46
+ * absent so callers never branch on undefined.
47
+ */
48
+ declare function readConfig(repoRoot?: string): Promise<DelfiniConfig | null>;
49
+ /**
50
+ * Persist a partial config update, merging over any existing config and
51
+ * preserving the scope not being edited. Writes `delfini-config.json` and
52
+ * removes a legacy `doc-scope.json` if present (one-time rename migration).
53
+ *
54
+ * `doc_scope` is required to be non-empty in the final config (a Delfini run
55
+ * with no docs is meaningless); `ignore_code_scope` may be empty.
56
+ */
57
+ declare function writeConfig(update: ConfigUpdate, options?: ConfigWriteOptions): Promise<void>;
58
+ /**
59
+ * Persist the doc scope, preserving any existing `ignore_code_scope`. Thin
60
+ * wrapper over `writeConfig` kept for the install / first-run callers whose
61
+ * only job is to seed the docs Delfini tracks.
62
+ */
63
+ declare function writeDocScope(paths: string[], options?: ConfigWriteOptions): Promise<void>;
64
+ /**
65
+ * True iff a Delfini config exists — the new `delfini-config.json` OR a legacy
66
+ * `doc-scope.json`. Used by `delfini install` to avoid clobbering an existing
67
+ * committed, team-shared config.
68
+ */
69
+ declare function configExists(repoRoot?: string): Promise<boolean>;
70
+ /**
71
+ * Delete the Delfini config — both the new `delfini-config.json` and any legacy
72
+ * `doc-scope.json` (`delfini --reset-scope`). Idempotent: absent files are a
73
+ * silent no-op.
74
+ */
75
+ declare function deleteConfig(repoRoot?: string): Promise<void>;
34
76
  declare function expandDocScope(paths: string[], repoRoot?: string): Promise<DocScopeExpansionResult>;
35
77
 
36
78
  declare class RepoRootNotFoundError extends Error {
@@ -66,11 +108,11 @@ interface RunInstallOptions {
66
108
  /**
67
109
  * Resolves the doc-scope path list. When provided, `runInstall` uses it
68
110
  * directly (the `--scope` CLI flag and the test seam) and never prompts —
69
- * a non-empty list is persisted to `doc-scope.json` (overwriting any
111
+ * a non-empty list is persisted to `delfini-config.json` doc_scope (overwriting any
70
112
  * existing file, since an explicit `--scope` is intent-to-overwrite); an
71
113
  * empty list is a no-op. When omitted, `runInstall` prompts interactively
72
- * on a TTY only if no `doc-scope.json` exists yet; on a non-TTY stdin, or
73
- * when a scope is already configured, it leaves `doc-scope.json` untouched.
114
+ * on a TTY only if no config exists yet; on a non-TTY stdin, or
115
+ * when a scope is already configured, it leaves the config untouched.
74
116
  * Invalid paths (rejected by `writeDocScope`) warn-and-skip — the scaffold
75
117
  * always completes; the SKILL.md first-run prompt re-seeds the scope later.
76
118
  */
@@ -88,10 +130,18 @@ declare const PROMPT_TOKEN_BUDGET = 150000;
88
130
  interface RunLocalPrepareOptions {
89
131
  /**
90
132
  * The `--scope <paths>` value. Accepts a comma-separated string OR a string[].
91
- * When provided, overrides the persisted `.claude/skills/delfini/doc-scope.json`
133
+ * When provided, overrides the persisted `delfini-config.json` doc_scope
92
134
  * WITHOUT modifying that file (FR144 per-run override invariant).
93
135
  */
94
136
  scope?: string | string[];
137
+ /**
138
+ * The `--ignore-code-scope <paths>` value. Accepts a comma-separated string
139
+ * OR a string[]. When provided, overrides the persisted `delfini-config.json`
140
+ * `ignore_code_scope` WITHOUT modifying that file (per-run override). Changed
141
+ * files matching any entry are dropped from the analysed diff. Empty/omitted
142
+ * → use the persisted `ignore_code_scope` (or none).
143
+ */
144
+ ignoreCodeScope?: string | string[];
95
145
  /**
96
146
  * The `--base <ref>` value. When provided, used as the diff base directly.
97
147
  * When omitted, defaults to `git merge-base HEAD origin/main`.
@@ -210,4 +260,4 @@ interface RunLocalFinalizeOptions {
210
260
  */
211
261
  declare function runLocalFinalize(options: RunLocalFinalizeOptions): Promise<number>;
212
262
 
213
- export { DOC_SCOPE_RELATIVE_PATH, DOC_SCOPE_VERSION, type DiffSource, type DiffStatus, type DocScope, DocScopeCorruptError, type DocScopeExpansionResult, DocScopeValidationError, DocScopeVersionMismatchError, type DocScopeWriteOptions, type InstallLogger, InstallToolNotSupportedError, PROMPT_TOKEN_BUDGET, RepoRootNotFoundError, type RunDiffStatusOptions, type RunInstallOptions, type RunLocalFinalizeOptions, type RunLocalPrepareOptions, appendToGitignore, deleteDocScope, docScopeExists, ensureTraceDir, expandDocScope, getRepoRoot, readDocScope, runDiffStatus, runInstall, runLocalFinalize, runLocalPrepare, writeDocScope, writeRetryAttemptFile, writeTraceFile };
263
+ export { ConfigCorruptError, type ConfigUpdate, ConfigValidationError, ConfigVersionMismatchError, type ConfigWriteOptions, DELFINI_CONFIG_RELATIVE_PATH, DELFINI_CONFIG_VERSION, type DelfiniConfig, type DiffSource, type DiffStatus, type DocScopeExpansionResult, type InstallLogger, InstallToolNotSupportedError, LEGACY_DOC_SCOPE_RELATIVE_PATH, PROMPT_TOKEN_BUDGET, RepoRootNotFoundError, type RunDiffStatusOptions, type RunInstallOptions, type RunLocalFinalizeOptions, type RunLocalPrepareOptions, appendToGitignore, configExists, deleteConfig, ensureTraceDir, expandDocScope, getRepoRoot, readConfig, runDiffStatus, runInstall, runLocalFinalize, runLocalPrepare, writeConfig, writeDocScope, writeRetryAttemptFile, writeTraceFile };