@devinnn/docdrift 0.1.6 → 0.1.7
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/README.md +1 -1
- package/dist/src/devin/schemas.js +22 -1
- package/dist/src/setup/devin-setup.js +157 -0
- package/dist/src/setup/index.js +20 -94
- package/dist/src/setup/onboard.js +132 -3
- package/dist/src/setup/setup-prompt.js +54 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -163,7 +163,7 @@ This repo has **intentional drift**: the API has been expanded (new fields `full
|
|
|
163
163
|
|
|
164
164
|
Once published to npm, any repo can use the CLI locally or in GitHub Actions.
|
|
165
165
|
|
|
166
|
-
1. **
|
|
166
|
+
1. **Setup** — `npx @devinnn/docdrift setup` (requires `DEVIN_API_KEY`). Devin generates `docdrift.yaml`, `.docdrift/DocDrift.md`, and `.github/workflows/docdrift.yml`. Prerequisite: add your repo in Devin's Machine first. Or add `docdrift.yaml` manually (see `docdrift-yml.md`).
|
|
167
167
|
2. **CLI**
|
|
168
168
|
```bash
|
|
169
169
|
npx @devinnn/docdrift validate
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PatchResultSchema = exports.PatchPlanSchema = void 0;
|
|
3
|
+
exports.PatchResultSchema = exports.SetupOutputSchema = exports.PatchPlanSchema = void 0;
|
|
4
4
|
exports.PatchPlanSchema = {
|
|
5
5
|
type: "object",
|
|
6
6
|
additionalProperties: false,
|
|
@@ -59,6 +59,27 @@ exports.PatchPlanSchema = {
|
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
|
+
/** Structured output for docdrift setup (Devin generates config files) */
|
|
63
|
+
exports.SetupOutputSchema = {
|
|
64
|
+
type: "object",
|
|
65
|
+
additionalProperties: false,
|
|
66
|
+
required: ["docdriftYaml", "summary"],
|
|
67
|
+
properties: {
|
|
68
|
+
docdriftYaml: { type: "string", description: "Full docdrift.yaml content, valid per schema" },
|
|
69
|
+
docDriftMd: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Content for .docdrift/DocDrift.md custom instructions (project-specific guidance for Devin)",
|
|
72
|
+
},
|
|
73
|
+
workflowYml: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Content for .github/workflows/docdrift.yml — must use npx @devinnn/docdrift for validate and run",
|
|
76
|
+
},
|
|
77
|
+
summary: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Brief summary of what you inferred (openapi paths, docsite, verification commands)",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
62
83
|
exports.PatchResultSchema = {
|
|
63
84
|
type: "object",
|
|
64
85
|
additionalProperties: false,
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runSetupDevin = runSetupDevin;
|
|
40
|
+
exports.runSetupDevinAndValidate = runSetupDevinAndValidate;
|
|
41
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
42
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
|
+
require("dotenv/config");
|
|
44
|
+
const v1_1 = require("../devin/v1");
|
|
45
|
+
const schemas_1 = require("../devin/schemas");
|
|
46
|
+
const setup_prompt_1 = require("./setup-prompt");
|
|
47
|
+
const generate_yaml_1 = require("./generate-yaml");
|
|
48
|
+
const index_1 = require("../index");
|
|
49
|
+
const onboard_1 = require("./onboard");
|
|
50
|
+
/** Resolve path to docdrift.schema.json in the package */
|
|
51
|
+
function getSchemaPath() {
|
|
52
|
+
// dist/src/setup -> ../../../ ; src/setup (tsx) -> ../..
|
|
53
|
+
const candidates = [
|
|
54
|
+
node_path_1.default.join(__dirname, "../../../docdrift.schema.json"),
|
|
55
|
+
node_path_1.default.join(__dirname, "../../docdrift.schema.json"),
|
|
56
|
+
];
|
|
57
|
+
const schemaPath = candidates.find((p) => node_fs_1.default.existsSync(p));
|
|
58
|
+
if (!schemaPath) {
|
|
59
|
+
throw new Error(`docdrift.schema.json not found. Tried: ${candidates.join(", ")}`);
|
|
60
|
+
}
|
|
61
|
+
return schemaPath;
|
|
62
|
+
}
|
|
63
|
+
function parseSetupOutput(session) {
|
|
64
|
+
const raw = session?.structured_output ?? session?.data?.structured_output;
|
|
65
|
+
if (!raw || typeof raw !== "object")
|
|
66
|
+
return null;
|
|
67
|
+
const o = raw;
|
|
68
|
+
const yaml = o.docdriftYaml;
|
|
69
|
+
const summary = o.summary;
|
|
70
|
+
if (typeof yaml !== "string" || typeof summary !== "string")
|
|
71
|
+
return null;
|
|
72
|
+
return {
|
|
73
|
+
docdriftYaml: yaml,
|
|
74
|
+
docDriftMd: typeof o.docDriftMd === "string" && o.docDriftMd ? o.docDriftMd : undefined,
|
|
75
|
+
workflowYml: typeof o.workflowYml === "string" && o.workflowYml ? o.workflowYml : undefined,
|
|
76
|
+
summary,
|
|
77
|
+
sessionUrl: "",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function runSetupDevin(options) {
|
|
81
|
+
const cwd = options.cwd ?? process.cwd();
|
|
82
|
+
const outputPath = node_path_1.default.resolve(cwd, options.outputPath ?? "docdrift.yaml");
|
|
83
|
+
const apiKey = process.env.DEVIN_API_KEY?.trim();
|
|
84
|
+
if (!apiKey) {
|
|
85
|
+
throw new Error("DEVIN_API_KEY is required for setup. Set it in .env or export.");
|
|
86
|
+
}
|
|
87
|
+
const configExists = node_fs_1.default.existsSync(outputPath);
|
|
88
|
+
if (configExists && !options.force) {
|
|
89
|
+
const { confirm } = await Promise.resolve().then(() => __importStar(require("@inquirer/prompts")));
|
|
90
|
+
const overwrite = await confirm({
|
|
91
|
+
message: "docdrift.yaml already exists. Overwrite?",
|
|
92
|
+
default: false,
|
|
93
|
+
});
|
|
94
|
+
if (!overwrite) {
|
|
95
|
+
throw new Error("Setup cancelled.");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
process.stdout.write("Uploading schema…\n");
|
|
99
|
+
const schemaPath = getSchemaPath();
|
|
100
|
+
const attachmentUrl = await (0, v1_1.devinUploadAttachment)(apiKey, schemaPath);
|
|
101
|
+
const prompt = (0, setup_prompt_1.buildSetupPrompt)([attachmentUrl]);
|
|
102
|
+
process.stdout.write("Creating Devin session…\n");
|
|
103
|
+
const session = await (0, v1_1.devinCreateSession)(apiKey, {
|
|
104
|
+
prompt,
|
|
105
|
+
unlisted: true,
|
|
106
|
+
max_acu_limit: 2,
|
|
107
|
+
tags: ["docdrift", "setup"],
|
|
108
|
+
attachments: [attachmentUrl],
|
|
109
|
+
structured_output: {
|
|
110
|
+
schema: schemas_1.SetupOutputSchema,
|
|
111
|
+
},
|
|
112
|
+
metadata: { purpose: "docdrift-setup" },
|
|
113
|
+
});
|
|
114
|
+
process.stdout.write("Devin is analyzing the repo and generating config…\n");
|
|
115
|
+
process.stdout.write(`Session: ${session.url}\n`);
|
|
116
|
+
const finalSession = await (0, v1_1.pollUntilTerminal)(apiKey, session.session_id, 15 * 60_000);
|
|
117
|
+
const result = parseSetupOutput(finalSession);
|
|
118
|
+
if (!result) {
|
|
119
|
+
throw new Error("Devin session did not return valid setup output. Check the session for details: " + session.url);
|
|
120
|
+
}
|
|
121
|
+
result.sessionUrl = session.url;
|
|
122
|
+
// Write files
|
|
123
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(outputPath), { recursive: true });
|
|
124
|
+
node_fs_1.default.writeFileSync(outputPath, result.docdriftYaml, "utf8");
|
|
125
|
+
if (result.docDriftMd) {
|
|
126
|
+
(0, onboard_1.ensureDocdriftDir)(cwd);
|
|
127
|
+
const docDriftPath = node_path_1.default.resolve(cwd, ".docdrift", "DocDrift.md");
|
|
128
|
+
node_fs_1.default.writeFileSync(docDriftPath, result.docDriftMd, "utf8");
|
|
129
|
+
}
|
|
130
|
+
if (result.workflowYml) {
|
|
131
|
+
const workflowsDir = node_path_1.default.resolve(cwd, ".github", "workflows");
|
|
132
|
+
node_fs_1.default.mkdirSync(workflowsDir, { recursive: true });
|
|
133
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(workflowsDir, "docdrift.yml"), result.workflowYml, "utf8");
|
|
134
|
+
(0, onboard_1.addSlaCheckWorkflow)(cwd);
|
|
135
|
+
}
|
|
136
|
+
(0, onboard_1.ensureGitignore)(cwd);
|
|
137
|
+
const validation = (0, generate_yaml_1.validateGeneratedConfig)(outputPath);
|
|
138
|
+
if (!validation.ok) {
|
|
139
|
+
throw new Error("Generated config failed validation:\n" + validation.errors.join("\n"));
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
async function runSetupDevinAndValidate(options) {
|
|
144
|
+
const result = await runSetupDevin(options);
|
|
145
|
+
const cwd = options.cwd ?? process.cwd();
|
|
146
|
+
const outputPath = node_path_1.default.resolve(cwd, options.outputPath ?? "docdrift.yaml");
|
|
147
|
+
if (outputPath === node_path_1.default.resolve(cwd, "docdrift.yaml")) {
|
|
148
|
+
try {
|
|
149
|
+
await (0, index_1.runValidate)();
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
package/dist/src/setup/index.js
CHANGED
|
@@ -1,109 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
exports.runSetup = runSetup;
|
|
40
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
41
|
-
const
|
|
42
|
-
const ai_infer_1 = require("./ai-infer");
|
|
43
|
-
const interactive_form_1 = require("./interactive-form");
|
|
44
|
-
const generate_yaml_1 = require("./generate-yaml");
|
|
45
|
-
const onboard_1 = require("./onboard");
|
|
46
|
-
const index_1 = require("../index");
|
|
8
|
+
const devin_setup_1 = require("./devin-setup");
|
|
47
9
|
async function runSetup(options = {}) {
|
|
48
10
|
const cwd = options.cwd ?? process.cwd();
|
|
49
11
|
const outputPath = node_path_1.default.resolve(cwd, options.outputPath ?? "docdrift.yaml");
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
if (!overwrite) {
|
|
58
|
-
console.log("Setup cancelled.");
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
process.stdout.write("Analyzing your repo…\n");
|
|
63
|
-
const fingerprint = (0, repo_fingerprint_1.buildRepoFingerprint)(cwd);
|
|
64
|
-
process.stdout.write("Generating suggestions…\n");
|
|
65
|
-
const inference = await (0, ai_infer_1.inferConfigFromFingerprint)(fingerprint, cwd);
|
|
66
|
-
const formResult = await (0, interactive_form_1.runInteractiveForm)(inference, cwd);
|
|
67
|
-
let config = (0, generate_yaml_1.buildConfigFromInference)(inference, formResult);
|
|
68
|
-
if (formResult.onboarding.addCustomInstructions) {
|
|
69
|
-
const devin = config.devin ?? {};
|
|
70
|
-
config.devin = {
|
|
71
|
-
...devin,
|
|
72
|
-
customInstructions: [".docdrift/DocDrift.md"],
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
(0, generate_yaml_1.writeConfig)(config, outputPath);
|
|
76
|
-
const { created } = (0, onboard_1.runOnboarding)(cwd, formResult.onboarding);
|
|
77
|
-
const validation = (0, generate_yaml_1.validateGeneratedConfig)(outputPath);
|
|
78
|
-
if (!validation.ok) {
|
|
79
|
-
console.error("Config validation failed:\n" + validation.errors.join("\n"));
|
|
80
|
-
throw new Error("Generated config is invalid. Fix the errors above or edit docdrift.yaml manually.");
|
|
81
|
-
}
|
|
82
|
-
if (outputPath === node_path_1.default.resolve(cwd, "docdrift.yaml")) {
|
|
83
|
-
try {
|
|
84
|
-
await (0, index_1.runValidate)();
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
88
|
-
throw err;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
12
|
+
const result = await (0, devin_setup_1.runSetupDevinAndValidate)({
|
|
13
|
+
cwd,
|
|
14
|
+
outputPath: options.outputPath ?? "docdrift.yaml",
|
|
15
|
+
force: options.force,
|
|
16
|
+
openPr: options.openPr,
|
|
17
|
+
});
|
|
91
18
|
console.log("\ndocdrift setup complete\n");
|
|
92
19
|
console.log(" docdrift.yaml written and validated");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
else if (item === ".gitignore")
|
|
99
|
-
console.log(" .gitignore updated");
|
|
100
|
-
else if (item.endsWith("docdrift.yml"))
|
|
101
|
-
console.log(" " + item + " added");
|
|
20
|
+
if (result.docDriftMd)
|
|
21
|
+
console.log(" .docdrift/DocDrift.md created (edit for custom instructions)");
|
|
22
|
+
if (result.workflowYml) {
|
|
23
|
+
console.log(" .github/workflows/docdrift.yml added");
|
|
24
|
+
console.log(" .github/workflows/docdrift-sla-check.yml added");
|
|
102
25
|
}
|
|
26
|
+
console.log(" .gitignore updated");
|
|
27
|
+
console.log("\nSummary: " + result.summary);
|
|
28
|
+
console.log("\nSession: " + result.sessionUrl);
|
|
103
29
|
console.log("\nNext steps:");
|
|
104
|
-
console.log(" 1.
|
|
105
|
-
console.log(" 2.
|
|
106
|
-
console.log(" 3. Run: docdrift validate — verify config");
|
|
107
|
-
console.log(" 4. Run: docdrift detect — check for drift");
|
|
108
|
-
console.log(" 5. Run: docdrift run — create Devin session (requires DEVIN_API_KEY)");
|
|
30
|
+
console.log(" 1. Add DEVIN_API_KEY to repo secrets (Settings > Secrets > Actions)");
|
|
31
|
+
console.log(" 2. Ensure your repo is set up in Devin (Devin's Machine > Add repository)");
|
|
32
|
+
console.log(" 3. Run: npx @devinnn/docdrift validate — verify config");
|
|
33
|
+
console.log(" 4. Run: npx @devinnn/docdrift detect — check for drift");
|
|
34
|
+
console.log(" 5. Run: npx @devinnn/docdrift run — create Devin session (requires DEVIN_API_KEY)");
|
|
109
35
|
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.ensureDocdriftDir = ensureDocdriftDir;
|
|
7
7
|
exports.createCustomInstructionsFile = createCustomInstructionsFile;
|
|
8
8
|
exports.ensureGitignore = ensureGitignore;
|
|
9
|
+
exports.addSlaCheckWorkflow = addSlaCheckWorkflow;
|
|
9
10
|
exports.addGitHubWorkflow = addGitHubWorkflow;
|
|
10
11
|
exports.runOnboarding = runOnboarding;
|
|
11
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -19,7 +20,129 @@ const GITIGNORE_BLOCK = `
|
|
|
19
20
|
.docdrift/state.json
|
|
20
21
|
.docdrift/run-output.json
|
|
21
22
|
`;
|
|
22
|
-
const WORKFLOW_CONTENT =
|
|
23
|
+
const WORKFLOW_CONTENT = `name: docdrift
|
|
24
|
+
|
|
25
|
+
on:
|
|
26
|
+
push:
|
|
27
|
+
branches: ["main"]
|
|
28
|
+
pull_request:
|
|
29
|
+
branches: ["main"]
|
|
30
|
+
workflow_dispatch:
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
docdrift:
|
|
34
|
+
if: github.event_name != 'pull_request' || !startsWith(github.event.pull_request.head.ref, 'docdrift/')
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
permissions:
|
|
37
|
+
contents: write
|
|
38
|
+
pull-requests: write
|
|
39
|
+
issues: write
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
with:
|
|
43
|
+
fetch-depth: 0
|
|
44
|
+
|
|
45
|
+
- uses: actions/setup-node@v4
|
|
46
|
+
with:
|
|
47
|
+
node-version: "20"
|
|
48
|
+
|
|
49
|
+
- run: npm install
|
|
50
|
+
|
|
51
|
+
- name: Determine SHAs
|
|
52
|
+
id: shas
|
|
53
|
+
run: |
|
|
54
|
+
if [ "\$\{\{ github.event_name \}\}" = "pull_request" ]; then
|
|
55
|
+
HEAD_SHA="\$\{\{ github.event.pull_request.head.sha \}\}"
|
|
56
|
+
BASE_SHA="\$\{\{ github.event.pull_request.base.sha \}\}"
|
|
57
|
+
else
|
|
58
|
+
HEAD_SHA="\$\{\{ github.sha \}\}"
|
|
59
|
+
BASE_SHA="\$\{\{ github.event.before \}\}"
|
|
60
|
+
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
|
61
|
+
BASE_SHA="$(git rev-parse HEAD^)"
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
echo "head=\${HEAD_SHA}" >> $GITHUB_OUTPUT
|
|
65
|
+
echo "base=\${BASE_SHA}" >> $GITHUB_OUTPUT
|
|
66
|
+
echo "pr_number=\$\{\{ github.event.pull_request.number || '' \}\}" >> $GITHUB_OUTPUT
|
|
67
|
+
|
|
68
|
+
- name: Restore docdrift state
|
|
69
|
+
uses: actions/cache/restore@v4
|
|
70
|
+
id: docdrift-cache
|
|
71
|
+
with:
|
|
72
|
+
path: .docdrift
|
|
73
|
+
key: docdrift-state-\$\{\{ github.event_name \}\}-\$\{\{ github.event.pull_request.number || 'main' \}\}-\$\{\{ github.run_id \}\}
|
|
74
|
+
restore-keys: |
|
|
75
|
+
docdrift-state-\$\{\{ github.event_name \}\}-\$\{\{ github.event.pull_request.number || 'main' \}\}-
|
|
76
|
+
|
|
77
|
+
- name: Validate config
|
|
78
|
+
run: npx @devinnn/docdrift validate
|
|
79
|
+
|
|
80
|
+
- name: Run Doc Drift
|
|
81
|
+
env:
|
|
82
|
+
DEVIN_API_KEY: \$\{\{ secrets.DEVIN_API_KEY \}\}
|
|
83
|
+
GITHUB_TOKEN: \$\{\{ secrets.GITHUB_TOKEN \}\}
|
|
84
|
+
GITHUB_REPOSITORY: \$\{\{ github.repository \}\}
|
|
85
|
+
GITHUB_SHA: \$\{\{ github.sha \}\}
|
|
86
|
+
GITHUB_EVENT_NAME: \$\{\{ github.event_name \}\}
|
|
87
|
+
GITHUB_PR_NUMBER: \$\{\{ steps.shas.outputs.pr_number \}\}
|
|
88
|
+
run: |
|
|
89
|
+
PR_ARGS=""
|
|
90
|
+
if [ -n "$GITHUB_PR_NUMBER" ]; then
|
|
91
|
+
PR_ARGS="--trigger pull_request --pr-number $GITHUB_PR_NUMBER"
|
|
92
|
+
fi
|
|
93
|
+
npx @devinnn/docdrift run --base \$\{\{ steps.shas.outputs.base \}\} --head \$\{\{ steps.shas.outputs.head \}\} $PR_ARGS
|
|
94
|
+
|
|
95
|
+
- name: Save docdrift state
|
|
96
|
+
if: always()
|
|
97
|
+
uses: actions/cache/save@v4
|
|
98
|
+
with:
|
|
99
|
+
path: .docdrift
|
|
100
|
+
key: docdrift-state-\$\{\{ github.event_name \}\}-\$\{\{ github.event.pull_request.number || 'main' \}\}-\$\{\{ github.run_id \}\}
|
|
101
|
+
|
|
102
|
+
- name: Upload artifacts
|
|
103
|
+
if: always()
|
|
104
|
+
uses: actions/upload-artifact@v4
|
|
105
|
+
with:
|
|
106
|
+
name: docdrift-artifacts
|
|
107
|
+
path: |
|
|
108
|
+
.docdrift/drift_report.json
|
|
109
|
+
.docdrift/metrics.json
|
|
110
|
+
.docdrift/run-output.json
|
|
111
|
+
.docdrift/evidence/**
|
|
112
|
+
.docdrift/state.json
|
|
113
|
+
`;
|
|
114
|
+
const SLA_CHECK_WORKFLOW_CONTENT = `name: docdrift-sla-check
|
|
115
|
+
|
|
116
|
+
on:
|
|
117
|
+
schedule:
|
|
118
|
+
# Run daily at 09:00 UTC (checks for doc-drift PRs open 7+ days)
|
|
119
|
+
- cron: "0 9 * * *"
|
|
120
|
+
workflow_dispatch:
|
|
121
|
+
|
|
122
|
+
jobs:
|
|
123
|
+
sla-check:
|
|
124
|
+
runs-on: ubuntu-latest
|
|
125
|
+
permissions:
|
|
126
|
+
contents: read
|
|
127
|
+
issues: write
|
|
128
|
+
steps:
|
|
129
|
+
- uses: actions/checkout@v4
|
|
130
|
+
|
|
131
|
+
- uses: actions/setup-node@v4
|
|
132
|
+
with:
|
|
133
|
+
node-version: "20"
|
|
134
|
+
|
|
135
|
+
- run: npm install
|
|
136
|
+
|
|
137
|
+
- name: Validate config
|
|
138
|
+
run: npx @devinnn/docdrift validate
|
|
139
|
+
|
|
140
|
+
- name: Run SLA check
|
|
141
|
+
env:
|
|
142
|
+
GITHUB_TOKEN: \$\{\{ secrets.GITHUB_TOKEN \}\}
|
|
143
|
+
GITHUB_REPOSITORY: \$\{\{ github.repository \}\}
|
|
144
|
+
run: npx @devinnn/docdrift sla-check
|
|
145
|
+
`;
|
|
23
146
|
function ensureDocdriftDir(cwd) {
|
|
24
147
|
const dir = node_path_1.default.resolve(cwd, DOCDRIFT_DIR);
|
|
25
148
|
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
@@ -54,11 +177,16 @@ function ensureGitignore(cwd) {
|
|
|
54
177
|
const toAppend = content.endsWith("\n") ? GITIGNORE_BLOCK.trimStart() : GITIGNORE_BLOCK;
|
|
55
178
|
node_fs_1.default.writeFileSync(gitignorePath, content + toAppend, "utf8");
|
|
56
179
|
}
|
|
180
|
+
function addSlaCheckWorkflow(cwd) {
|
|
181
|
+
const workflowsDir = node_path_1.default.resolve(cwd, ".github", "workflows");
|
|
182
|
+
node_fs_1.default.mkdirSync(workflowsDir, { recursive: true });
|
|
183
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(workflowsDir, "docdrift-sla-check.yml"), SLA_CHECK_WORKFLOW_CONTENT, "utf8");
|
|
184
|
+
}
|
|
57
185
|
function addGitHubWorkflow(cwd) {
|
|
58
186
|
const workflowsDir = node_path_1.default.resolve(cwd, ".github", "workflows");
|
|
59
187
|
node_fs_1.default.mkdirSync(workflowsDir, { recursive: true });
|
|
60
|
-
|
|
61
|
-
|
|
188
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(workflowsDir, "docdrift.yml"), WORKFLOW_CONTENT, "utf8");
|
|
189
|
+
addSlaCheckWorkflow(cwd);
|
|
62
190
|
}
|
|
63
191
|
function runOnboarding(cwd, choices) {
|
|
64
192
|
const created = [];
|
|
@@ -75,6 +203,7 @@ function runOnboarding(cwd, choices) {
|
|
|
75
203
|
if (choices.addWorkflow) {
|
|
76
204
|
addGitHubWorkflow(cwd);
|
|
77
205
|
created.push(".github/workflows/docdrift.yml");
|
|
206
|
+
created.push(".github/workflows/docdrift-sla-check.yml");
|
|
78
207
|
}
|
|
79
208
|
return { created };
|
|
80
209
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Prompt for Devin setup session — Devin analyzes the repo and generates
|
|
4
|
+
* docdrift.yaml, DocDrift.md, and GitHub workflow. The repo is already in
|
|
5
|
+
* Devin's Machine, so Devin has full context.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.buildSetupPrompt = buildSetupPrompt;
|
|
9
|
+
function attachmentBlock(urls) {
|
|
10
|
+
return urls.map((url, i) => `- ATTACHMENT ${i + 1}: ${url}`).join("\n");
|
|
11
|
+
}
|
|
12
|
+
function buildSetupPrompt(attachmentUrls) {
|
|
13
|
+
return [
|
|
14
|
+
"You are Devin. Task: set up docdrift for this repository.",
|
|
15
|
+
"",
|
|
16
|
+
"This repo is already loaded in your environment. Analyze it and produce the docdrift configuration files.",
|
|
17
|
+
"",
|
|
18
|
+
"ATTACHMENTS (read these for spec and schema):",
|
|
19
|
+
attachmentBlock(attachmentUrls),
|
|
20
|
+
"",
|
|
21
|
+
"REQUIREMENTS:",
|
|
22
|
+
"",
|
|
23
|
+
"1) docdrift.yaml (REQUIRED)",
|
|
24
|
+
" - Use version: 2",
|
|
25
|
+
" - Use specProviders format with format: openapi3",
|
|
26
|
+
" - Infer: current (type: export, command, outputPath), published path",
|
|
27
|
+
" - Set docsite to your docs root (e.g. docs, apps/docs-site)",
|
|
28
|
+
" - devin: apiVersion v1, unlisted true, maxAcuLimit 2, tags [docdrift]",
|
|
29
|
+
" - If you find an OpenAPI/swagger file or export script, use it",
|
|
30
|
+
" - policy: prCaps, confidence, allowlist (paths Devin may edit), verification.commands (e.g. npm run docs:build, npm run build)",
|
|
31
|
+
" - Add schema comment at top: # yaml-language-server: $schema=https://unpkg.com/@devinnn/docdrift/docdrift.schema.json",
|
|
32
|
+
"",
|
|
33
|
+
"2) .docdrift/DocDrift.md (RECOMMENDED)",
|
|
34
|
+
" - Starter custom instructions: PR title prefix [docdrift], tone, project-specific guidance",
|
|
35
|
+
" - If you include this, set devin.customInstructions: [.docdrift/DocDrift.md] in docdrift.yaml",
|
|
36
|
+
"",
|
|
37
|
+
"3) .github/workflows/docdrift.yml (RECOMMENDED)",
|
|
38
|
+
" - CRITICAL: Use npx @devinnn/docdrift (not npx docdrift)",
|
|
39
|
+
" - Steps: checkout, setup-node 20, Determine SHAs (base/head for push or PR), Validate config, Run Doc Drift",
|
|
40
|
+
" - Env: DEVIN_API_KEY, GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_SHA",
|
|
41
|
+
" - Skip when PR head ref starts with docdrift/ (avoid feedback loop)",
|
|
42
|
+
" - Upload .docdrift artifacts (drift_report.json, metrics.json, evidence, state.json)",
|
|
43
|
+
" - Note: docdrift-sla-check.yml (daily cron for PRs open 7+ days) is added automatically",
|
|
44
|
+
"",
|
|
45
|
+
"OUTPUT:",
|
|
46
|
+
"Emit your final output in the provided structured output schema.",
|
|
47
|
+
"- docdriftYaml: complete YAML string (no leading/trailing comments about the task)",
|
|
48
|
+
"- docDriftMd: content for .docdrift/DocDrift.md, or empty string to omit",
|
|
49
|
+
"- workflowYml: content for .github/workflows/docdrift.yml, or empty string to omit",
|
|
50
|
+
"- summary: what you inferred (openapi export, docsite path, verification commands)",
|
|
51
|
+
"",
|
|
52
|
+
"Do NOT create files in the repo. Only produce the structured output.",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devinnn/docdrift",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Detect and remediate documentation drift with Devin sessions",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -61,4 +61,4 @@
|
|
|
61
61
|
"vitest": "^3.0.5",
|
|
62
62
|
"zod-to-json-schema": "^3.25.1"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|