docbridge 0.4.0 → 0.5.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/CHANGELOG.md +47 -10
- package/README.md +191 -125
- package/dist/bin/darwin-arm64/SHA256SUMS +2 -2
- package/dist/bin/darwin-arm64/speclink-swift-scanner +0 -0
- package/dist/bin/darwin-arm64/speclink_dart_scanner +0 -0
- package/dist/bin/linux-x64/SHA256SUMS +1 -1
- package/dist/bin/linux-x64/speclink-swift-scanner +0 -0
- package/dist/bin/linux-x64/speclink_dart_scanner +0 -0
- package/dist/index.js +891 -14
- package/package.json +1 -1
- package/templates/skills/README.md +10 -6
- package/templates/skills/docbridge-adopt/SKILL.md +23 -5
package/dist/index.js
CHANGED
|
@@ -168755,12 +168755,12 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
|
|
|
168755
168755
|
});
|
|
168756
168756
|
|
|
168757
168757
|
// src/cli/index.ts
|
|
168758
|
-
import { readFileSync as
|
|
168759
|
-
import { resolve as
|
|
168758
|
+
import { readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
168759
|
+
import { resolve as resolve4 } from "path";
|
|
168760
168760
|
// package.json
|
|
168761
168761
|
var package_default = {
|
|
168762
168762
|
name: "docbridge",
|
|
168763
|
-
version: "0.
|
|
168763
|
+
version: "0.5.0",
|
|
168764
168764
|
description: "A Bun CLI that links Markdown specifications and code declarations.",
|
|
168765
168765
|
license: "MIT",
|
|
168766
168766
|
type: "module",
|
|
@@ -171886,6 +171886,861 @@ function fullChangeText(changes) {
|
|
|
171886
171886
|
return typeof last.text === "string" ? last.text : undefined;
|
|
171887
171887
|
}
|
|
171888
171888
|
|
|
171889
|
+
// src/cli/init.ts
|
|
171890
|
+
import {
|
|
171891
|
+
cpSync,
|
|
171892
|
+
mkdirSync as mkdirSync2,
|
|
171893
|
+
readFileSync as readFileSync4,
|
|
171894
|
+
statSync as statSync3,
|
|
171895
|
+
writeFileSync
|
|
171896
|
+
} from "fs";
|
|
171897
|
+
import { join as join7, resolve as resolve3 } from "path";
|
|
171898
|
+
|
|
171899
|
+
// src/core/init-discovery.ts
|
|
171900
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync } from "fs";
|
|
171901
|
+
import { basename, dirname as dirname2, join as join5 } from "path";
|
|
171902
|
+
var HIGH_CONFIDENCE_DIR_NAMES = new Set([
|
|
171903
|
+
"docs/specs",
|
|
171904
|
+
"specs",
|
|
171905
|
+
"spec",
|
|
171906
|
+
"requirements",
|
|
171907
|
+
"design",
|
|
171908
|
+
"architecture",
|
|
171909
|
+
"adr",
|
|
171910
|
+
"decisions"
|
|
171911
|
+
]);
|
|
171912
|
+
var MEDIUM_CONFIDENCE_DIR_NAMES = new Set(["docs", "documentation", "doc"]);
|
|
171913
|
+
var EXCLUDED_PROSE_BASENAMES = new Set([
|
|
171914
|
+
"readme.md",
|
|
171915
|
+
"changelog.md",
|
|
171916
|
+
"contributing.md",
|
|
171917
|
+
"license.md",
|
|
171918
|
+
"code_of_conduct.md",
|
|
171919
|
+
"security.md"
|
|
171920
|
+
]);
|
|
171921
|
+
var EXCLUDED_DIR_SEGMENTS = new Set([
|
|
171922
|
+
"runbook",
|
|
171923
|
+
"runbooks",
|
|
171924
|
+
"release",
|
|
171925
|
+
"releases",
|
|
171926
|
+
"changelog",
|
|
171927
|
+
"changelogs",
|
|
171928
|
+
"contributing"
|
|
171929
|
+
]);
|
|
171930
|
+
var IGNORED_WALK_SEGMENTS = new Set(["node_modules", ".git", "dist", "build"]);
|
|
171931
|
+
var TYPESCRIPT_PATTERNS = [
|
|
171932
|
+
"src/**/*.ts",
|
|
171933
|
+
"lib/**/*.ts",
|
|
171934
|
+
"packages/*/src/**/*.ts",
|
|
171935
|
+
"apps/*/src/**/*.ts"
|
|
171936
|
+
];
|
|
171937
|
+
var SWIFT_PATTERNS = ["Sources/**/*.swift", "*/Sources/**/*.swift"];
|
|
171938
|
+
var DART_PATTERNS = ["lib/**/*.dart"];
|
|
171939
|
+
var LANGUAGE_PATTERNS = {
|
|
171940
|
+
typescript: TYPESCRIPT_PATTERNS,
|
|
171941
|
+
swift: SWIFT_PATTERNS,
|
|
171942
|
+
dart: DART_PATTERNS
|
|
171943
|
+
};
|
|
171944
|
+
function discoverRepository(projectRoot) {
|
|
171945
|
+
return {
|
|
171946
|
+
docs: discoverDocsScope(projectRoot),
|
|
171947
|
+
code: discoverCodeScope(projectRoot),
|
|
171948
|
+
agent: discoverAgentTarget(projectRoot)
|
|
171949
|
+
};
|
|
171950
|
+
}
|
|
171951
|
+
function discoverDocsScope(projectRoot) {
|
|
171952
|
+
const markdownFiles = collectMarkdownFiles(projectRoot);
|
|
171953
|
+
const eligible = markdownFiles.filter((filePath) => isEligibleDocsFile(filePath));
|
|
171954
|
+
if (eligible.length === 0) {
|
|
171955
|
+
return {
|
|
171956
|
+
candidates: [],
|
|
171957
|
+
recommended: undefined,
|
|
171958
|
+
ambiguous: true,
|
|
171959
|
+
message: "No specification-like Markdown files were found. Confirm docs scope before creating docbridge.config.json."
|
|
171960
|
+
};
|
|
171961
|
+
}
|
|
171962
|
+
const grouped = new Map;
|
|
171963
|
+
for (const filePath of eligible) {
|
|
171964
|
+
const directory = dirname2(filePath);
|
|
171965
|
+
const existing = grouped.get(directory) ?? [];
|
|
171966
|
+
existing.push(filePath);
|
|
171967
|
+
grouped.set(directory, existing);
|
|
171968
|
+
}
|
|
171969
|
+
const candidates = [...grouped.entries()].map(([directory, files]) => ({
|
|
171970
|
+
directory,
|
|
171971
|
+
pattern: `${directory}/**/*.md`,
|
|
171972
|
+
score: scoreDocsDirectory(directory),
|
|
171973
|
+
fileCount: files.length
|
|
171974
|
+
})).sort((left, right) => right.score !== left.score ? right.score - left.score : left.directory.localeCompare(right.directory));
|
|
171975
|
+
const topScore = candidates[0]?.score ?? 0;
|
|
171976
|
+
if (topScore === 0) {
|
|
171977
|
+
return {
|
|
171978
|
+
candidates,
|
|
171979
|
+
recommended: undefined,
|
|
171980
|
+
ambiguous: true,
|
|
171981
|
+
message: "Markdown files were found, but no likely specification directory was detected. Confirm docs scope before creating docbridge.config.json."
|
|
171982
|
+
};
|
|
171983
|
+
}
|
|
171984
|
+
const topCandidates = candidates.filter((candidate) => candidate.score === topScore);
|
|
171985
|
+
if (topCandidates.length !== 1) {
|
|
171986
|
+
return {
|
|
171987
|
+
candidates,
|
|
171988
|
+
recommended: undefined,
|
|
171989
|
+
ambiguous: true,
|
|
171990
|
+
message: "Multiple likely docs directories were detected. Confirm docs scope before creating docbridge.config.json."
|
|
171991
|
+
};
|
|
171992
|
+
}
|
|
171993
|
+
return {
|
|
171994
|
+
candidates,
|
|
171995
|
+
recommended: topCandidates[0],
|
|
171996
|
+
ambiguous: false,
|
|
171997
|
+
message: undefined
|
|
171998
|
+
};
|
|
171999
|
+
}
|
|
172000
|
+
function discoverCodeScope(projectRoot) {
|
|
172001
|
+
const languages = [];
|
|
172002
|
+
for (const language of ["typescript", "swift", "dart"]) {
|
|
172003
|
+
const patterns = activeCodePatterns(projectRoot, language);
|
|
172004
|
+
if (patterns.length > 0) {
|
|
172005
|
+
languages.push({
|
|
172006
|
+
language,
|
|
172007
|
+
patterns,
|
|
172008
|
+
fileCount: countCodeFiles(projectRoot, patterns, language)
|
|
172009
|
+
});
|
|
172010
|
+
}
|
|
172011
|
+
}
|
|
172012
|
+
if (languages.length === 0) {
|
|
172013
|
+
return {
|
|
172014
|
+
languages: [],
|
|
172015
|
+
ambiguous: true,
|
|
172016
|
+
message: "No supported code files were detected. Confirm code scope before creating docbridge.config.json."
|
|
172017
|
+
};
|
|
172018
|
+
}
|
|
172019
|
+
return {
|
|
172020
|
+
languages,
|
|
172021
|
+
ambiguous: false,
|
|
172022
|
+
message: undefined
|
|
172023
|
+
};
|
|
172024
|
+
}
|
|
172025
|
+
function discoverAgentTarget(projectRoot) {
|
|
172026
|
+
const hasAgentsDir = existsSync2(join5(projectRoot, ".agents"));
|
|
172027
|
+
const hasClaudeDir = existsSync2(join5(projectRoot, ".claude"));
|
|
172028
|
+
let defaultTarget = "none";
|
|
172029
|
+
let recommendedTarget = "none";
|
|
172030
|
+
let message;
|
|
172031
|
+
if (hasAgentsDir && hasClaudeDir) {
|
|
172032
|
+
defaultTarget = "both";
|
|
172033
|
+
recommendedTarget = "both";
|
|
172034
|
+
} else if (hasAgentsDir) {
|
|
172035
|
+
defaultTarget = "codex";
|
|
172036
|
+
recommendedTarget = "codex";
|
|
172037
|
+
} else if (hasClaudeDir) {
|
|
172038
|
+
defaultTarget = "claude";
|
|
172039
|
+
recommendedTarget = "claude";
|
|
172040
|
+
} else {
|
|
172041
|
+
defaultTarget = "none";
|
|
172042
|
+
recommendedTarget = "codex";
|
|
172043
|
+
message = "No agent directory was detected. Interactive setup recommends Codex (.agents/skills/) after confirmation.";
|
|
172044
|
+
}
|
|
172045
|
+
return {
|
|
172046
|
+
hasAgentsDir,
|
|
172047
|
+
hasClaudeDir,
|
|
172048
|
+
defaultTarget,
|
|
172049
|
+
recommendedTarget,
|
|
172050
|
+
message
|
|
172051
|
+
};
|
|
172052
|
+
}
|
|
172053
|
+
function collectMarkdownFiles(projectRoot, currentDir = ".") {
|
|
172054
|
+
const absoluteDir = join5(projectRoot, currentDir);
|
|
172055
|
+
let entries;
|
|
172056
|
+
try {
|
|
172057
|
+
entries = readdirSync2(absoluteDir);
|
|
172058
|
+
} catch {
|
|
172059
|
+
return [];
|
|
172060
|
+
}
|
|
172061
|
+
const files = [];
|
|
172062
|
+
for (const entry of entries) {
|
|
172063
|
+
const relPath = currentDir === "." ? entry : `${currentDir}/${entry}`;
|
|
172064
|
+
const segments = relPath.split("/");
|
|
172065
|
+
if (segments.some((segment) => IGNORED_WALK_SEGMENTS.has(segment) || segment.startsWith("."))) {
|
|
172066
|
+
continue;
|
|
172067
|
+
}
|
|
172068
|
+
const absolutePath = join5(projectRoot, relPath);
|
|
172069
|
+
let stats;
|
|
172070
|
+
try {
|
|
172071
|
+
stats = statSync(absolutePath);
|
|
172072
|
+
} catch {
|
|
172073
|
+
continue;
|
|
172074
|
+
}
|
|
172075
|
+
if (stats.isDirectory()) {
|
|
172076
|
+
files.push(...collectMarkdownFiles(projectRoot, relPath));
|
|
172077
|
+
continue;
|
|
172078
|
+
}
|
|
172079
|
+
if (stats.isFile() && relPath.endsWith(".md")) {
|
|
172080
|
+
files.push(relPath);
|
|
172081
|
+
}
|
|
172082
|
+
}
|
|
172083
|
+
return files.sort();
|
|
172084
|
+
}
|
|
172085
|
+
function isEligibleDocsFile(filePath) {
|
|
172086
|
+
if (EXCLUDED_PROSE_BASENAMES.has(basename(filePath).toLowerCase())) {
|
|
172087
|
+
return false;
|
|
172088
|
+
}
|
|
172089
|
+
const segments = filePath.split("/").map((segment) => segment.toLowerCase());
|
|
172090
|
+
return !segments.some((segment) => EXCLUDED_DIR_SEGMENTS.has(segment));
|
|
172091
|
+
}
|
|
172092
|
+
function scoreDocsDirectory(directory) {
|
|
172093
|
+
const normalized = directory.replace(/^\.\/?/, "");
|
|
172094
|
+
if (normalized === ".") {
|
|
172095
|
+
return 0;
|
|
172096
|
+
}
|
|
172097
|
+
const segments = normalized.split("/");
|
|
172098
|
+
let score = 0;
|
|
172099
|
+
for (let index = 0;index < segments.length; index += 1) {
|
|
172100
|
+
const tail = segments.slice(index).join("/");
|
|
172101
|
+
if (HIGH_CONFIDENCE_DIR_NAMES.has(tail) || HIGH_CONFIDENCE_DIR_NAMES.has(segments[index] ?? "")) {
|
|
172102
|
+
score = Math.max(score, 2);
|
|
172103
|
+
} else if (MEDIUM_CONFIDENCE_DIR_NAMES.has(tail) || MEDIUM_CONFIDENCE_DIR_NAMES.has(segments[index] ?? "")) {
|
|
172104
|
+
score = Math.max(score, 1);
|
|
172105
|
+
}
|
|
172106
|
+
}
|
|
172107
|
+
return score;
|
|
172108
|
+
}
|
|
172109
|
+
function activeCodePatterns(projectRoot, language) {
|
|
172110
|
+
return LANGUAGE_PATTERNS[language].filter((pattern) => countCodeFiles(projectRoot, [pattern], language) > 0);
|
|
172111
|
+
}
|
|
172112
|
+
function countCodeFiles(projectRoot, patterns, language) {
|
|
172113
|
+
const seen = new Set;
|
|
172114
|
+
for (const pattern of patterns) {
|
|
172115
|
+
for (const filePath of collectFiles(projectRoot, [pattern])) {
|
|
172116
|
+
if (!isExcludedCodeFile(filePath, language)) {
|
|
172117
|
+
seen.add(filePath);
|
|
172118
|
+
}
|
|
172119
|
+
}
|
|
172120
|
+
}
|
|
172121
|
+
return seen.size;
|
|
172122
|
+
}
|
|
172123
|
+
function isExcludedCodeFile(filePath, language) {
|
|
172124
|
+
const lower = filePath.toLowerCase();
|
|
172125
|
+
const segments = filePath.split("/");
|
|
172126
|
+
if (language === "typescript") {
|
|
172127
|
+
if (lower.endsWith(".d.ts")) {
|
|
172128
|
+
return true;
|
|
172129
|
+
}
|
|
172130
|
+
if (lower.endsWith(".test.ts") || lower.endsWith(".spec.ts") || segments.includes("__tests__") || segments.includes("tests") || segments.includes("test")) {
|
|
172131
|
+
return true;
|
|
172132
|
+
}
|
|
172133
|
+
}
|
|
172134
|
+
if (language === "swift") {
|
|
172135
|
+
if (lower.endsWith("tests.swift") || segments.includes("Tests") || segments.includes("tests")) {
|
|
172136
|
+
return true;
|
|
172137
|
+
}
|
|
172138
|
+
}
|
|
172139
|
+
if (language === "dart") {
|
|
172140
|
+
if (lower.endsWith("_test.dart") || segments.includes("test")) {
|
|
172141
|
+
return true;
|
|
172142
|
+
}
|
|
172143
|
+
}
|
|
172144
|
+
if (segments.some((segment) => ["generated", "gen", ".dart_tool", "build"].includes(segment.toLowerCase()))) {
|
|
172145
|
+
return true;
|
|
172146
|
+
}
|
|
172147
|
+
return lower.includes(".generated.");
|
|
172148
|
+
}
|
|
172149
|
+
|
|
172150
|
+
// src/core/init-plan.ts
|
|
172151
|
+
import {
|
|
172152
|
+
existsSync as existsSync3,
|
|
172153
|
+
readdirSync as readdirSync3,
|
|
172154
|
+
readFileSync as readFileSync3,
|
|
172155
|
+
realpathSync as realpathSync2,
|
|
172156
|
+
statSync as statSync2
|
|
172157
|
+
} from "fs";
|
|
172158
|
+
import { dirname as dirname3, join as join6, relative as relative4 } from "path";
|
|
172159
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
172160
|
+
var CONFIG_FILE_NAME2 = "docbridge.config.json";
|
|
172161
|
+
var INIT_SKILL_NAMES = [
|
|
172162
|
+
"docbridge-adopt",
|
|
172163
|
+
"docbridge-annotate",
|
|
172164
|
+
"docbridge-link",
|
|
172165
|
+
"docbridge-review",
|
|
172166
|
+
"docbridge-sync"
|
|
172167
|
+
];
|
|
172168
|
+
var INIT_WITH_AGENT_SKILL_NAMES = ["docbridge-adopt"];
|
|
172169
|
+
function planInitCommand(input) {
|
|
172170
|
+
const packageRoot = input.packageRoot ?? resolvePackageRoot();
|
|
172171
|
+
const existingConfig = readExistingConfig(input.projectRoot);
|
|
172172
|
+
const messages = [];
|
|
172173
|
+
const nextSteps = [];
|
|
172174
|
+
let exitCode = 0;
|
|
172175
|
+
const agentResolution = input.command === "init" ? resolveInitAgentTarget(input.discovery, input.options) : resolveInitWithAgentTarget(input.discovery, input.options);
|
|
172176
|
+
if (agentResolution.error !== undefined) {
|
|
172177
|
+
return {
|
|
172178
|
+
command: input.command,
|
|
172179
|
+
configOps: [],
|
|
172180
|
+
skillOps: [],
|
|
172181
|
+
messages: [agentResolution.error],
|
|
172182
|
+
nextSteps: [],
|
|
172183
|
+
agentGuidance: [],
|
|
172184
|
+
exitCode: 1
|
|
172185
|
+
};
|
|
172186
|
+
}
|
|
172187
|
+
const agentTarget = agentResolution.target ?? "none";
|
|
172188
|
+
const configOps = input.command === "init" ? planConfigOperations({
|
|
172189
|
+
projectRoot: input.projectRoot,
|
|
172190
|
+
options: input.options,
|
|
172191
|
+
discovery: input.discovery,
|
|
172192
|
+
confirmedScope: input.confirmedScope,
|
|
172193
|
+
existingConfig,
|
|
172194
|
+
messages
|
|
172195
|
+
}) : [];
|
|
172196
|
+
if (configOps.some((operation) => operation.action === "create" || operation.action === "would-create")) {
|
|
172197
|
+
nextSteps.push("Review docbridge.config.json and adjust include.docs or include.code if needed.");
|
|
172198
|
+
nextSteps.push("Add @doc and @code annotations to link code and documentation.");
|
|
172199
|
+
nextSteps.push("Run docbridge check when links exist.");
|
|
172200
|
+
} else if (existingConfig.ok) {
|
|
172201
|
+
messages.push("Existing docbridge.config.json was left unchanged.");
|
|
172202
|
+
summarizeExistingConfig(existingConfig.config, messages);
|
|
172203
|
+
} else if (existingConfig.rawText !== undefined) {
|
|
172204
|
+
messages.push("Existing docbridge.config.json is invalid and was not modified.");
|
|
172205
|
+
for (const diagnostic of existingConfig.diagnostics) {
|
|
172206
|
+
messages.push(diagnostic.message);
|
|
172207
|
+
}
|
|
172208
|
+
messages.push("Repair the config manually or delete it before re-running init.");
|
|
172209
|
+
exitCode = 1;
|
|
172210
|
+
}
|
|
172211
|
+
const skillOps = planSkillOperations({
|
|
172212
|
+
command: input.command,
|
|
172213
|
+
projectRoot: input.projectRoot,
|
|
172214
|
+
packageRoot,
|
|
172215
|
+
agentTarget,
|
|
172216
|
+
options: input.options
|
|
172217
|
+
});
|
|
172218
|
+
const agentGuidance = input.command === "init-with-agent" && agentTarget !== "none" ? buildAgentGuidance(input.projectRoot, agentTarget) : [];
|
|
172219
|
+
if (input.command === "init-with-agent") {
|
|
172220
|
+
nextSteps.push("Run the printed one-shot command or fallback prompt in your agent.");
|
|
172221
|
+
nextSteps.push("Let docbridge-adopt confirm scope and create or improve docbridge.config.json.");
|
|
172222
|
+
}
|
|
172223
|
+
if (input.options.yes && input.command === "init" && configOps.length === 0 && existingConfig.rawText === undefined && (input.discovery.docs.ambiguous || input.discovery.code.ambiguous)) {
|
|
172224
|
+
exitCode = 1;
|
|
172225
|
+
}
|
|
172226
|
+
return {
|
|
172227
|
+
command: input.command,
|
|
172228
|
+
configOps,
|
|
172229
|
+
skillOps,
|
|
172230
|
+
messages,
|
|
172231
|
+
nextSteps,
|
|
172232
|
+
agentGuidance,
|
|
172233
|
+
exitCode
|
|
172234
|
+
};
|
|
172235
|
+
}
|
|
172236
|
+
function buildConfigFromScope(scope) {
|
|
172237
|
+
const code = {};
|
|
172238
|
+
for (const language of scope.languages) {
|
|
172239
|
+
code[language.language] = { patterns: [...language.patterns] };
|
|
172240
|
+
}
|
|
172241
|
+
return {
|
|
172242
|
+
include: {
|
|
172243
|
+
code,
|
|
172244
|
+
docs: [scope.docsPattern]
|
|
172245
|
+
}
|
|
172246
|
+
};
|
|
172247
|
+
}
|
|
172248
|
+
function resolvePackageRoot(moduleUrl = import.meta.url) {
|
|
172249
|
+
const modulePath = fileURLToPath4(moduleUrl);
|
|
172250
|
+
let resolved;
|
|
172251
|
+
try {
|
|
172252
|
+
resolved = realpathSync2(modulePath);
|
|
172253
|
+
} catch {
|
|
172254
|
+
resolved = modulePath;
|
|
172255
|
+
}
|
|
172256
|
+
let dir = dirname3(resolved);
|
|
172257
|
+
for (;; ) {
|
|
172258
|
+
if (existsSync3(join6(dir, "templates", "skills"))) {
|
|
172259
|
+
return dir;
|
|
172260
|
+
}
|
|
172261
|
+
const parent = dirname3(dir);
|
|
172262
|
+
if (parent === dir) {
|
|
172263
|
+
return dir;
|
|
172264
|
+
}
|
|
172265
|
+
dir = parent;
|
|
172266
|
+
}
|
|
172267
|
+
}
|
|
172268
|
+
function listDistributableSkills(packageRoot) {
|
|
172269
|
+
const skillsRoot = join6(packageRoot, "templates", "skills");
|
|
172270
|
+
let entries;
|
|
172271
|
+
try {
|
|
172272
|
+
entries = readdirSync3(skillsRoot);
|
|
172273
|
+
} catch {
|
|
172274
|
+
return [];
|
|
172275
|
+
}
|
|
172276
|
+
return entries.filter((entry) => entry.startsWith("docbridge-")).filter((entry) => existsSync3(join6(skillsRoot, entry, "SKILL.md"))).sort();
|
|
172277
|
+
}
|
|
172278
|
+
function formatInitPlan(plan) {
|
|
172279
|
+
const lines = [];
|
|
172280
|
+
for (const message of plan.messages) {
|
|
172281
|
+
lines.push(message);
|
|
172282
|
+
}
|
|
172283
|
+
if (plan.configOps.length > 0) {
|
|
172284
|
+
lines.push("");
|
|
172285
|
+
lines.push("Config:");
|
|
172286
|
+
for (const operation of plan.configOps) {
|
|
172287
|
+
lines.push(formatFileOp(operation));
|
|
172288
|
+
if (operation.content !== undefined) {
|
|
172289
|
+
lines.push(operation.content);
|
|
172290
|
+
}
|
|
172291
|
+
}
|
|
172292
|
+
}
|
|
172293
|
+
if (plan.skillOps.length > 0) {
|
|
172294
|
+
lines.push("");
|
|
172295
|
+
lines.push("Skills:");
|
|
172296
|
+
for (const operation of plan.skillOps) {
|
|
172297
|
+
lines.push(formatFileOp(operation));
|
|
172298
|
+
}
|
|
172299
|
+
}
|
|
172300
|
+
if (plan.agentGuidance.length > 0) {
|
|
172301
|
+
lines.push("");
|
|
172302
|
+
lines.push("Agent setup:");
|
|
172303
|
+
for (const guidance of plan.agentGuidance) {
|
|
172304
|
+
lines.push("");
|
|
172305
|
+
lines.push(`${capitalize(guidance.agent)} destination: ${guidance.destination}`);
|
|
172306
|
+
lines.push(`One-shot command: ${guidance.oneShotCommand}`);
|
|
172307
|
+
lines.push("Fallback prompt:");
|
|
172308
|
+
lines.push(guidance.fallbackPrompt);
|
|
172309
|
+
}
|
|
172310
|
+
}
|
|
172311
|
+
if (plan.nextSteps.length > 0) {
|
|
172312
|
+
lines.push("");
|
|
172313
|
+
lines.push("Next steps:");
|
|
172314
|
+
for (const step of plan.nextSteps) {
|
|
172315
|
+
lines.push(`- ${step}`);
|
|
172316
|
+
}
|
|
172317
|
+
}
|
|
172318
|
+
return `${lines.join(`
|
|
172319
|
+
`)}
|
|
172320
|
+
`;
|
|
172321
|
+
}
|
|
172322
|
+
function planConfigOperations(input) {
|
|
172323
|
+
if (input.existingConfig.rawText !== undefined) {
|
|
172324
|
+
return [];
|
|
172325
|
+
}
|
|
172326
|
+
const scope = input.confirmedScope ?? inferConfirmedScope(input.discovery, input.options);
|
|
172327
|
+
if (scope === undefined) {
|
|
172328
|
+
if (input.discovery.docs.message !== undefined) {
|
|
172329
|
+
input.messages.push(input.discovery.docs.message);
|
|
172330
|
+
}
|
|
172331
|
+
if (input.discovery.code.message !== undefined) {
|
|
172332
|
+
input.messages.push(input.discovery.code.message);
|
|
172333
|
+
}
|
|
172334
|
+
if (input.options.yes) {
|
|
172335
|
+
input.messages.push("Automatic setup stopped because docs or code scope is ambiguous. Re-run interactively or confirm scope explicitly.");
|
|
172336
|
+
}
|
|
172337
|
+
return [];
|
|
172338
|
+
}
|
|
172339
|
+
const config = buildConfigFromScope(scope);
|
|
172340
|
+
const content = `${JSON.stringify({ $schema: "./schemas/docbridge.schema.json", ...config }, null, 2)}
|
|
172341
|
+
`;
|
|
172342
|
+
const action = input.options.dryRun ? "would-create" : "create";
|
|
172343
|
+
input.messages.push(`Docs scope: ${scope.docsPattern}`);
|
|
172344
|
+
input.messages.push(`Code scope: ${scope.languages.map((language) => `${language.language} (${language.patterns.join(", ")})`).join("; ")}`);
|
|
172345
|
+
return [
|
|
172346
|
+
{
|
|
172347
|
+
action,
|
|
172348
|
+
path: CONFIG_FILE_NAME2,
|
|
172349
|
+
content,
|
|
172350
|
+
reason: "Create docbridge.config.json from discovered scope."
|
|
172351
|
+
}
|
|
172352
|
+
];
|
|
172353
|
+
}
|
|
172354
|
+
function inferConfirmedScope(discovery, options) {
|
|
172355
|
+
if (discovery.docs.ambiguous || discovery.code.ambiguous) {
|
|
172356
|
+
return;
|
|
172357
|
+
}
|
|
172358
|
+
if (discovery.docs.recommended === undefined || discovery.code.languages.length === 0) {
|
|
172359
|
+
return;
|
|
172360
|
+
}
|
|
172361
|
+
if (!options.yes) {
|
|
172362
|
+
return;
|
|
172363
|
+
}
|
|
172364
|
+
return {
|
|
172365
|
+
docsPattern: discovery.docs.recommended.pattern,
|
|
172366
|
+
languages: discovery.code.languages
|
|
172367
|
+
};
|
|
172368
|
+
}
|
|
172369
|
+
function planSkillOperations(input) {
|
|
172370
|
+
if (input.agentTarget === "none") {
|
|
172371
|
+
return [];
|
|
172372
|
+
}
|
|
172373
|
+
const skillNames = input.command === "init" ? [...INIT_SKILL_NAMES] : [...INIT_WITH_AGENT_SKILL_NAMES];
|
|
172374
|
+
const destinations = agentDestinations(input.agentTarget);
|
|
172375
|
+
const availableSkills = new Set(listDistributableSkills(input.packageRoot));
|
|
172376
|
+
const operations = [];
|
|
172377
|
+
for (const destination of destinations) {
|
|
172378
|
+
for (const skillName of skillNames) {
|
|
172379
|
+
if (!availableSkills.has(skillName)) {
|
|
172380
|
+
continue;
|
|
172381
|
+
}
|
|
172382
|
+
const destinationDir = join6(input.projectRoot, destination, skillName);
|
|
172383
|
+
const exists = existsSync3(destinationDir);
|
|
172384
|
+
let action;
|
|
172385
|
+
let reason;
|
|
172386
|
+
if (exists && input.options.force) {
|
|
172387
|
+
action = input.options.dryRun ? "would-overwrite" : "overwrite";
|
|
172388
|
+
reason = "Overwrite existing skill directory.";
|
|
172389
|
+
} else if (exists) {
|
|
172390
|
+
action = "skip";
|
|
172391
|
+
reason = "Skill directory already exists.";
|
|
172392
|
+
} else {
|
|
172393
|
+
action = input.options.dryRun ? "would-create" : "create";
|
|
172394
|
+
reason = "Install distributable skill template.";
|
|
172395
|
+
}
|
|
172396
|
+
operations.push({
|
|
172397
|
+
action,
|
|
172398
|
+
path: relative4(input.projectRoot, destinationDir),
|
|
172399
|
+
reason
|
|
172400
|
+
});
|
|
172401
|
+
}
|
|
172402
|
+
}
|
|
172403
|
+
return operations;
|
|
172404
|
+
}
|
|
172405
|
+
function buildAgentGuidance(projectRoot, agentTarget) {
|
|
172406
|
+
const guidance = [];
|
|
172407
|
+
if (agentTarget === "codex" || agentTarget === "both") {
|
|
172408
|
+
guidance.push({
|
|
172409
|
+
agent: "codex",
|
|
172410
|
+
destination: ".agents/skills/docbridge-adopt/",
|
|
172411
|
+
oneShotCommand: `cd ${projectRoot} && $docbridge_adopt_prompt`,
|
|
172412
|
+
fallbackPrompt: buildFallbackPrompt(projectRoot, "codex")
|
|
172413
|
+
});
|
|
172414
|
+
}
|
|
172415
|
+
if (agentTarget === "claude" || agentTarget === "both") {
|
|
172416
|
+
guidance.push({
|
|
172417
|
+
agent: "claude",
|
|
172418
|
+
destination: ".claude/skills/docbridge-adopt/",
|
|
172419
|
+
oneShotCommand: `cd ${projectRoot} && /docbridge-adopt`,
|
|
172420
|
+
fallbackPrompt: buildFallbackPrompt(projectRoot, "claude")
|
|
172421
|
+
});
|
|
172422
|
+
}
|
|
172423
|
+
for (const entry of guidance) {
|
|
172424
|
+
entry.oneShotCommand = entry.oneShotCommand.replace("$docbridge_adopt_prompt", 'codex --prompt "Use the docbridge-adopt skill to adopt DocBridge in this repository."');
|
|
172425
|
+
}
|
|
172426
|
+
return guidance;
|
|
172427
|
+
}
|
|
172428
|
+
function buildFallbackPrompt(projectRoot, agent) {
|
|
172429
|
+
const invocation = agent === "claude" ? "Use the docbridge-adopt skill" : "Use the docbridge-adopt skill from .agents/skills/docbridge-adopt";
|
|
172430
|
+
return `${invocation} to adopt DocBridge in ${projectRoot}. Confirm docs and code scope, create or improve docbridge.config.json, install the companion DocBridge skills, and suggest the next linking steps.`;
|
|
172431
|
+
}
|
|
172432
|
+
function resolveInitAgentTarget(discovery, options) {
|
|
172433
|
+
if (options.agentTarget !== undefined) {
|
|
172434
|
+
return { target: options.agentTarget, error: undefined };
|
|
172435
|
+
}
|
|
172436
|
+
if (options.yes) {
|
|
172437
|
+
return { target: discovery.agent.defaultTarget, error: undefined };
|
|
172438
|
+
}
|
|
172439
|
+
return { target: discovery.agent.recommendedTarget, error: undefined };
|
|
172440
|
+
}
|
|
172441
|
+
function resolveInitWithAgentTarget(discovery, options) {
|
|
172442
|
+
if (options.agentTarget !== undefined) {
|
|
172443
|
+
if (options.agentTarget === "none") {
|
|
172444
|
+
return {
|
|
172445
|
+
target: undefined,
|
|
172446
|
+
error: "init-with-agent requires an agent target other than none."
|
|
172447
|
+
};
|
|
172448
|
+
}
|
|
172449
|
+
return { target: options.agentTarget, error: undefined };
|
|
172450
|
+
}
|
|
172451
|
+
if (discovery.agent.defaultTarget === "none") {
|
|
172452
|
+
if (options.yes) {
|
|
172453
|
+
return {
|
|
172454
|
+
target: undefined,
|
|
172455
|
+
error: "init-with-agent --yes requires an explicit agent target when no .agents/ or .claude/ directory exists. Pass --agent-target codex, claude, or both."
|
|
172456
|
+
};
|
|
172457
|
+
}
|
|
172458
|
+
return { target: discovery.agent.recommendedTarget, error: undefined };
|
|
172459
|
+
}
|
|
172460
|
+
return { target: discovery.agent.defaultTarget, error: undefined };
|
|
172461
|
+
}
|
|
172462
|
+
function readExistingConfig(projectRoot) {
|
|
172463
|
+
const configPath = join6(projectRoot, CONFIG_FILE_NAME2);
|
|
172464
|
+
let rawText;
|
|
172465
|
+
try {
|
|
172466
|
+
rawText = readFileSync3(configPath, "utf8");
|
|
172467
|
+
} catch {
|
|
172468
|
+
rawText = undefined;
|
|
172469
|
+
}
|
|
172470
|
+
const resolved = resolveConfig(rawText);
|
|
172471
|
+
return {
|
|
172472
|
+
rawText,
|
|
172473
|
+
ok: resolved.ok,
|
|
172474
|
+
config: resolved.config,
|
|
172475
|
+
diagnostics: resolved.diagnostics
|
|
172476
|
+
};
|
|
172477
|
+
}
|
|
172478
|
+
function summarizeExistingConfig(config, messages) {
|
|
172479
|
+
messages.push(`Docs scope: ${config.include.docs.join(", ")}`);
|
|
172480
|
+
const codeSummary = Object.entries(config.include.code).map(([language, entry]) => `${language}: ${entry?.patterns.join(", ") ?? ""}`).join("; ");
|
|
172481
|
+
messages.push(`Code scope: ${codeSummary}`);
|
|
172482
|
+
}
|
|
172483
|
+
function agentDestinations(agentTarget) {
|
|
172484
|
+
switch (agentTarget) {
|
|
172485
|
+
case "codex":
|
|
172486
|
+
return [".agents/skills"];
|
|
172487
|
+
case "claude":
|
|
172488
|
+
return [".claude/skills"];
|
|
172489
|
+
case "both":
|
|
172490
|
+
return [".agents/skills", ".claude/skills"];
|
|
172491
|
+
default:
|
|
172492
|
+
return [];
|
|
172493
|
+
}
|
|
172494
|
+
}
|
|
172495
|
+
function formatFileOp(operation) {
|
|
172496
|
+
const label = operation.action === "would-create" ? "would create" : operation.action === "would-overwrite" ? "would overwrite" : operation.action;
|
|
172497
|
+
const reason = operation.reason ? ` (${operation.reason})` : "";
|
|
172498
|
+
return `- ${label} ${operation.path}${reason}`;
|
|
172499
|
+
}
|
|
172500
|
+
function capitalize(value) {
|
|
172501
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
172502
|
+
}
|
|
172503
|
+
|
|
172504
|
+
// src/cli/init.ts
|
|
172505
|
+
class InitCliError extends Error {
|
|
172506
|
+
}
|
|
172507
|
+
var AGENT_TARGETS = new Set(["codex", "claude", "both", "none"]);
|
|
172508
|
+
function parseInitOptions(args, command) {
|
|
172509
|
+
const options = {
|
|
172510
|
+
root: ".",
|
|
172511
|
+
yes: false,
|
|
172512
|
+
dryRun: false,
|
|
172513
|
+
force: false,
|
|
172514
|
+
agentTarget: undefined
|
|
172515
|
+
};
|
|
172516
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
172517
|
+
const arg = args[index];
|
|
172518
|
+
if (arg === undefined) {
|
|
172519
|
+
continue;
|
|
172520
|
+
}
|
|
172521
|
+
if (arg === "--yes") {
|
|
172522
|
+
options.yes = true;
|
|
172523
|
+
continue;
|
|
172524
|
+
}
|
|
172525
|
+
if (arg === "--dry-run") {
|
|
172526
|
+
options.dryRun = true;
|
|
172527
|
+
continue;
|
|
172528
|
+
}
|
|
172529
|
+
if (arg === "--force") {
|
|
172530
|
+
options.force = true;
|
|
172531
|
+
continue;
|
|
172532
|
+
}
|
|
172533
|
+
if (arg === "--root") {
|
|
172534
|
+
const root = args[index + 1];
|
|
172535
|
+
if (root === undefined) {
|
|
172536
|
+
throw new InitCliError("--root requires a path.");
|
|
172537
|
+
}
|
|
172538
|
+
options.root = root;
|
|
172539
|
+
index += 1;
|
|
172540
|
+
continue;
|
|
172541
|
+
}
|
|
172542
|
+
if (arg === "--agent-target") {
|
|
172543
|
+
const target = args[index + 1];
|
|
172544
|
+
if (target === undefined) {
|
|
172545
|
+
throw new InitCliError("--agent-target requires a value.");
|
|
172546
|
+
}
|
|
172547
|
+
if (!AGENT_TARGETS.has(target)) {
|
|
172548
|
+
throw new InitCliError(`Unknown agent target: ${target}. Supported values: codex, claude, both${command === "init" ? ", none" : ""}.`);
|
|
172549
|
+
}
|
|
172550
|
+
if (command === "init-with-agent" && target === "none") {
|
|
172551
|
+
throw new InitCliError("init-with-agent requires an agent target other than none.");
|
|
172552
|
+
}
|
|
172553
|
+
options.agentTarget = target;
|
|
172554
|
+
index += 1;
|
|
172555
|
+
continue;
|
|
172556
|
+
}
|
|
172557
|
+
if (arg.startsWith("--")) {
|
|
172558
|
+
throw new InitCliError(`Unknown option: ${arg}`);
|
|
172559
|
+
}
|
|
172560
|
+
throw new InitCliError(`Unexpected argument: ${arg}`);
|
|
172561
|
+
}
|
|
172562
|
+
return options;
|
|
172563
|
+
}
|
|
172564
|
+
function createDefaultPrompts(isInteractive = process.stdin.isTTY) {
|
|
172565
|
+
return {
|
|
172566
|
+
isInteractive,
|
|
172567
|
+
confirm(message, defaultValue = true) {
|
|
172568
|
+
if (!isInteractive) {
|
|
172569
|
+
return defaultValue;
|
|
172570
|
+
}
|
|
172571
|
+
process.stdout.write(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"} `);
|
|
172572
|
+
const answer = readFileSync4(0, "utf8").trim().toLowerCase();
|
|
172573
|
+
if (answer.length === 0) {
|
|
172574
|
+
return defaultValue;
|
|
172575
|
+
}
|
|
172576
|
+
return answer === "y" || answer === "yes";
|
|
172577
|
+
},
|
|
172578
|
+
select(message, choices, defaultChoice) {
|
|
172579
|
+
if (!isInteractive) {
|
|
172580
|
+
return defaultChoice;
|
|
172581
|
+
}
|
|
172582
|
+
process.stdout.write(`${message}
|
|
172583
|
+
`);
|
|
172584
|
+
for (const [index, choice] of choices.entries()) {
|
|
172585
|
+
process.stdout.write(` ${index + 1}. ${choice}
|
|
172586
|
+
`);
|
|
172587
|
+
}
|
|
172588
|
+
process.stdout.write(`Choice [${defaultChoice}]: `);
|
|
172589
|
+
const answer = readFileSync4(0, "utf8").trim();
|
|
172590
|
+
if (answer.length === 0) {
|
|
172591
|
+
return defaultChoice;
|
|
172592
|
+
}
|
|
172593
|
+
const numeric = Number.parseInt(answer, 10);
|
|
172594
|
+
if (!Number.isNaN(numeric) && numeric >= 1 && numeric <= choices.length) {
|
|
172595
|
+
return choices[numeric - 1] ?? defaultChoice;
|
|
172596
|
+
}
|
|
172597
|
+
if (choices.includes(answer)) {
|
|
172598
|
+
return answer;
|
|
172599
|
+
}
|
|
172600
|
+
return defaultChoice;
|
|
172601
|
+
}
|
|
172602
|
+
};
|
|
172603
|
+
}
|
|
172604
|
+
function runInit(options, io, runtime = { prompts: createDefaultPrompts(false) }) {
|
|
172605
|
+
return runInitCommand("init", options, io, runtime);
|
|
172606
|
+
}
|
|
172607
|
+
function runInitWithAgent(options, io, runtime = { prompts: createDefaultPrompts(false) }) {
|
|
172608
|
+
return runInitCommand("init-with-agent", options, io, runtime);
|
|
172609
|
+
}
|
|
172610
|
+
function runInitCommand(command, options, io, runtime) {
|
|
172611
|
+
const projectRoot = resolveProjectRoot(options.root);
|
|
172612
|
+
const discovery = discoverRepository(projectRoot);
|
|
172613
|
+
const confirmedScope = command === "init" ? resolveConfirmedScope({
|
|
172614
|
+
command,
|
|
172615
|
+
discovery,
|
|
172616
|
+
options,
|
|
172617
|
+
prompts: runtime.prompts
|
|
172618
|
+
}) : undefined;
|
|
172619
|
+
const plan = planInitCommand({
|
|
172620
|
+
command,
|
|
172621
|
+
projectRoot,
|
|
172622
|
+
options,
|
|
172623
|
+
discovery,
|
|
172624
|
+
...confirmedScope !== undefined ? { confirmedScope } : {},
|
|
172625
|
+
...runtime.packageRoot !== undefined ? { packageRoot: runtime.packageRoot } : {}
|
|
172626
|
+
});
|
|
172627
|
+
if (!options.dryRun) {
|
|
172628
|
+
executePlan(projectRoot, plan, runtime.packageRoot ?? resolvePackageRoot());
|
|
172629
|
+
}
|
|
172630
|
+
io.stdout(formatInitPlan(plan));
|
|
172631
|
+
return plan.exitCode;
|
|
172632
|
+
}
|
|
172633
|
+
function resolveConfirmedScope(input) {
|
|
172634
|
+
if (input.options.yes) {
|
|
172635
|
+
if (input.discovery.docs.ambiguous || input.discovery.code.ambiguous) {
|
|
172636
|
+
return;
|
|
172637
|
+
}
|
|
172638
|
+
if (input.discovery.docs.recommended === undefined) {
|
|
172639
|
+
return;
|
|
172640
|
+
}
|
|
172641
|
+
return {
|
|
172642
|
+
docsPattern: input.discovery.docs.recommended.pattern,
|
|
172643
|
+
languages: input.discovery.code.languages
|
|
172644
|
+
};
|
|
172645
|
+
}
|
|
172646
|
+
if (!input.prompts.isInteractive) {
|
|
172647
|
+
throw new InitCliError("Interactive setup requires a TTY. Re-run with --yes for non-interactive mode.");
|
|
172648
|
+
}
|
|
172649
|
+
if (input.discovery.docs.ambiguous) {
|
|
172650
|
+
const choices = input.discovery.docs.candidates.map((candidate) => candidate.pattern);
|
|
172651
|
+
if (choices.length === 0) {
|
|
172652
|
+
throw new InitCliError("Docs scope is ambiguous and no candidates were detected. Create docbridge.config.json manually.");
|
|
172653
|
+
}
|
|
172654
|
+
const docsPattern2 = input.prompts.select("Choose docs scope:", choices, choices[0] ?? "");
|
|
172655
|
+
const languages2 = selectLanguages(input.discovery.code.languages, input.prompts);
|
|
172656
|
+
return { docsPattern: docsPattern2, languages: languages2 };
|
|
172657
|
+
}
|
|
172658
|
+
if (input.discovery.docs.recommended === undefined) {
|
|
172659
|
+
throw new InitCliError("Docs scope could not be determined.");
|
|
172660
|
+
}
|
|
172661
|
+
let docsPattern = input.discovery.docs.recommended.pattern;
|
|
172662
|
+
if (!input.prompts.confirm(`Use docs scope ${docsPattern}?`, true)) {
|
|
172663
|
+
const choices = input.discovery.docs.candidates.map((candidate) => candidate.pattern);
|
|
172664
|
+
docsPattern = input.prompts.select("Choose docs scope:", choices, docsPattern);
|
|
172665
|
+
}
|
|
172666
|
+
const languages = selectLanguages(input.discovery.code.languages, input.prompts);
|
|
172667
|
+
if (input.command === "init") {
|
|
172668
|
+
const recommendedTarget = input.discovery.agent.recommendedTarget;
|
|
172669
|
+
if (input.options.agentTarget === undefined && input.discovery.agent.defaultTarget === "none" && !input.prompts.confirm(`Install Codex skills under .agents/skills/?`, recommendedTarget === "codex")) {
|
|
172670
|
+
input.options.agentTarget = "none";
|
|
172671
|
+
}
|
|
172672
|
+
}
|
|
172673
|
+
return {
|
|
172674
|
+
docsPattern,
|
|
172675
|
+
languages
|
|
172676
|
+
};
|
|
172677
|
+
}
|
|
172678
|
+
function selectLanguages(languages, prompts) {
|
|
172679
|
+
if (languages.length <= 1) {
|
|
172680
|
+
return languages;
|
|
172681
|
+
}
|
|
172682
|
+
const selected = [...languages];
|
|
172683
|
+
for (const language of languages) {
|
|
172684
|
+
const keep = prompts.confirm(`Include ${language.language} code scope (${language.patterns.join(", ")})?`, true);
|
|
172685
|
+
if (!keep) {
|
|
172686
|
+
const index = selected.findIndex((entry) => entry.language === language.language);
|
|
172687
|
+
if (index >= 0) {
|
|
172688
|
+
selected.splice(index, 1);
|
|
172689
|
+
}
|
|
172690
|
+
}
|
|
172691
|
+
}
|
|
172692
|
+
if (selected.length === 0) {
|
|
172693
|
+
throw new InitCliError("At least one supported code language must remain in scope.");
|
|
172694
|
+
}
|
|
172695
|
+
return selected;
|
|
172696
|
+
}
|
|
172697
|
+
function executePlan(projectRoot, plan, packageRoot) {
|
|
172698
|
+
for (const operation of plan.configOps) {
|
|
172699
|
+
executeFileOperation(projectRoot, operation);
|
|
172700
|
+
}
|
|
172701
|
+
for (const operation of plan.skillOps) {
|
|
172702
|
+
if (operation.action === "skip") {
|
|
172703
|
+
continue;
|
|
172704
|
+
}
|
|
172705
|
+
executeSkillOperation(projectRoot, packageRoot, operation);
|
|
172706
|
+
}
|
|
172707
|
+
}
|
|
172708
|
+
function executeFileOperation(projectRoot, operation) {
|
|
172709
|
+
const absolutePath = join7(projectRoot, operation.path);
|
|
172710
|
+
if (operation.action === "create" && operation.content !== undefined) {
|
|
172711
|
+
writeFileSync(absolutePath, operation.content, "utf8");
|
|
172712
|
+
}
|
|
172713
|
+
if (operation.action === "overwrite" && operation.content !== undefined) {
|
|
172714
|
+
writeFileSync(absolutePath, operation.content, "utf8");
|
|
172715
|
+
}
|
|
172716
|
+
}
|
|
172717
|
+
function executeSkillOperation(projectRoot, packageRoot, operation) {
|
|
172718
|
+
if (operation.action !== "create" && operation.action !== "overwrite") {
|
|
172719
|
+
return;
|
|
172720
|
+
}
|
|
172721
|
+
const skillName = operation.path.split("/").at(-1);
|
|
172722
|
+
if (skillName === undefined) {
|
|
172723
|
+
return;
|
|
172724
|
+
}
|
|
172725
|
+
const sourceDir = join7(packageRoot, "templates", "skills", skillName);
|
|
172726
|
+
const destinationDir = join7(projectRoot, operation.path);
|
|
172727
|
+
mkdirSync2(destinationDir, { recursive: true });
|
|
172728
|
+
cpSync(sourceDir, destinationDir, { recursive: true, force: true });
|
|
172729
|
+
}
|
|
172730
|
+
function resolveProjectRoot(root) {
|
|
172731
|
+
const projectRoot = resolve3(root);
|
|
172732
|
+
let stats;
|
|
172733
|
+
try {
|
|
172734
|
+
stats = statSync3(projectRoot);
|
|
172735
|
+
} catch {
|
|
172736
|
+
throw new InitCliError(`Root path does not exist: ${root}`);
|
|
172737
|
+
}
|
|
172738
|
+
if (!stats.isDirectory()) {
|
|
172739
|
+
throw new InitCliError(`Root path is not a directory: ${root}`);
|
|
172740
|
+
}
|
|
172741
|
+
return projectRoot;
|
|
172742
|
+
}
|
|
172743
|
+
|
|
171889
172744
|
// src/cli/index.ts
|
|
171890
172745
|
var VERSION = package_default.version;
|
|
171891
172746
|
|
|
@@ -171899,6 +172754,8 @@ Usage:
|
|
|
171899
172754
|
docbridge related [--root <path>] [--json] [--stdin] [--gate] [files...]
|
|
171900
172755
|
docbridge context [--root <path>] [--json] [--stdin] [files...]
|
|
171901
172756
|
docbridge graph [--root <path>] [--json] [--include-content] [--stdin] [files...]
|
|
172757
|
+
docbridge init [--root <path>] [--yes] [--dry-run] [--force] [--agent-target <target>]
|
|
172758
|
+
docbridge init-with-agent [--root <path>] [--yes] [--dry-run] [--force] [--agent-target <target>]
|
|
171902
172759
|
docbridge lsp
|
|
171903
172760
|
|
|
171904
172761
|
Commands:
|
|
@@ -171906,6 +172763,8 @@ Commands:
|
|
|
171906
172763
|
related List the linked counterparts of the given changed files.
|
|
171907
172764
|
context Print the content of the counterparts linked from the given files.
|
|
171908
172765
|
graph Print the resolved link graph.
|
|
172766
|
+
init Set up docbridge.config.json and install DocBridge agent skills.
|
|
172767
|
+
init-with-agent Install docbridge-adopt and print agent setup commands.
|
|
171909
172768
|
lsp Run the Language Server over stdio.
|
|
171910
172769
|
|
|
171911
172770
|
Global options:
|
|
@@ -171933,6 +172792,13 @@ Graph options:
|
|
|
171933
172792
|
--json Emit machine-readable JSON.
|
|
171934
172793
|
--include-content Include lightweight node content. Requires --json.
|
|
171935
172794
|
--stdin Read newline-separated file paths from stdin.
|
|
172795
|
+
|
|
172796
|
+
Init options:
|
|
172797
|
+
--root <path> Project root to set up. Defaults to current directory.
|
|
172798
|
+
--yes Accept safe defaults without prompting.
|
|
172799
|
+
--dry-run Print planned file operations without writing files.
|
|
172800
|
+
--force Overwrite existing installed skills.
|
|
172801
|
+
--agent-target <target> Agent target: codex, claude, both, or none (init only).
|
|
171936
172802
|
`;
|
|
171937
172803
|
function parseCheckOptions(args) {
|
|
171938
172804
|
const options = {
|
|
@@ -172084,11 +172950,11 @@ function parseGraphOptions(args) {
|
|
|
172084
172950
|
}
|
|
172085
172951
|
return options;
|
|
172086
172952
|
}
|
|
172087
|
-
function
|
|
172088
|
-
const projectRoot =
|
|
172953
|
+
function resolveProjectRoot2(root) {
|
|
172954
|
+
const projectRoot = resolve4(root);
|
|
172089
172955
|
let stats;
|
|
172090
172956
|
try {
|
|
172091
|
-
stats =
|
|
172957
|
+
stats = statSync4(projectRoot);
|
|
172092
172958
|
} catch {
|
|
172093
172959
|
throw new CliError(`Root path does not exist: ${root}`);
|
|
172094
172960
|
}
|
|
@@ -172098,7 +172964,7 @@ function resolveProjectRoot(root) {
|
|
|
172098
172964
|
return projectRoot;
|
|
172099
172965
|
}
|
|
172100
172966
|
function runCheck(options, io) {
|
|
172101
|
-
const projectRoot =
|
|
172967
|
+
const projectRoot = resolveProjectRoot2(options.root);
|
|
172102
172968
|
const result = check({ projectRoot, audit: options.audit });
|
|
172103
172969
|
if (options.json) {
|
|
172104
172970
|
io.stdout(`${JSON.stringify(result, null, 2)}
|
|
@@ -172118,10 +172984,10 @@ function runRelated(options, io) {
|
|
|
172118
172984
|
if (!options.stdin && options.files.length === 0) {
|
|
172119
172985
|
throw new CliError("Provide file paths as arguments or use --stdin.");
|
|
172120
172986
|
}
|
|
172121
|
-
const projectRoot =
|
|
172987
|
+
const projectRoot = resolveProjectRoot2(options.root);
|
|
172122
172988
|
const changedFiles = [...options.files];
|
|
172123
172989
|
if (options.stdin) {
|
|
172124
|
-
const readStdin = io.stdin ?? (() =>
|
|
172990
|
+
const readStdin = io.stdin ?? (() => readFileSync5(0, "utf8"));
|
|
172125
172991
|
changedFiles.push(...readStdin().split(`
|
|
172126
172992
|
`));
|
|
172127
172993
|
}
|
|
@@ -172161,10 +173027,10 @@ function runContext(options, io) {
|
|
|
172161
173027
|
if (!options.stdin && options.files.length === 0) {
|
|
172162
173028
|
throw new CliError("Provide file paths as arguments or use --stdin.");
|
|
172163
173029
|
}
|
|
172164
|
-
const projectRoot =
|
|
173030
|
+
const projectRoot = resolveProjectRoot2(options.root);
|
|
172165
173031
|
const inputFiles = [...options.files];
|
|
172166
173032
|
if (options.stdin) {
|
|
172167
|
-
const readStdin = io.stdin ?? (() =>
|
|
173033
|
+
const readStdin = io.stdin ?? (() => readFileSync5(0, "utf8"));
|
|
172168
173034
|
inputFiles.push(...readStdin().split(`
|
|
172169
173035
|
`));
|
|
172170
173036
|
}
|
|
@@ -172188,10 +173054,10 @@ function runContext(options, io) {
|
|
|
172188
173054
|
return 0;
|
|
172189
173055
|
}
|
|
172190
173056
|
function runGraph(options, io) {
|
|
172191
|
-
const projectRoot =
|
|
173057
|
+
const projectRoot = resolveProjectRoot2(options.root);
|
|
172192
173058
|
const inputFiles = [...options.files];
|
|
172193
173059
|
if (options.stdin) {
|
|
172194
|
-
const readStdin = io.stdin ?? (() =>
|
|
173060
|
+
const readStdin = io.stdin ?? (() => readFileSync5(0, "utf8"));
|
|
172195
173061
|
inputFiles.push(...readStdin().split(`
|
|
172196
173062
|
`));
|
|
172197
173063
|
}
|
|
@@ -172222,7 +173088,7 @@ function runGraph(options, io) {
|
|
|
172222
173088
|
function run(argv, io = {
|
|
172223
173089
|
stdout: (text) => process.stdout.write(text),
|
|
172224
173090
|
stderr: (text) => process.stderr.write(text)
|
|
172225
|
-
}) {
|
|
173091
|
+
}, initRuntime = { prompts: createDefaultPrompts() }) {
|
|
172226
173092
|
try {
|
|
172227
173093
|
const [command, ...rest] = argv;
|
|
172228
173094
|
if (command === undefined || command === "--help" || command === "-h") {
|
|
@@ -172253,8 +173119,19 @@ function run(argv, io = {
|
|
|
172253
173119
|
runLspServer();
|
|
172254
173120
|
return 0;
|
|
172255
173121
|
}
|
|
173122
|
+
if (command === "init") {
|
|
173123
|
+
return runInit(parseInitOptions(rest, "init"), io, initRuntime);
|
|
173124
|
+
}
|
|
173125
|
+
if (command === "init-with-agent") {
|
|
173126
|
+
return runInitWithAgent(parseInitOptions(rest, "init-with-agent"), io, initRuntime);
|
|
173127
|
+
}
|
|
172256
173128
|
throw new CliError(`Unknown command: ${command}`);
|
|
172257
173129
|
} catch (error) {
|
|
173130
|
+
if (error instanceof InitCliError || error instanceof CliError) {
|
|
173131
|
+
io.stderr(`${error.message}
|
|
173132
|
+
`);
|
|
173133
|
+
return 1;
|
|
173134
|
+
}
|
|
172258
173135
|
io.stderr(`${error instanceof Error ? error.message : String(error)}
|
|
172259
173136
|
`);
|
|
172260
173137
|
return 1;
|