@delfini/cli 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -33,10 +33,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  ));
34
34
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
35
 
36
- // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.9_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
36
+ // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.15_tsx@4.22.4_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
37
37
  var getImportMetaUrl, importMetaUrl;
38
38
  var init_cjs_shims = __esm({
39
- "../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.9_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js"() {
39
+ "../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.15_tsx@4.22.4_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js"() {
40
40
  "use strict";
41
41
  getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
42
42
  importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
@@ -1957,231 +1957,16 @@ function describeError(err) {
1957
1957
 
1958
1958
  // src/commands/install.ts
1959
1959
  init_cjs_shims();
1960
- var import_node_fs2 = require("fs");
1961
- var import_node_path2 = require("path");
1960
+ var import_node_fs3 = require("fs");
1961
+ var import_node_path3 = require("path");
1962
1962
  var import_promises = require("readline/promises");
1963
1963
  var import_node_url = require("url");
1964
1964
 
1965
- // src/trace.ts
1965
+ // src/doc-scope.ts
1966
1966
  init_cjs_shims();
1967
1967
  var import_node_fs = require("fs");
1968
- var import_node_path = require("path");
1969
- var TRACE_DIR_NAME = ".delfini-trace";
1970
- var GITIGNORE_LINE = ".delfini-trace/";
1971
- function ensureTraceDir(repoRoot) {
1972
- const tracePath = (0, import_node_path.join)(repoRoot, TRACE_DIR_NAME);
1973
- if ((0, import_node_fs.existsSync)(tracePath)) {
1974
- const stat = (0, import_node_fs.statSync)(tracePath);
1975
- if (!stat.isDirectory()) {
1976
- throw new Error(
1977
- `ensureTraceDir: ${tracePath} exists but is not a directory. Remove or rename it so the CLI can create the trace directory.`
1978
- );
1979
- }
1980
- return tracePath;
1981
- }
1982
- (0, import_node_fs.mkdirSync)(tracePath, { recursive: true });
1983
- return tracePath;
1984
- }
1985
- function appendToGitignore(repoRoot) {
1986
- const gitignorePath = (0, import_node_path.join)(repoRoot, ".gitignore");
1987
- const existed = (0, import_node_fs.existsSync)(gitignorePath);
1988
- const existingContent = existed ? (0, import_node_fs.readFileSync)(gitignorePath, "utf8") : "";
1989
- const lines = existingContent.split(/\r?\n/);
1990
- for (const line of lines) {
1991
- if (line.trim() === GITIGNORE_LINE) {
1992
- return { changed: false };
1993
- }
1994
- }
1995
- const useCRLF = existingContent.includes("\r\n");
1996
- const newline = useCRLF ? "\r\n" : "\n";
1997
- if (!existed || existingContent.length === 0) {
1998
- (0, import_node_fs.writeFileSync)(gitignorePath, `${GITIGNORE_LINE}${newline}`);
1999
- return { changed: true };
2000
- }
2001
- const endsWithNewline = existingContent.endsWith("\n");
2002
- const prefix = endsWithNewline ? "" : newline;
2003
- (0, import_node_fs.appendFileSync)(gitignorePath, `${prefix}${GITIGNORE_LINE}${newline}`);
2004
- return { changed: true };
2005
- }
2006
- function writeTraceFile(repoRoot, filename, content) {
2007
- validateBasename(filename);
2008
- const traceDir = ensureTraceDir(repoRoot);
2009
- const target = (0, import_node_path.join)(traceDir, filename);
2010
- (0, import_node_fs.writeFileSync)(target, content);
2011
- return target;
2012
- }
2013
- function validateBasename(filename) {
2014
- if (filename.length === 0) {
2015
- throw new Error("writeTraceFile: filename must not be empty");
2016
- }
2017
- if (filename === "." || filename === "..") {
2018
- throw new Error(`writeTraceFile: filename must not be "${filename}"`);
2019
- }
2020
- if (filename.includes("/") || filename.includes("\\")) {
2021
- throw new Error(
2022
- `writeTraceFile: filename must be a basename without path separators, got "${filename}"`
2023
- );
2024
- }
2025
- }
2026
-
2027
- // src/commands/install.ts
2028
- var InstallToolNotSupportedError = class extends Error {
2029
- code = "INSTALL_TOOL_NOT_SUPPORTED";
2030
- constructor(tool) {
2031
- super(
2032
- `delfini install: --tool '${tool}' is not supported. The Skill is Claude-only in V1 (design-spec NG2 / project-context "Skill \u2014 Out of Scope in V1 / V1.1: No multi-LLM-provider support \u2014 Claude-only by design"). Use --tool CLAUDE (the default).`
2033
- );
2034
- this.name = "InstallToolNotSupportedError";
2035
- }
2036
- };
2037
- var CLAUDE_MD_OPEN_MARKER = "<!-- delfini:auto-invoke-block-v1 -->";
2038
- var CLAUDE_MD_CLOSE_MARKER = "<!-- /delfini:auto-invoke-block-v1 -->";
2039
- var SKILL_RELATIVE_PATH = ".claude/skills/delfini/SKILL.md";
2040
- var CLAUDE_MD_FILENAME = "CLAUDE.md";
2041
- var SUPPORTED_TOOL = "CLAUDE";
2042
- var TEMPLATES_DIR = resolveTemplatesDir();
2043
- function resolveTemplatesDir() {
2044
- let dir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
2045
- for (let i = 0; i < 5; i++) {
2046
- const candidate = (0, import_node_path2.resolve)(dir, "templates");
2047
- if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(candidate, "SKILL.md"))) {
2048
- return candidate;
2049
- }
2050
- const parent = (0, import_node_path2.dirname)(dir);
2051
- if (parent === dir) break;
2052
- dir = parent;
2053
- }
2054
- throw new Error("templates/ directory not found relative to the CLI module");
2055
- }
2056
- async function runInstall(targetPath, options) {
2057
- const tool = options?.tool ?? SUPPORTED_TOOL;
2058
- const logger = options?.logger ?? console;
2059
- if (tool !== SUPPORTED_TOOL) {
2060
- throw new InstallToolNotSupportedError(tool);
2061
- }
2062
- const resolvedTarget = (0, import_node_path2.resolve)(process.cwd(), targetPath);
2063
- const repoRoot = await getRepoRoot(resolvedTarget);
2064
- writeSkillTemplate(repoRoot, logger);
2065
- await applyAutoInvokeDecision(repoRoot, logger, options?.confirmAutoInvoke);
2066
- appendGitignoreLine(repoRoot, logger);
2067
- }
2068
- function parseYesNo(answer) {
2069
- const normalised = answer.trim().toLowerCase();
2070
- return normalised === "y" || normalised === "yes";
2071
- }
2072
- async function applyAutoInvokeDecision(repoRoot, logger, confirmAutoInvoke) {
2073
- const decision = await resolveAutoInvoke(confirmAutoInvoke);
2074
- if (decision === "yes") {
2075
- appendClaudeMdBlock(repoRoot, logger);
2076
- } else if (decision === "no") {
2077
- stripClaudeMdBlock(repoRoot, logger);
2078
- } else {
2079
- const target = (0, import_node_path2.join)(repoRoot, CLAUDE_MD_FILENAME);
2080
- log(logger, `CLAUDE.md \u2192 ${target} (non-interactive shell: auto-invoke prompt skipped, no change)`);
2081
- }
2082
- }
2083
- async function resolveAutoInvoke(confirmAutoInvoke) {
2084
- if (confirmAutoInvoke) {
2085
- return await confirmAutoInvoke() ? "yes" : "no";
2086
- }
2087
- if (!process.stdin.isTTY) {
2088
- return "skip";
2089
- }
2090
- return await promptAutoInvoke() ? "yes" : "no";
2091
- }
2092
- async function promptAutoInvoke() {
2093
- const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
2094
- try {
2095
- const answer = await rl.question("Auto-invoke /delfini on PR creation? (y/n) ");
2096
- return parseYesNo(answer);
2097
- } finally {
2098
- rl.close();
2099
- }
2100
- }
2101
- function writeSkillTemplate(repoRoot, logger) {
2102
- const target = (0, import_node_path2.join)(repoRoot, SKILL_RELATIVE_PATH);
2103
- const templateSource = (0, import_node_path2.join)(TEMPLATES_DIR, "SKILL.md");
2104
- const content = (0, import_node_fs2.readFileSync)(templateSource);
2105
- (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(target), { recursive: true });
2106
- (0, import_node_fs2.writeFileSync)(target, content);
2107
- log(logger, `SKILL.md \u2192 ${target}`);
2108
- }
2109
- function appendClaudeMdBlock(repoRoot, logger) {
2110
- const target = (0, import_node_path2.join)(repoRoot, CLAUDE_MD_FILENAME);
2111
- const blockSource = (0, import_node_path2.join)(TEMPLATES_DIR, "claude-md-append-block.txt");
2112
- const body = (0, import_node_fs2.readFileSync)(blockSource, "utf8").replace(/\s+$/u, "");
2113
- const block = `${CLAUDE_MD_OPEN_MARKER}
2114
- ${body}
2115
- ${CLAUDE_MD_CLOSE_MARKER}
2116
- `;
2117
- if (!(0, import_node_fs2.existsSync)(target)) {
2118
- (0, import_node_fs2.writeFileSync)(target, block);
2119
- log(logger, `CLAUDE.md \u2192 ${target} (created)`);
2120
- return;
2121
- }
2122
- const existing = (0, import_node_fs2.readFileSync)(target, "utf8");
2123
- if (existing.includes(CLAUDE_MD_OPEN_MARKER)) {
2124
- log(logger, `CLAUDE.md \u2192 ${target} (block already present, no change)`);
2125
- return;
2126
- }
2127
- if (existing.length === 0) {
2128
- (0, import_node_fs2.writeFileSync)(target, block);
2129
- log(logger, `CLAUDE.md \u2192 ${target} (block appended)`);
2130
- return;
2131
- }
2132
- const needsLeadingNewline = !existing.endsWith("\n");
2133
- const prefix = needsLeadingNewline ? "\n" : "";
2134
- (0, import_node_fs2.writeFileSync)(target, `${existing}${prefix}${block}`);
2135
- log(logger, `CLAUDE.md \u2192 ${target} (block appended)`);
2136
- }
2137
- function stripClaudeMdBlock(repoRoot, logger) {
2138
- const target = (0, import_node_path2.join)(repoRoot, CLAUDE_MD_FILENAME);
2139
- if (!(0, import_node_fs2.existsSync)(target)) {
2140
- log(logger, `CLAUDE.md \u2192 ${target} (no block to remove, no change)`);
2141
- return;
2142
- }
2143
- const existing = (0, import_node_fs2.readFileSync)(target, "utf8");
2144
- const openIdx = existing.indexOf(CLAUDE_MD_OPEN_MARKER);
2145
- if (openIdx === -1) {
2146
- log(logger, `CLAUDE.md \u2192 ${target} (no block to remove, no change)`);
2147
- return;
2148
- }
2149
- const afterOpen = openIdx + CLAUDE_MD_OPEN_MARKER.length;
2150
- const closeIdx = existing.indexOf(CLAUDE_MD_CLOSE_MARKER, afterOpen);
2151
- let endIdx;
2152
- if (closeIdx === -1) {
2153
- endIdx = existing.length;
2154
- } else {
2155
- endIdx = closeIdx + CLAUDE_MD_CLOSE_MARKER.length;
2156
- if (existing.startsWith("\r\n", endIdx)) {
2157
- endIdx += 2;
2158
- } else if (existing[endIdx] === "\n") {
2159
- endIdx += 1;
2160
- }
2161
- }
2162
- const result = existing.slice(0, openIdx) + existing.slice(endIdx);
2163
- (0, import_node_fs2.writeFileSync)(target, result);
2164
- log(logger, `CLAUDE.md \u2192 ${target} (block removed)`);
2165
- }
2166
- function appendGitignoreLine(repoRoot, logger) {
2167
- const target = (0, import_node_path2.join)(repoRoot, ".gitignore");
2168
- const { changed } = appendToGitignore(repoRoot);
2169
- if (changed) {
2170
- log(logger, `.gitignore \u2192 ${target} (appended .delfini-trace/)`);
2171
- } else {
2172
- log(logger, `.gitignore \u2192 ${target} (.delfini-trace/ already present, no change)`);
2173
- }
2174
- }
2175
- function log(logger, message) {
2176
- if (typeof logger.log === "function") {
2177
- logger.log(message);
2178
- }
2179
- }
2180
-
2181
- // src/commands/local-finalize.ts
2182
- init_cjs_shims();
2183
- var import_node_fs3 = require("fs");
2184
- var import_node_path3 = __toESM(require("path"), 1);
1968
+ var import_node_path = __toESM(require("path"), 1);
1969
+ var import_tinyglobby = require("tinyglobby");
2185
1970
 
2186
1971
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js
2187
1972
  init_cjs_shims();
@@ -7299,54 +7084,527 @@ function mostCommonHunkReason(droppedHunks) {
7299
7084
  return bestReason;
7300
7085
  }
7301
7086
 
7302
- // src/commands/local-finalize.ts
7303
- var TRACE_DIR_RELATIVE = ".delfini-trace";
7304
- var ANALYSIS_INPUT_FILENAME = "analysis-input.json";
7305
- var REPORT_FILENAME = "report.md";
7306
- var SEVERITY_ICON = {
7307
- High: "[H]",
7308
- Medium: "[M]",
7309
- Low: "[L]"
7087
+ // src/doc-scope.ts
7088
+ var DOC_SCOPE_RELATIVE_PATH = ".claude/skills/delfini/doc-scope.json";
7089
+ var DOC_SCOPE_VERSION = 1;
7090
+ var DOC_SCOPE_VERSION_MISMATCH_MESSAGE = "your doc-scope.json is for a newer @delfini/cli; please upgrade.";
7091
+ var REPO_ROOT_REL = ".";
7092
+ var DocScopeVersionMismatchError = class extends Error {
7093
+ code = "DOC_SCOPE_VERSION_MISMATCH";
7094
+ constructor(message = DOC_SCOPE_VERSION_MISMATCH_MESSAGE) {
7095
+ super(message);
7096
+ this.name = "DocScopeVersionMismatchError";
7097
+ }
7310
7098
  };
7311
- var ClarifyingQuestionSchema = external_exports.object({
7312
- whatChanged: external_exports.string().min(1),
7313
- naturalHomeDoc: external_exports.string().min(1),
7314
- naturalHomeSection: external_exports.string().min(1),
7315
- question: external_exports.string().min(1),
7316
- proposedReplacement: external_exports.string().nullable()
7099
+ var DocScopeCorruptError = class extends Error {
7100
+ code = "DOC_SCOPE_CORRUPT";
7101
+ constructor(message) {
7102
+ super(message);
7103
+ this.name = "DocScopeCorruptError";
7104
+ }
7105
+ };
7106
+ var DocScopeValidationError = class extends Error {
7107
+ code = "DOC_SCOPE_VALIDATION";
7108
+ constructor(message) {
7109
+ super(message);
7110
+ this.name = "DocScopeValidationError";
7111
+ }
7112
+ };
7113
+ var docScopeSchemaV1 = external_exports.object({
7114
+ version: external_exports.literal(1),
7115
+ doc_scope: external_exports.array(external_exports.string().min(1))
7317
7116
  });
7318
- var ClarifyingQuestionsArraySchema = external_exports.array(ClarifyingQuestionSchema);
7319
- async function runLocalFinalize(options) {
7320
- const stderr = options.stderr ?? process.stderr;
7321
- const stdout = options.stdout ?? process.stdout;
7322
- const repoRoot = options.repoRoot ?? await getRepoRoot();
7323
- const findingsPath = import_node_path3.default.isAbsolute(options.findingsPath) ? options.findingsPath : import_node_path3.default.join(repoRoot, options.findingsPath);
7324
- let findingsContent;
7117
+ var versionProbeSchema = external_exports.object({
7118
+ version: external_exports.number().int().positive()
7119
+ });
7120
+ async function readDocScope(repoRoot) {
7121
+ const root = repoRoot ?? await getRepoRoot();
7122
+ const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7123
+ let raw;
7325
7124
  try {
7326
- findingsContent = await import_node_fs3.promises.readFile(findingsPath, "utf8");
7125
+ raw = await import_node_fs.promises.readFile(target, "utf8");
7327
7126
  } catch (err) {
7328
- return emitSchemaValidationError(stderr, [
7329
- {
7330
- path: "findings.json",
7331
- message: `Failed to read findings file at "${findingsPath}": ${formatErrorMessage(err)}`
7332
- }
7333
- ]);
7127
+ if (isNoEntError(err)) return null;
7128
+ throw err;
7334
7129
  }
7335
- let rawJson;
7130
+ let parsed;
7336
7131
  try {
7337
- rawJson = JSON.parse(findingsContent);
7132
+ parsed = JSON.parse(raw);
7338
7133
  } catch (err) {
7339
- return emitSchemaValidationError(stderr, [
7340
- {
7341
- path: "findings.json",
7342
- message: `Failed to parse findings.json as JSON: ${formatErrorMessage(err)}`
7343
- }
7344
- ]);
7134
+ throw new DocScopeCorruptError(
7135
+ `${DOC_SCOPE_RELATIVE_PATH} is malformed: ${err.message}`
7136
+ );
7137
+ }
7138
+ const probe = versionProbeSchema.safeParse(parsed);
7139
+ if (probe.success && probe.data.version > DOC_SCOPE_VERSION) {
7140
+ throw new DocScopeVersionMismatchError();
7141
+ }
7142
+ const result = docScopeSchemaV1.safeParse(parsed);
7143
+ if (!result.success) {
7144
+ throw new DocScopeCorruptError(
7145
+ `${DOC_SCOPE_RELATIVE_PATH} is malformed: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
7146
+ );
7147
+ }
7148
+ return result.data;
7149
+ }
7150
+ async function writeDocScope(paths, options) {
7151
+ const root = options?.repoRoot ?? await getRepoRoot();
7152
+ if (!Array.isArray(paths) || paths.length === 0) {
7153
+ throw new DocScopeValidationError("at least one path is required");
7154
+ }
7155
+ const errors = [];
7156
+ for (const entry of paths) {
7157
+ const err = validateDocScopeEntry(entry, REPO_ROOT_REL);
7158
+ if (err !== null) errors.push(err);
7159
+ }
7160
+ if (errors.length > 0) {
7161
+ throw new DocScopeValidationError(
7162
+ `${DOC_SCOPE_RELATIVE_PATH}: invalid path(s):
7163
+ ${errors.map((e) => ` - ${e}`).join("\n")}`
7164
+ );
7165
+ }
7166
+ const normalised = normalizeDocScope(paths);
7167
+ if (normalised.length === 0) {
7168
+ throw new DocScopeValidationError(
7169
+ `${DOC_SCOPE_RELATIVE_PATH}: every entry collapses to an empty scope after normalisation (e.g. '.', './', 'docs/..') \u2014 provide at least one concrete path`
7170
+ );
7171
+ }
7172
+ const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7173
+ await import_node_fs.promises.mkdir(import_node_path.default.dirname(target), { recursive: true });
7174
+ const payload = { version: DOC_SCOPE_VERSION, doc_scope: normalised };
7175
+ const json = `${JSON.stringify(payload, null, 2)}
7176
+ `;
7177
+ await import_node_fs.promises.writeFile(target, json, "utf8");
7178
+ }
7179
+ async function docScopeExists(repoRoot) {
7180
+ const root = repoRoot ?? await getRepoRoot();
7181
+ const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7182
+ try {
7183
+ const st = await import_node_fs.promises.stat(target);
7184
+ return st.isFile();
7185
+ } catch {
7186
+ return false;
7187
+ }
7188
+ }
7189
+ async function deleteDocScope(repoRoot) {
7190
+ const root = repoRoot ?? await getRepoRoot();
7191
+ const target = import_node_path.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7192
+ try {
7193
+ await import_node_fs.promises.unlink(target);
7194
+ } catch (err) {
7195
+ if (!isNoEntError(err)) throw err;
7196
+ }
7197
+ }
7198
+ async function expandDocScope(paths, repoRoot) {
7199
+ const root = repoRoot ?? await getRepoRoot();
7200
+ const normalisedRoot = import_node_path.default.resolve(root);
7201
+ const found = /* @__PURE__ */ new Set();
7202
+ const missing = [];
7203
+ for (const rawEntry of paths) {
7204
+ if (typeof rawEntry !== "string") continue;
7205
+ const normalised = normalizeDocScope([rawEntry]);
7206
+ if (normalised.length === 0) {
7207
+ if (rawEntry.trim().length > 0) missing.push(rawEntry);
7208
+ continue;
7209
+ }
7210
+ const entry = normalised[0];
7211
+ if (validateDocScopeEntry(entry, REPO_ROOT_REL) !== null) {
7212
+ missing.push(rawEntry);
7213
+ continue;
7214
+ }
7215
+ if (classifyEntry(entry) === "glob") {
7216
+ const matches = await (0, import_tinyglobby.glob)(entry, {
7217
+ cwd: root,
7218
+ absolute: true,
7219
+ onlyFiles: true,
7220
+ dot: false,
7221
+ // Case-folding parity with the engine predicate (`nocase: true`).
7222
+ caseSensitiveMatch: false,
7223
+ // Migrating from fast-glob — disable tinyglobby's directory-pattern
7224
+ // auto-expansion so a glob like `packages/*/README.md` keeps exact
7225
+ // fast-glob semantics.
7226
+ expandDirectories: false
7227
+ });
7228
+ const inRoot = matches.filter((m) => isInsideRoot(m, normalisedRoot));
7229
+ if (inRoot.length === 0) {
7230
+ missing.push(rawEntry);
7231
+ } else {
7232
+ for (const m of inRoot) found.add(m);
7233
+ }
7234
+ continue;
7235
+ }
7236
+ const absolute = import_node_path.default.resolve(root, entry);
7237
+ let stat;
7238
+ try {
7239
+ stat = await import_node_fs.promises.stat(absolute);
7240
+ } catch (err) {
7241
+ if (isNoEntError(err)) {
7242
+ missing.push(rawEntry);
7243
+ continue;
7244
+ }
7245
+ throw err;
7246
+ }
7247
+ if (stat.isDirectory()) {
7248
+ const children = await (0, import_tinyglobby.glob)("**/*.md", {
7249
+ cwd: absolute,
7250
+ absolute: true,
7251
+ onlyFiles: true,
7252
+ caseSensitiveMatch: false,
7253
+ dot: false,
7254
+ expandDirectories: false
7255
+ });
7256
+ for (const c of children) {
7257
+ if (isInsideRoot(c, normalisedRoot)) found.add(c);
7258
+ }
7259
+ } else if (stat.isFile()) {
7260
+ if (isInsideRoot(absolute, normalisedRoot)) found.add(absolute);
7261
+ } else {
7262
+ missing.push(rawEntry);
7263
+ }
7264
+ }
7265
+ const files = Array.from(found).sort();
7266
+ return { files, missingPaths: missing };
7267
+ }
7268
+ function isNoEntError(err) {
7269
+ return typeof err === "object" && err !== null && err.code === "ENOENT";
7270
+ }
7271
+ function isInsideRoot(absolute, normalisedRoot) {
7272
+ const resolved = import_node_path.default.resolve(absolute);
7273
+ return resolved === normalisedRoot || resolved.startsWith(normalisedRoot + import_node_path.default.sep);
7274
+ }
7275
+
7276
+ // src/trace.ts
7277
+ init_cjs_shims();
7278
+ var import_node_fs2 = require("fs");
7279
+ var import_node_path2 = require("path");
7280
+ var TRACE_DIR_NAME = ".delfini-trace";
7281
+ var GITIGNORE_LINE = ".delfini-trace/";
7282
+ function ensureTraceDir(repoRoot) {
7283
+ const tracePath = (0, import_node_path2.join)(repoRoot, TRACE_DIR_NAME);
7284
+ if ((0, import_node_fs2.existsSync)(tracePath)) {
7285
+ const stat = (0, import_node_fs2.statSync)(tracePath);
7286
+ if (!stat.isDirectory()) {
7287
+ throw new Error(
7288
+ `ensureTraceDir: ${tracePath} exists but is not a directory. Remove or rename it so the CLI can create the trace directory.`
7289
+ );
7290
+ }
7291
+ return tracePath;
7292
+ }
7293
+ (0, import_node_fs2.mkdirSync)(tracePath, { recursive: true });
7294
+ return tracePath;
7295
+ }
7296
+ function appendToGitignore(repoRoot) {
7297
+ const gitignorePath = (0, import_node_path2.join)(repoRoot, ".gitignore");
7298
+ const existed = (0, import_node_fs2.existsSync)(gitignorePath);
7299
+ const existingContent = existed ? (0, import_node_fs2.readFileSync)(gitignorePath, "utf8") : "";
7300
+ const lines = existingContent.split(/\r?\n/);
7301
+ for (const line of lines) {
7302
+ if (line.trim() === GITIGNORE_LINE) {
7303
+ return { changed: false };
7304
+ }
7305
+ }
7306
+ const useCRLF = existingContent.includes("\r\n");
7307
+ const newline = useCRLF ? "\r\n" : "\n";
7308
+ if (!existed || existingContent.length === 0) {
7309
+ (0, import_node_fs2.writeFileSync)(gitignorePath, `${GITIGNORE_LINE}${newline}`);
7310
+ return { changed: true };
7311
+ }
7312
+ const endsWithNewline = existingContent.endsWith("\n");
7313
+ const prefix = endsWithNewline ? "" : newline;
7314
+ (0, import_node_fs2.appendFileSync)(gitignorePath, `${prefix}${GITIGNORE_LINE}${newline}`);
7315
+ return { changed: true };
7316
+ }
7317
+ function writeTraceFile(repoRoot, filename, content) {
7318
+ validateBasename(filename);
7319
+ const traceDir = ensureTraceDir(repoRoot);
7320
+ const target = (0, import_node_path2.join)(traceDir, filename);
7321
+ (0, import_node_fs2.writeFileSync)(target, content);
7322
+ return target;
7323
+ }
7324
+ function validateBasename(filename) {
7325
+ if (filename.length === 0) {
7326
+ throw new Error("writeTraceFile: filename must not be empty");
7327
+ }
7328
+ if (filename === "." || filename === "..") {
7329
+ throw new Error(`writeTraceFile: filename must not be "${filename}"`);
7330
+ }
7331
+ if (filename.includes("/") || filename.includes("\\")) {
7332
+ throw new Error(
7333
+ `writeTraceFile: filename must be a basename without path separators, got "${filename}"`
7334
+ );
7335
+ }
7336
+ }
7337
+
7338
+ // src/commands/install.ts
7339
+ var InstallToolNotSupportedError = class extends Error {
7340
+ code = "INSTALL_TOOL_NOT_SUPPORTED";
7341
+ constructor(tool) {
7342
+ super(
7343
+ `delfini install: --tool '${tool}' is not supported. The Skill is Claude-only in V1 (design-spec NG2 / project-context "Skill \u2014 Out of Scope in V1 / V1.1: No multi-LLM-provider support \u2014 Claude-only by design"). Use --tool CLAUDE (the default).`
7344
+ );
7345
+ this.name = "InstallToolNotSupportedError";
7346
+ }
7347
+ };
7348
+ var CLAUDE_MD_OPEN_MARKER = "<!-- delfini:auto-invoke-block-v1 -->";
7349
+ var CLAUDE_MD_CLOSE_MARKER = "<!-- /delfini:auto-invoke-block-v1 -->";
7350
+ var SKILL_RELATIVE_PATH = ".claude/skills/delfini/SKILL.md";
7351
+ var CLAUDE_MD_FILENAME = "CLAUDE.md";
7352
+ var SUPPORTED_TOOL = "CLAUDE";
7353
+ var TEMPLATES_DIR = resolveTemplatesDir();
7354
+ function resolveTemplatesDir() {
7355
+ let dir = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
7356
+ for (let i = 0; i < 5; i++) {
7357
+ const candidate = (0, import_node_path3.resolve)(dir, "templates");
7358
+ if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(candidate, "SKILL.md"))) {
7359
+ return candidate;
7360
+ }
7361
+ const parent = (0, import_node_path3.dirname)(dir);
7362
+ if (parent === dir) break;
7363
+ dir = parent;
7364
+ }
7365
+ throw new Error("templates/ directory not found relative to the CLI module");
7366
+ }
7367
+ async function runInstall(targetPath, options) {
7368
+ const tool = options?.tool ?? SUPPORTED_TOOL;
7369
+ const logger = options?.logger ?? console;
7370
+ if (tool !== SUPPORTED_TOOL) {
7371
+ throw new InstallToolNotSupportedError(tool);
7345
7372
  }
7346
- const analysisInputPath = import_node_path3.default.join(repoRoot, TRACE_DIR_RELATIVE, ANALYSIS_INPUT_FILENAME);
7373
+ const resolvedTarget = (0, import_node_path3.resolve)(process.cwd(), targetPath);
7374
+ const repoRoot = await getRepoRoot(resolvedTarget);
7375
+ writeSkillTemplate(repoRoot, logger);
7376
+ await applyDocScope(repoRoot, logger, options?.provideDocScope);
7377
+ await applyAutoInvokeDecision(repoRoot, logger, options?.confirmAutoInvoke);
7378
+ appendGitignoreLine(repoRoot, logger);
7379
+ }
7380
+ function parseScopeInput(answer) {
7381
+ return answer.split(/[\s,]+/u).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
7382
+ }
7383
+ function sanitiseScope(paths) {
7384
+ return paths.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
7385
+ }
7386
+ async function applyDocScope(repoRoot, logger, provideDocScope) {
7387
+ const target = (0, import_node_path3.join)(repoRoot, DOC_SCOPE_RELATIVE_PATH);
7388
+ if (provideDocScope) {
7389
+ const paths2 = sanitiseScope(await provideDocScope());
7390
+ if (paths2.length === 0) {
7391
+ log(logger, `doc-scope.json \u2192 ${target} (no paths provided, no change)`);
7392
+ return;
7393
+ }
7394
+ await persistDocScope(repoRoot, logger, target, paths2);
7395
+ return;
7396
+ }
7397
+ if (await docScopeExists(repoRoot)) {
7398
+ log(logger, `doc-scope.json \u2192 ${target} (already configured, no change)`);
7399
+ return;
7400
+ }
7401
+ if (!process.stdin.isTTY) {
7402
+ log(
7403
+ logger,
7404
+ `doc-scope.json \u2192 ${target} (non-interactive shell: scope prompt skipped, no change)`
7405
+ );
7406
+ return;
7407
+ }
7408
+ const paths = await promptDocScope();
7409
+ if (paths.length === 0) {
7410
+ log(
7411
+ logger,
7412
+ `doc-scope.json \u2192 ${target} (no paths provided, no change \u2014 first /delfini run will prompt)`
7413
+ );
7414
+ return;
7415
+ }
7416
+ await persistDocScope(repoRoot, logger, target, paths);
7417
+ }
7418
+ async function promptDocScope() {
7419
+ const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
7420
+ try {
7421
+ const answer = await rl.question(
7422
+ "Which docs should Delfini track? Enter one or more paths \u2014 directories (recursive .md scan), files, or globs, space- or comma-separated (e.g. `docs/ specs/architecture.md packages/*/README.md`). Leave blank to skip: "
7423
+ );
7424
+ return parseScopeInput(answer);
7425
+ } finally {
7426
+ rl.close();
7427
+ }
7428
+ }
7429
+ async function persistDocScope(repoRoot, logger, target, paths) {
7430
+ try {
7431
+ await writeDocScope(paths, { repoRoot });
7432
+ log(logger, `doc-scope.json \u2192 ${target} (wrote ${paths.length} path(s))`);
7433
+ } catch (err) {
7434
+ if (err instanceof DocScopeValidationError) {
7435
+ log(
7436
+ logger,
7437
+ `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.`
7438
+ );
7439
+ return;
7440
+ }
7441
+ throw err;
7442
+ }
7443
+ }
7444
+ function parseYesNo(answer) {
7445
+ const normalised = answer.trim().toLowerCase();
7446
+ return normalised === "y" || normalised === "yes";
7447
+ }
7448
+ async function applyAutoInvokeDecision(repoRoot, logger, confirmAutoInvoke) {
7449
+ const decision = await resolveAutoInvoke(confirmAutoInvoke);
7450
+ if (decision === "yes") {
7451
+ appendClaudeMdBlock(repoRoot, logger);
7452
+ } else if (decision === "no") {
7453
+ stripClaudeMdBlock(repoRoot, logger);
7454
+ } else {
7455
+ const target = (0, import_node_path3.join)(repoRoot, CLAUDE_MD_FILENAME);
7456
+ log(logger, `CLAUDE.md \u2192 ${target} (non-interactive shell: auto-invoke prompt skipped, no change)`);
7457
+ }
7458
+ }
7459
+ async function resolveAutoInvoke(confirmAutoInvoke) {
7460
+ if (confirmAutoInvoke) {
7461
+ return await confirmAutoInvoke() ? "yes" : "no";
7462
+ }
7463
+ if (!process.stdin.isTTY) {
7464
+ return "skip";
7465
+ }
7466
+ return await promptAutoInvoke() ? "yes" : "no";
7467
+ }
7468
+ async function promptAutoInvoke() {
7469
+ const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
7470
+ try {
7471
+ const answer = await rl.question("Auto-invoke /delfini on PR creation? (y/n) ");
7472
+ return parseYesNo(answer);
7473
+ } finally {
7474
+ rl.close();
7475
+ }
7476
+ }
7477
+ function writeSkillTemplate(repoRoot, logger) {
7478
+ const target = (0, import_node_path3.join)(repoRoot, SKILL_RELATIVE_PATH);
7479
+ const templateSource = (0, import_node_path3.join)(TEMPLATES_DIR, "SKILL.md");
7480
+ const content = (0, import_node_fs3.readFileSync)(templateSource);
7481
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(target), { recursive: true });
7482
+ (0, import_node_fs3.writeFileSync)(target, content);
7483
+ log(logger, `SKILL.md \u2192 ${target}`);
7484
+ }
7485
+ function appendClaudeMdBlock(repoRoot, logger) {
7486
+ const target = (0, import_node_path3.join)(repoRoot, CLAUDE_MD_FILENAME);
7487
+ const blockSource = (0, import_node_path3.join)(TEMPLATES_DIR, "claude-md-append-block.txt");
7488
+ const body = (0, import_node_fs3.readFileSync)(blockSource, "utf8").replace(/\s+$/u, "");
7489
+ const block = `${CLAUDE_MD_OPEN_MARKER}
7490
+ ${body}
7491
+ ${CLAUDE_MD_CLOSE_MARKER}
7492
+ `;
7493
+ if (!(0, import_node_fs3.existsSync)(target)) {
7494
+ (0, import_node_fs3.writeFileSync)(target, block);
7495
+ log(logger, `CLAUDE.md \u2192 ${target} (created)`);
7496
+ return;
7497
+ }
7498
+ const existing = (0, import_node_fs3.readFileSync)(target, "utf8");
7499
+ if (existing.includes(CLAUDE_MD_OPEN_MARKER)) {
7500
+ log(logger, `CLAUDE.md \u2192 ${target} (block already present, no change)`);
7501
+ return;
7502
+ }
7503
+ if (existing.length === 0) {
7504
+ (0, import_node_fs3.writeFileSync)(target, block);
7505
+ log(logger, `CLAUDE.md \u2192 ${target} (block appended)`);
7506
+ return;
7507
+ }
7508
+ const needsLeadingNewline = !existing.endsWith("\n");
7509
+ const prefix = needsLeadingNewline ? "\n" : "";
7510
+ (0, import_node_fs3.writeFileSync)(target, `${existing}${prefix}${block}`);
7511
+ log(logger, `CLAUDE.md \u2192 ${target} (block appended)`);
7512
+ }
7513
+ function stripClaudeMdBlock(repoRoot, logger) {
7514
+ const target = (0, import_node_path3.join)(repoRoot, CLAUDE_MD_FILENAME);
7515
+ if (!(0, import_node_fs3.existsSync)(target)) {
7516
+ log(logger, `CLAUDE.md \u2192 ${target} (no block to remove, no change)`);
7517
+ return;
7518
+ }
7519
+ const existing = (0, import_node_fs3.readFileSync)(target, "utf8");
7520
+ const openIdx = existing.indexOf(CLAUDE_MD_OPEN_MARKER);
7521
+ if (openIdx === -1) {
7522
+ log(logger, `CLAUDE.md \u2192 ${target} (no block to remove, no change)`);
7523
+ return;
7524
+ }
7525
+ const afterOpen = openIdx + CLAUDE_MD_OPEN_MARKER.length;
7526
+ const closeIdx = existing.indexOf(CLAUDE_MD_CLOSE_MARKER, afterOpen);
7527
+ let endIdx;
7528
+ if (closeIdx === -1) {
7529
+ endIdx = existing.length;
7530
+ } else {
7531
+ endIdx = closeIdx + CLAUDE_MD_CLOSE_MARKER.length;
7532
+ if (existing.startsWith("\r\n", endIdx)) {
7533
+ endIdx += 2;
7534
+ } else if (existing[endIdx] === "\n") {
7535
+ endIdx += 1;
7536
+ }
7537
+ }
7538
+ const result = existing.slice(0, openIdx) + existing.slice(endIdx);
7539
+ (0, import_node_fs3.writeFileSync)(target, result);
7540
+ log(logger, `CLAUDE.md \u2192 ${target} (block removed)`);
7541
+ }
7542
+ function appendGitignoreLine(repoRoot, logger) {
7543
+ const target = (0, import_node_path3.join)(repoRoot, ".gitignore");
7544
+ const { changed } = appendToGitignore(repoRoot);
7545
+ if (changed) {
7546
+ log(logger, `.gitignore \u2192 ${target} (appended .delfini-trace/)`);
7547
+ } else {
7548
+ log(logger, `.gitignore \u2192 ${target} (.delfini-trace/ already present, no change)`);
7549
+ }
7550
+ }
7551
+ function log(logger, message) {
7552
+ if (typeof logger.log === "function") {
7553
+ logger.log(message);
7554
+ }
7555
+ }
7556
+
7557
+ // src/commands/local-finalize.ts
7558
+ init_cjs_shims();
7559
+ var import_node_fs4 = require("fs");
7560
+ var import_node_path4 = __toESM(require("path"), 1);
7561
+ var TRACE_DIR_RELATIVE = ".delfini-trace";
7562
+ var ANALYSIS_INPUT_FILENAME = "analysis-input.json";
7563
+ var REPORT_FILENAME = "report.md";
7564
+ var SEVERITY_ICON = {
7565
+ High: "[H]",
7566
+ Medium: "[M]",
7567
+ Low: "[L]"
7568
+ };
7569
+ var ClarifyingQuestionSchema = external_exports.object({
7570
+ whatChanged: external_exports.string().min(1),
7571
+ naturalHomeDoc: external_exports.string().min(1),
7572
+ naturalHomeSection: external_exports.string().min(1),
7573
+ question: external_exports.string().min(1),
7574
+ proposedReplacement: external_exports.string().nullable()
7575
+ });
7576
+ var ClarifyingQuestionsArraySchema = external_exports.array(ClarifyingQuestionSchema);
7577
+ async function runLocalFinalize(options) {
7578
+ const stderr = options.stderr ?? process.stderr;
7579
+ const stdout = options.stdout ?? process.stdout;
7580
+ const repoRoot = options.repoRoot ?? await getRepoRoot();
7581
+ const findingsPath = import_node_path4.default.isAbsolute(options.findingsPath) ? options.findingsPath : import_node_path4.default.join(repoRoot, options.findingsPath);
7582
+ let findingsContent;
7583
+ try {
7584
+ findingsContent = await import_node_fs4.promises.readFile(findingsPath, "utf8");
7585
+ } catch (err) {
7586
+ return emitSchemaValidationError(stderr, [
7587
+ {
7588
+ path: "findings.json",
7589
+ message: `Failed to read findings file at "${findingsPath}": ${formatErrorMessage(err)}`
7590
+ }
7591
+ ]);
7592
+ }
7593
+ let rawJson;
7594
+ try {
7595
+ rawJson = JSON.parse(findingsContent);
7596
+ } catch (err) {
7597
+ return emitSchemaValidationError(stderr, [
7598
+ {
7599
+ path: "findings.json",
7600
+ message: `Failed to parse findings.json as JSON: ${formatErrorMessage(err)}`
7601
+ }
7602
+ ]);
7603
+ }
7604
+ const analysisInputPath = import_node_path4.default.join(repoRoot, TRACE_DIR_RELATIVE, ANALYSIS_INPUT_FILENAME);
7347
7605
  let docs;
7348
7606
  try {
7349
- const raw = await import_node_fs3.promises.readFile(analysisInputPath, "utf8");
7607
+ const raw = await import_node_fs4.promises.readFile(analysisInputPath, "utf8");
7350
7608
  const parsed = JSON.parse(raw);
7351
7609
  if (!Array.isArray(parsed.docs)) {
7352
7610
  return emitSchemaValidationError(stderr, [
@@ -7561,155 +7819,6 @@ var import_node_path5 = __toESM(require("path"), 1);
7561
7819
  var import_node_url2 = require("url");
7562
7820
  var import_node_util = require("util");
7563
7821
  var import_simple_git3 = __toESM(require("simple-git"), 1);
7564
-
7565
- // src/doc-scope.ts
7566
- init_cjs_shims();
7567
- var import_node_fs4 = require("fs");
7568
- var import_node_path4 = __toESM(require("path"), 1);
7569
- var import_tinyglobby = require("tinyglobby");
7570
- var DOC_SCOPE_RELATIVE_PATH = ".claude/skills/delfini/doc-scope.json";
7571
- var DOC_SCOPE_VERSION = 1;
7572
- var DOC_SCOPE_VERSION_MISMATCH_MESSAGE = "your doc-scope.json is for a newer @delfini/cli; please upgrade.";
7573
- var REPO_ROOT_REL = ".";
7574
- var DocScopeVersionMismatchError = class extends Error {
7575
- code = "DOC_SCOPE_VERSION_MISMATCH";
7576
- constructor(message = DOC_SCOPE_VERSION_MISMATCH_MESSAGE) {
7577
- super(message);
7578
- this.name = "DocScopeVersionMismatchError";
7579
- }
7580
- };
7581
- var DocScopeCorruptError = class extends Error {
7582
- code = "DOC_SCOPE_CORRUPT";
7583
- constructor(message) {
7584
- super(message);
7585
- this.name = "DocScopeCorruptError";
7586
- }
7587
- };
7588
- var docScopeSchemaV1 = external_exports.object({
7589
- version: external_exports.literal(1),
7590
- doc_scope: external_exports.array(external_exports.string().min(1))
7591
- });
7592
- var versionProbeSchema = external_exports.object({
7593
- version: external_exports.number().int().positive()
7594
- });
7595
- async function readDocScope(repoRoot) {
7596
- const root = repoRoot ?? await getRepoRoot();
7597
- const target = import_node_path4.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7598
- let raw;
7599
- try {
7600
- raw = await import_node_fs4.promises.readFile(target, "utf8");
7601
- } catch (err) {
7602
- if (isNoEntError(err)) return null;
7603
- throw err;
7604
- }
7605
- let parsed;
7606
- try {
7607
- parsed = JSON.parse(raw);
7608
- } catch (err) {
7609
- throw new DocScopeCorruptError(
7610
- `${DOC_SCOPE_RELATIVE_PATH} is malformed: ${err.message}`
7611
- );
7612
- }
7613
- const probe = versionProbeSchema.safeParse(parsed);
7614
- if (probe.success && probe.data.version > DOC_SCOPE_VERSION) {
7615
- throw new DocScopeVersionMismatchError();
7616
- }
7617
- const result = docScopeSchemaV1.safeParse(parsed);
7618
- if (!result.success) {
7619
- throw new DocScopeCorruptError(
7620
- `${DOC_SCOPE_RELATIVE_PATH} is malformed: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
7621
- );
7622
- }
7623
- return result.data;
7624
- }
7625
- async function deleteDocScope(repoRoot) {
7626
- const root = repoRoot ?? await getRepoRoot();
7627
- const target = import_node_path4.default.join(root, DOC_SCOPE_RELATIVE_PATH);
7628
- try {
7629
- await import_node_fs4.promises.unlink(target);
7630
- } catch (err) {
7631
- if (!isNoEntError(err)) throw err;
7632
- }
7633
- }
7634
- async function expandDocScope(paths, repoRoot) {
7635
- const root = repoRoot ?? await getRepoRoot();
7636
- const normalisedRoot = import_node_path4.default.resolve(root);
7637
- const found = /* @__PURE__ */ new Set();
7638
- const missing = [];
7639
- for (const rawEntry of paths) {
7640
- if (typeof rawEntry !== "string") continue;
7641
- const normalised = normalizeDocScope([rawEntry]);
7642
- if (normalised.length === 0) {
7643
- if (rawEntry.trim().length > 0) missing.push(rawEntry);
7644
- continue;
7645
- }
7646
- const entry = normalised[0];
7647
- if (validateDocScopeEntry(entry, REPO_ROOT_REL) !== null) {
7648
- missing.push(rawEntry);
7649
- continue;
7650
- }
7651
- if (classifyEntry(entry) === "glob") {
7652
- const matches = await (0, import_tinyglobby.glob)(entry, {
7653
- cwd: root,
7654
- absolute: true,
7655
- onlyFiles: true,
7656
- dot: false,
7657
- // Case-folding parity with the engine predicate (`nocase: true`).
7658
- caseSensitiveMatch: false,
7659
- // Migrating from fast-glob — disable tinyglobby's directory-pattern
7660
- // auto-expansion so a glob like `packages/*/README.md` keeps exact
7661
- // fast-glob semantics.
7662
- expandDirectories: false
7663
- });
7664
- const inRoot = matches.filter((m) => isInsideRoot(m, normalisedRoot));
7665
- if (inRoot.length === 0) {
7666
- missing.push(rawEntry);
7667
- } else {
7668
- for (const m of inRoot) found.add(m);
7669
- }
7670
- continue;
7671
- }
7672
- const absolute = import_node_path4.default.resolve(root, entry);
7673
- let stat;
7674
- try {
7675
- stat = await import_node_fs4.promises.stat(absolute);
7676
- } catch (err) {
7677
- if (isNoEntError(err)) {
7678
- missing.push(rawEntry);
7679
- continue;
7680
- }
7681
- throw err;
7682
- }
7683
- if (stat.isDirectory()) {
7684
- const children = await (0, import_tinyglobby.glob)("**/*.md", {
7685
- cwd: absolute,
7686
- absolute: true,
7687
- onlyFiles: true,
7688
- caseSensitiveMatch: false,
7689
- dot: false,
7690
- expandDirectories: false
7691
- });
7692
- for (const c of children) {
7693
- if (isInsideRoot(c, normalisedRoot)) found.add(c);
7694
- }
7695
- } else if (stat.isFile()) {
7696
- if (isInsideRoot(absolute, normalisedRoot)) found.add(absolute);
7697
- } else {
7698
- missing.push(rawEntry);
7699
- }
7700
- }
7701
- const files = Array.from(found).sort();
7702
- return { files, missingPaths: missing };
7703
- }
7704
- function isNoEntError(err) {
7705
- return typeof err === "object" && err !== null && err.code === "ENOENT";
7706
- }
7707
- function isInsideRoot(absolute, normalisedRoot) {
7708
- const resolved = import_node_path4.default.resolve(absolute);
7709
- return resolved === normalisedRoot || resolved.startsWith(normalisedRoot + import_node_path4.default.sep);
7710
- }
7711
-
7712
- // src/commands/local-prepare.ts
7713
7822
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
7714
7823
  var UNTRACKED_DIFF_MAX_BUFFER = 64 * 1024 * 1024;
7715
7824
  var PROMPT_TOKEN_BUDGET = 15e4;
@@ -8037,10 +8146,16 @@ async function main(argv) {
8037
8146
  });
8038
8147
  program.command("install <path>").description(
8039
8148
  "Scaffold .claude/skills/delfini/SKILL.md + CLAUDE.md auto-invoke + .gitignore append"
8040
- ).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").action(async (targetPath, opts) => {
8041
- const confirmAutoInvoke = opts.autoInvoke === void 0 ? void 0 : () => Promise.resolve(opts.autoInvoke);
8042
- await runInstall(targetPath, { tool: opts.tool, confirmAutoInvoke });
8043
- });
8149
+ ).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(
8150
+ "--scope <paths>",
8151
+ "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."
8152
+ ).action(
8153
+ async (targetPath, opts) => {
8154
+ const confirmAutoInvoke = opts.autoInvoke === void 0 ? void 0 : () => Promise.resolve(opts.autoInvoke);
8155
+ const provideDocScope = opts.scope === void 0 ? void 0 : () => Promise.resolve(parseScopeInput(opts.scope));
8156
+ await runInstall(targetPath, { tool: opts.tool, confirmAutoInvoke, provideDocScope });
8157
+ }
8158
+ );
8044
8159
  program.command("local-prepare").description(
8045
8160
  "Compute diff + doc-scope + prompt + token-budget gate; write .delfini-trace/"
8046
8161
  ).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(