@devinnn/docdrift 0.1.12 → 0.1.14
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/src/cli.js +2 -1
- package/dist/src/setup/devin-setup.js +102 -14
- package/dist/src/setup/index.js +2 -0
- package/dist/src/setup/setup-prompt.js +39 -4
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -57,7 +57,7 @@ async function main() {
|
|
|
57
57
|
" status Show run status [--since 24h]\n" +
|
|
58
58
|
" sla-check Check SLA for unmerged PRs\n" +
|
|
59
59
|
" setup Interactive setup (generates v2 docdrift.yaml)\n" +
|
|
60
|
-
" generate-yaml Generate config [--output path] [--force]");
|
|
60
|
+
" generate-yaml Generate config [--output path] [--force] [--open-pr]");
|
|
61
61
|
}
|
|
62
62
|
if (command === "setup" || command === "generate-yaml") {
|
|
63
63
|
require("dotenv").config();
|
|
@@ -65,6 +65,7 @@ async function main() {
|
|
|
65
65
|
await runSetup({
|
|
66
66
|
outputPath: getArg(args, "--output") ?? "docdrift.yaml",
|
|
67
67
|
force: args.includes("--force"),
|
|
68
|
+
openPr: args.includes("--open-pr"),
|
|
68
69
|
});
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
@@ -101,23 +101,105 @@ async function runSetupLocal(options) {
|
|
|
101
101
|
sessionUrl: "",
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
/** Extract transcript text from session for fallback parsing when structured_output is empty */
|
|
105
|
+
function getSessionTranscript(session) {
|
|
106
|
+
const s = session;
|
|
107
|
+
const topLevel = (typeof s.output === "string" ? s.output : null) ?? (typeof s.result === "string" ? s.result : null) ?? (typeof s.transcript === "string" ? s.transcript : null);
|
|
108
|
+
if (topLevel)
|
|
109
|
+
return topLevel;
|
|
110
|
+
const data = s.data;
|
|
111
|
+
const messages = (session.messages ?? data?.messages ?? s.events);
|
|
112
|
+
if (!Array.isArray(messages))
|
|
113
|
+
return "";
|
|
114
|
+
return messages
|
|
115
|
+
.map((m) => {
|
|
116
|
+
if (typeof m.content === "string")
|
|
117
|
+
return m.content;
|
|
118
|
+
if (typeof m.text === "string")
|
|
119
|
+
return m.text;
|
|
120
|
+
if (typeof m.message === "string")
|
|
121
|
+
return m.message;
|
|
122
|
+
if (typeof m.body === "string")
|
|
123
|
+
return m.body;
|
|
124
|
+
if (m.message && typeof m.message === "object" && typeof m.message.content === "string")
|
|
125
|
+
return m.message.content;
|
|
126
|
+
return "";
|
|
127
|
+
})
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.join("\n");
|
|
130
|
+
}
|
|
131
|
+
/** Parse setup output from <docdrift_setup_output>...</docdrift_setup_output> JSON block in text */
|
|
132
|
+
function parseFromStrictTag(text) {
|
|
133
|
+
const openTag = `<${setup_prompt_1.DOCDRIFT_SETUP_OUTPUT_TAG}>`;
|
|
134
|
+
const closeTag = `</${setup_prompt_1.DOCDRIFT_SETUP_OUTPUT_TAG}>`;
|
|
135
|
+
const openIdx = text.indexOf(openTag);
|
|
136
|
+
const closeIdx = text.indexOf(closeTag, openIdx);
|
|
137
|
+
if (openIdx === -1 || closeIdx === -1)
|
|
138
|
+
return null;
|
|
139
|
+
const inner = text.slice(openIdx + openTag.length, closeIdx).trim();
|
|
140
|
+
try {
|
|
141
|
+
const o = JSON.parse(inner);
|
|
142
|
+
const yaml = o.docdriftYaml;
|
|
143
|
+
const summary = o.summary;
|
|
144
|
+
if (typeof yaml !== "string" || typeof summary !== "string")
|
|
145
|
+
return null;
|
|
146
|
+
return {
|
|
147
|
+
docdriftYaml: yaml,
|
|
148
|
+
docDriftMd: typeof o.docDriftMd === "string" && o.docDriftMd ? o.docDriftMd : undefined,
|
|
149
|
+
workflowYml: typeof o.workflowYml === "string" && o.workflowYml ? o.workflowYml : undefined,
|
|
150
|
+
summary,
|
|
151
|
+
sessionUrl: "",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
107
155
|
return null;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Fallback: parse from markdown blocks like **docdriftYaml:** ```yaml ... ``` */
|
|
159
|
+
function parseFromMarkdownBlocks(text) {
|
|
160
|
+
const yamlMatch = text.match(/\*\*docdriftYaml:\*\*[\s\S]*?```(?:yaml)?\s*([\s\S]*?)```/i);
|
|
161
|
+
const docMdMatch = text.match(/\*\*docDriftMd:\*\*[\s\S]*?```(?:markdown)?\s*([\s\S]*?)```/i);
|
|
162
|
+
const workflowMatch = text.match(/\*\*workflowYml:\*\*[\s\S]*?```(?:yaml)?\s*([\s\S]*?)```/i);
|
|
163
|
+
const summaryBlock = text.match(/\*\*summary:\*\*([\s\S]*?)(?=\n\n\*\*|$)/i)?.[1]?.trim();
|
|
164
|
+
const yaml = yamlMatch?.[1]?.trim();
|
|
165
|
+
const summary = (summaryBlock || "Inferred from repo analysis").slice(0, 500);
|
|
166
|
+
if (!yaml)
|
|
112
167
|
return null;
|
|
113
168
|
return {
|
|
114
169
|
docdriftYaml: yaml,
|
|
115
|
-
docDriftMd:
|
|
116
|
-
workflowYml:
|
|
170
|
+
docDriftMd: docMdMatch?.[1]?.trim() || undefined,
|
|
171
|
+
workflowYml: workflowMatch?.[1]?.trim() || undefined,
|
|
117
172
|
summary,
|
|
118
173
|
sessionUrl: "",
|
|
119
174
|
};
|
|
120
175
|
}
|
|
176
|
+
function parseSetupOutput(session) {
|
|
177
|
+
const raw = session?.structured_output ?? session?.data?.structured_output;
|
|
178
|
+
if (raw && typeof raw === "object") {
|
|
179
|
+
const o = raw;
|
|
180
|
+
const yaml = o.docdriftYaml;
|
|
181
|
+
const summary = o.summary;
|
|
182
|
+
if (typeof yaml === "string" && typeof summary === "string") {
|
|
183
|
+
return {
|
|
184
|
+
docdriftYaml: yaml,
|
|
185
|
+
docDriftMd: typeof o.docDriftMd === "string" && o.docDriftMd ? o.docDriftMd : undefined,
|
|
186
|
+
workflowYml: typeof o.workflowYml === "string" && o.workflowYml ? o.workflowYml : undefined,
|
|
187
|
+
summary,
|
|
188
|
+
sessionUrl: "",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const transcript = getSessionTranscript(session);
|
|
193
|
+
if (transcript) {
|
|
194
|
+
const fromTag = parseFromStrictTag(transcript);
|
|
195
|
+
if (fromTag)
|
|
196
|
+
return fromTag;
|
|
197
|
+
const fromMarkdown = parseFromMarkdownBlocks(transcript);
|
|
198
|
+
if (fromMarkdown)
|
|
199
|
+
return fromMarkdown;
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
121
203
|
async function runSetupDevin(options) {
|
|
122
204
|
const cwd = options.cwd ?? process.cwd();
|
|
123
205
|
const outputPath = node_path_1.default.resolve(cwd, options.outputPath ?? "docdrift.yaml");
|
|
@@ -139,7 +221,7 @@ async function runSetupDevin(options) {
|
|
|
139
221
|
process.stdout.write("Uploading schema…\n");
|
|
140
222
|
const schemaPath = getSchemaPath();
|
|
141
223
|
const attachmentUrl = await (0, v1_1.devinUploadAttachment)(apiKey, schemaPath);
|
|
142
|
-
const prompt = (0, setup_prompt_1.buildSetupPrompt)([attachmentUrl]);
|
|
224
|
+
const prompt = (0, setup_prompt_1.buildSetupPrompt)([attachmentUrl], { openPr: options.openPr });
|
|
143
225
|
process.stdout.write("Creating Devin session…\n");
|
|
144
226
|
const session = await (0, v1_1.devinCreateSession)(apiKey, {
|
|
145
227
|
prompt,
|
|
@@ -147,20 +229,26 @@ async function runSetupDevin(options) {
|
|
|
147
229
|
max_acu_limit: 2,
|
|
148
230
|
tags: ["docdrift", "setup"],
|
|
149
231
|
attachments: [attachmentUrl],
|
|
150
|
-
|
|
151
|
-
schema: schemas_1.SetupOutputSchema,
|
|
152
|
-
},
|
|
232
|
+
structured_output_schema: schemas_1.SetupOutputSchema,
|
|
153
233
|
metadata: { purpose: "docdrift-setup" },
|
|
154
234
|
});
|
|
155
235
|
process.stdout.write("Devin is analyzing the repo and generating config…\n");
|
|
156
236
|
process.stdout.write(`Session: ${session.url}\n`);
|
|
157
237
|
const finalSession = await (0, v1_1.pollUntilTerminal)(apiKey, session.session_id, 15 * 60_000);
|
|
158
238
|
const result = parseSetupOutput(finalSession);
|
|
239
|
+
if (result?.summary && !(finalSession.structured_output ?? finalSession.data?.structured_output)) {
|
|
240
|
+
process.stdout.write(" (Parsed from session transcript — structured_output was empty; using strict-tag fallback)\n");
|
|
241
|
+
}
|
|
159
242
|
if (!result) {
|
|
160
243
|
throw new Error("Devin session did not return valid setup output. Check the session for details: " + session.url);
|
|
161
244
|
}
|
|
162
245
|
result.sessionUrl = session.url;
|
|
163
|
-
|
|
246
|
+
const prUrl = finalSession.pull_request_url ??
|
|
247
|
+
finalSession.pr_url ??
|
|
248
|
+
finalSession.pull_request?.url;
|
|
249
|
+
if (prUrl)
|
|
250
|
+
result.prUrl = prUrl;
|
|
251
|
+
// Write files (always write for validation; when openPr, Devin also created PR)
|
|
164
252
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(outputPath), { recursive: true });
|
|
165
253
|
node_fs_1.default.writeFileSync(outputPath, result.docdriftYaml, "utf8");
|
|
166
254
|
if (result.docDriftMd) {
|
package/dist/src/setup/index.js
CHANGED
|
@@ -113,6 +113,8 @@ async function runSetup(options = {}) {
|
|
|
113
113
|
console.log("\nSummary: " + result.summary);
|
|
114
114
|
if (result.sessionUrl)
|
|
115
115
|
console.log("\nSession: " + result.sessionUrl);
|
|
116
|
+
if (result.prUrl)
|
|
117
|
+
console.log("PR: " + result.prUrl);
|
|
116
118
|
console.log("\nNext steps:");
|
|
117
119
|
const usedLocal = mode === "local" || (mode === "devin" && !hasDevinKey) || usedLocalFallback;
|
|
118
120
|
if (usedLocal) {
|
|
@@ -5,11 +5,46 @@
|
|
|
5
5
|
* Devin's Machine, so Devin has full context.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.DOCDRIFT_SETUP_OUTPUT_TAG = void 0;
|
|
8
9
|
exports.buildSetupPrompt = buildSetupPrompt;
|
|
10
|
+
/** XML tag used as strict delimiter for fallback parsing from chat transcript */
|
|
11
|
+
exports.DOCDRIFT_SETUP_OUTPUT_TAG = "docdrift_setup_output";
|
|
9
12
|
function attachmentBlock(urls) {
|
|
10
13
|
return urls.map((url, i) => `- ATTACHMENT ${i + 1}: ${url}`).join("\n");
|
|
11
14
|
}
|
|
12
|
-
function
|
|
15
|
+
function strictOutputBlock() {
|
|
16
|
+
return [
|
|
17
|
+
"",
|
|
18
|
+
"STRICT OUTPUT FORMAT (REQUIRED FOR PARSING):",
|
|
19
|
+
"You MUST include this exact block in your final message so we can reliably parse it.",
|
|
20
|
+
"Format: open with <" +
|
|
21
|
+
exports.DOCDRIFT_SETUP_OUTPUT_TAG +
|
|
22
|
+
">, then valid JSON, then close with </" +
|
|
23
|
+
exports.DOCDRIFT_SETUP_OUTPUT_TAG +
|
|
24
|
+
">.",
|
|
25
|
+
"Example (escape quotes in strings as \\\"):",
|
|
26
|
+
"",
|
|
27
|
+
`<${exports.DOCDRIFT_SETUP_OUTPUT_TAG}>`,
|
|
28
|
+
'{"docdriftYaml":"# yaml...","docDriftMd":"# DocDrift...","workflowYml":"name: docdrift...","summary":"OpenAPI at..."}',
|
|
29
|
+
`</${exports.DOCDRIFT_SETUP_OUTPUT_TAG}>`,
|
|
30
|
+
"",
|
|
31
|
+
"Rules: Valid JSON only. Newlines in YAML/yml strings become \\n. Escape \" as \\\".",
|
|
32
|
+
].join("\n");
|
|
33
|
+
}
|
|
34
|
+
function buildSetupPrompt(attachmentUrls, options) {
|
|
35
|
+
const openPr = options?.openPr ?? false;
|
|
36
|
+
const createFilesBlock = openPr
|
|
37
|
+
? [
|
|
38
|
+
"",
|
|
39
|
+
"CREATE A PULL REQUEST:",
|
|
40
|
+
"- Create branch docdrift/setup from main",
|
|
41
|
+
"- Create docdrift.yaml, .docdrift/DocDrift.md, .github/workflows/docdrift.yml in the repo",
|
|
42
|
+
"- Commit with message: [docdrift] Add docdrift configuration",
|
|
43
|
+
"- Push and open a PR to main with title: [docdrift] Add docdrift configuration",
|
|
44
|
+
"- In the PR description, explain what was inferred (openapi export, docsite path, verification commands)",
|
|
45
|
+
"- You MUST still emit the strict output block below so we can validate the config",
|
|
46
|
+
].join("\n")
|
|
47
|
+
: "Do NOT create files in the repo. Only produce the structured output and the strict output block.";
|
|
13
48
|
return [
|
|
14
49
|
"You are Devin. Task: set up docdrift for this repository.",
|
|
15
50
|
"",
|
|
@@ -43,12 +78,12 @@ function buildSetupPrompt(attachmentUrls) {
|
|
|
43
78
|
" - Note: docdrift-sla-check.yml (daily cron for PRs open 7+ days) is added automatically",
|
|
44
79
|
"",
|
|
45
80
|
"OUTPUT:",
|
|
46
|
-
"Emit your final output in the provided structured output schema.",
|
|
81
|
+
"Emit your final output in the provided structured output schema if possible.",
|
|
47
82
|
"- docdriftYaml: complete YAML string (no leading/trailing comments about the task)",
|
|
48
83
|
"- docDriftMd: content for .docdrift/DocDrift.md, or empty string to omit",
|
|
49
84
|
"- workflowYml: content for .github/workflows/docdrift.yml, or empty string to omit",
|
|
50
85
|
"- summary: what you inferred (openapi export, docsite path, verification commands)",
|
|
51
|
-
|
|
52
|
-
|
|
86
|
+
createFilesBlock,
|
|
87
|
+
strictOutputBlock(),
|
|
53
88
|
].join("\n");
|
|
54
89
|
}
|