@wipcomputer/wip-ai-devops-toolbox 1.9.20
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/.license-guard.json +7 -0
- package/.publish-skill.json +4 -0
- package/CHANGELOG.md +1120 -0
- package/CLA.md +19 -0
- package/DEV-GUIDE-GENERAL-PUBLIC.md +882 -0
- package/LICENSE +52 -0
- package/README.md +238 -0
- package/SKILL.md +728 -0
- package/TECHNICAL.md +282 -0
- package/UNIVERSAL-INTERFACE.md +180 -0
- package/_trash/RELEASE-NOTES-v1-8-0.md +29 -0
- package/_trash/RELEASE-NOTES-v1-8-1.md +7 -0
- package/_trash/RELEASE-NOTES-v1-8-2.md +7 -0
- package/_trash/RELEASE-NOTES-v1-9-0.md +37 -0
- package/_trash/RELEASE-NOTES-v1-9-1.md +38 -0
- package/_trash/RELEASE-NOTES-v1-9-10.md +40 -0
- package/_trash/RELEASE-NOTES-v1-9-2.md +40 -0
- package/_trash/RELEASE-NOTES-v1-9-6.md +72 -0
- package/_trash/RELEASE-NOTES-v1-9-7.md +23 -0
- package/_trash/RELEASE-NOTES-v1-9-9.md +75 -0
- package/_trash/guide 2/DEV-GUIDE.md +487 -0
- package/_trash/guide 2/scripts/deploy-public.sh +152 -0
- package/package.json +27 -0
- package/scripts/SKILL-deploy-public.md +61 -0
- package/scripts/SKILL-post-merge-rename.md +47 -0
- package/scripts/deploy-public.sh +264 -0
- package/scripts/post-merge-rename.sh +205 -0
- package/scripts/publish-skill.sh +134 -0
- package/tools/deploy-public/LICENSE +52 -0
- package/tools/deploy-public/README.md +31 -0
- package/tools/deploy-public/SKILL.md +71 -0
- package/tools/deploy-public/deploy-public.sh +264 -0
- package/tools/deploy-public/package.json +9 -0
- package/tools/ldm-jobs/LICENSE +52 -0
- package/tools/ldm-jobs/README.md +46 -0
- package/tools/ldm-jobs/backup.sh +16 -0
- package/tools/ldm-jobs/branch-protect.sh +39 -0
- package/tools/ldm-jobs/crystal-capture.sh +19 -0
- package/tools/ldm-jobs/setup-shell.sh +27 -0
- package/tools/ldm-jobs/visibility-audit.sh +27 -0
- package/tools/post-merge-rename/LICENSE +52 -0
- package/tools/post-merge-rename/README.md +29 -0
- package/tools/post-merge-rename/SKILL.md +57 -0
- package/tools/post-merge-rename/package.json +9 -0
- package/tools/post-merge-rename/post-merge-rename.sh +122 -0
- package/tools/wip-branch-guard/INSTALL.md +41 -0
- package/tools/wip-branch-guard/guard.mjs +259 -0
- package/tools/wip-branch-guard/package.json +11 -0
- package/tools/wip-file-guard/CHANGELOG.md +6 -0
- package/tools/wip-file-guard/LICENSE +52 -0
- package/tools/wip-file-guard/README.md +113 -0
- package/tools/wip-file-guard/REFERENCE.md +86 -0
- package/tools/wip-file-guard/SKILL.md +105 -0
- package/tools/wip-file-guard/guard.mjs +128 -0
- package/tools/wip-file-guard/openclaw.plugin.json +8 -0
- package/tools/wip-file-guard/package.json +27 -0
- package/tools/wip-file-guard/test.sh +119 -0
- package/tools/wip-license-guard/LICENSE +52 -0
- package/tools/wip-license-guard/README.md +32 -0
- package/tools/wip-license-guard/SKILL.md +65 -0
- package/tools/wip-license-guard/cli.mjs +464 -0
- package/tools/wip-license-guard/core.mjs +310 -0
- package/tools/wip-license-guard/hook.mjs +146 -0
- package/tools/wip-license-guard/package.json +15 -0
- package/tools/wip-license-hook/CHANGELOG.md +17 -0
- package/tools/wip-license-hook/LICENSE +52 -0
- package/tools/wip-license-hook/README.md +200 -0
- package/tools/wip-license-hook/SKILL.md +111 -0
- package/tools/wip-license-hook/dist/cli/index.d.ts +15 -0
- package/tools/wip-license-hook/dist/cli/index.js +170 -0
- package/tools/wip-license-hook/dist/cli/index.js.map +1 -0
- package/tools/wip-license-hook/dist/core/detector.d.ts +12 -0
- package/tools/wip-license-hook/dist/core/detector.js +104 -0
- package/tools/wip-license-hook/dist/core/detector.js.map +1 -0
- package/tools/wip-license-hook/dist/core/index.d.ts +4 -0
- package/tools/wip-license-hook/dist/core/index.js +5 -0
- package/tools/wip-license-hook/dist/core/index.js.map +1 -0
- package/tools/wip-license-hook/dist/core/ledger.d.ts +49 -0
- package/tools/wip-license-hook/dist/core/ledger.js +72 -0
- package/tools/wip-license-hook/dist/core/ledger.js.map +1 -0
- package/tools/wip-license-hook/dist/core/reporter.d.ts +14 -0
- package/tools/wip-license-hook/dist/core/reporter.js +227 -0
- package/tools/wip-license-hook/dist/core/reporter.js.map +1 -0
- package/tools/wip-license-hook/dist/core/scanner.d.ts +39 -0
- package/tools/wip-license-hook/dist/core/scanner.js +325 -0
- package/tools/wip-license-hook/dist/core/scanner.js.map +1 -0
- package/tools/wip-license-hook/hooks/pre-pull.sh +55 -0
- package/tools/wip-license-hook/hooks/pre-push.sh +51 -0
- package/tools/wip-license-hook/mcp-server.mjs +119 -0
- package/tools/wip-license-hook/package-lock.json +54 -0
- package/tools/wip-license-hook/package.json +43 -0
- package/tools/wip-license-hook/src/cli/index.ts +189 -0
- package/tools/wip-license-hook/src/core/detector.ts +130 -0
- package/tools/wip-license-hook/src/core/index.ts +4 -0
- package/tools/wip-license-hook/src/core/ledger.ts +116 -0
- package/tools/wip-license-hook/src/core/reporter.ts +255 -0
- package/tools/wip-license-hook/src/core/scanner.ts +367 -0
- package/tools/wip-license-hook/tsconfig.json +16 -0
- package/tools/wip-readme-format/README.md +49 -0
- package/tools/wip-readme-format/SKILL.md +84 -0
- package/tools/wip-readme-format/format.mjs +570 -0
- package/tools/wip-readme-format/package.json +15 -0
- package/tools/wip-release/CHANGELOG.md +42 -0
- package/tools/wip-release/LICENSE +52 -0
- package/tools/wip-release/README.md +45 -0
- package/tools/wip-release/REFERENCE.md +100 -0
- package/tools/wip-release/SKILL.md +139 -0
- package/tools/wip-release/cli.js +161 -0
- package/tools/wip-release/core.mjs +1174 -0
- package/tools/wip-release/mcp-server.mjs +109 -0
- package/tools/wip-release/package.json +36 -0
- package/tools/wip-repo-init/README.md +38 -0
- package/tools/wip-repo-init/SKILL.md +77 -0
- package/tools/wip-repo-init/init.mjs +142 -0
- package/tools/wip-repo-init/package.json +11 -0
- package/tools/wip-repo-permissions-hook/LICENSE +52 -0
- package/tools/wip-repo-permissions-hook/README.md +86 -0
- package/tools/wip-repo-permissions-hook/SKILL.md +73 -0
- package/tools/wip-repo-permissions-hook/cli.js +83 -0
- package/tools/wip-repo-permissions-hook/core.mjs +122 -0
- package/tools/wip-repo-permissions-hook/guard.mjs +64 -0
- package/tools/wip-repo-permissions-hook/mcp-server.mjs +92 -0
- package/tools/wip-repo-permissions-hook/openclaw.plugin.json +8 -0
- package/tools/wip-repo-permissions-hook/package.json +31 -0
- package/tools/wip-repos/LICENSE +52 -0
- package/tools/wip-repos/README.md +77 -0
- package/tools/wip-repos/SKILL.md +80 -0
- package/tools/wip-repos/cli.mjs +176 -0
- package/tools/wip-repos/core.mjs +290 -0
- package/tools/wip-repos/mcp-server.mjs +157 -0
- package/tools/wip-repos/package.json +34 -0
- package/tools/wip-universal-installer/CHANGELOG.md +57 -0
- package/tools/wip-universal-installer/LICENSE +52 -0
- package/tools/wip-universal-installer/README.md +81 -0
- package/tools/wip-universal-installer/REFERENCE.md +122 -0
- package/tools/wip-universal-installer/SKILL.md +87 -0
- package/tools/wip-universal-installer/SPEC.md +180 -0
- package/tools/wip-universal-installer/detect.mjs +130 -0
- package/tools/wip-universal-installer/examples/minimal/README.md +20 -0
- package/tools/wip-universal-installer/examples/minimal/SKILL.md +28 -0
- package/tools/wip-universal-installer/examples/minimal/cli.mjs +4 -0
- package/tools/wip-universal-installer/examples/minimal/core.mjs +8 -0
- package/tools/wip-universal-installer/examples/minimal/mcp-server.mjs +27 -0
- package/tools/wip-universal-installer/examples/minimal/package.json +12 -0
- package/tools/wip-universal-installer/install.js +930 -0
- package/tools/wip-universal-installer/package.json +36 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scanner — detects dependencies and their licenses across package managers.
|
|
3
|
+
* Supports: npm, pip, cargo, go modules. Works offline.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import { detectLicenseFromText, normalizeSpdx, type LicenseId } from "./detector.js";
|
|
10
|
+
import {
|
|
11
|
+
readLedger, writeLedger, upsertEntry, findEntry, addAlert,
|
|
12
|
+
archiveSnapshot, hasLicenseChanged,
|
|
13
|
+
type Ledger, type LedgerEntry, type DependencyType,
|
|
14
|
+
} from "./ledger.js";
|
|
15
|
+
|
|
16
|
+
export interface ScanResult {
|
|
17
|
+
name: string;
|
|
18
|
+
source: string;
|
|
19
|
+
type: DependencyType;
|
|
20
|
+
detectedLicense: LicenseId;
|
|
21
|
+
licenseText?: string;
|
|
22
|
+
wasChanged: boolean;
|
|
23
|
+
isNew: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ScanOptions {
|
|
27
|
+
repoRoot: string;
|
|
28
|
+
offline?: boolean;
|
|
29
|
+
verbose?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── License file discovery ───
|
|
33
|
+
|
|
34
|
+
const LICENSE_NAMES = ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING", "COPYING.md"];
|
|
35
|
+
|
|
36
|
+
export function findLicenseFile(dir: string): string | null {
|
|
37
|
+
for (const name of LICENSE_NAMES) {
|
|
38
|
+
const p = join(dir, name);
|
|
39
|
+
if (existsSync(p)) return p;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function readLicenseFromDir(dir: string): { license: LicenseId; text: string } | null {
|
|
45
|
+
const p = findLicenseFile(dir);
|
|
46
|
+
if (!p) return null;
|
|
47
|
+
const text = readFileSync(p, "utf-8");
|
|
48
|
+
return { license: detectLicenseFromText(text), text };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Package manager detection ───
|
|
52
|
+
|
|
53
|
+
function detectPackageManagers(repoRoot: string): DependencyType[] {
|
|
54
|
+
const types: DependencyType[] = [];
|
|
55
|
+
if (existsSync(join(repoRoot, "package.json"))) types.push("npm");
|
|
56
|
+
if (existsSync(join(repoRoot, "requirements.txt")) || existsSync(join(repoRoot, "Pipfile")) || existsSync(join(repoRoot, "pyproject.toml"))) types.push("pip");
|
|
57
|
+
if (existsSync(join(repoRoot, "Cargo.toml"))) types.push("cargo");
|
|
58
|
+
if (existsSync(join(repoRoot, "go.mod"))) types.push("go");
|
|
59
|
+
return types;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── npm scanning ───
|
|
63
|
+
|
|
64
|
+
function scanNpmDeps(repoRoot: string, offline: boolean): ScanResult[] {
|
|
65
|
+
const results: ScanResult[] = [];
|
|
66
|
+
const pkgPath = join(repoRoot, "package.json");
|
|
67
|
+
if (!existsSync(pkgPath)) return results;
|
|
68
|
+
|
|
69
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
70
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
71
|
+
|
|
72
|
+
for (const [name, _version] of Object.entries(allDeps)) {
|
|
73
|
+
let detectedLicense: LicenseId = "UNKNOWN";
|
|
74
|
+
let licenseText: string | undefined;
|
|
75
|
+
|
|
76
|
+
// Check node_modules first (works offline)
|
|
77
|
+
const modDir = join(repoRoot, "node_modules", name);
|
|
78
|
+
if (existsSync(modDir)) {
|
|
79
|
+
const fromFile = readLicenseFromDir(modDir);
|
|
80
|
+
if (fromFile) {
|
|
81
|
+
detectedLicense = fromFile.license;
|
|
82
|
+
licenseText = fromFile.text;
|
|
83
|
+
}
|
|
84
|
+
// Also check package.json license field
|
|
85
|
+
const modPkg = join(modDir, "package.json");
|
|
86
|
+
if (existsSync(modPkg) && detectedLicense === "UNKNOWN") {
|
|
87
|
+
try {
|
|
88
|
+
const modMeta = JSON.parse(readFileSync(modPkg, "utf-8"));
|
|
89
|
+
if (modMeta.license) detectedLicense = normalizeSpdx(modMeta.license);
|
|
90
|
+
} catch { /* skip */ }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Try npm view if online and still unknown
|
|
95
|
+
if (detectedLicense === "UNKNOWN" && !offline) {
|
|
96
|
+
try {
|
|
97
|
+
const out = execSync(`npm view ${name} license 2>/dev/null`, { encoding: "utf-8", timeout: 10000 }).trim();
|
|
98
|
+
if (out) detectedLicense = normalizeSpdx(out);
|
|
99
|
+
} catch { /* offline or not found */ }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
results.push({
|
|
103
|
+
name,
|
|
104
|
+
source: `npm:${name}`,
|
|
105
|
+
type: "npm",
|
|
106
|
+
detectedLicense,
|
|
107
|
+
licenseText,
|
|
108
|
+
wasChanged: false,
|
|
109
|
+
isNew: true,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── pip scanning ───
|
|
117
|
+
|
|
118
|
+
function parsePipDeps(repoRoot: string): string[] {
|
|
119
|
+
const names: string[] = [];
|
|
120
|
+
const reqPath = join(repoRoot, "requirements.txt");
|
|
121
|
+
if (existsSync(reqPath)) {
|
|
122
|
+
const lines = readFileSync(reqPath, "utf-8").split("\n");
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
126
|
+
const name = trimmed.split(/[=<>!~\[]/)[0].trim();
|
|
127
|
+
if (name) names.push(name);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return names;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function scanPipDeps(repoRoot: string, offline: boolean): ScanResult[] {
|
|
134
|
+
const deps = parsePipDeps(repoRoot);
|
|
135
|
+
const results: ScanResult[] = [];
|
|
136
|
+
|
|
137
|
+
for (const name of deps) {
|
|
138
|
+
let detectedLicense: LicenseId = "UNKNOWN";
|
|
139
|
+
|
|
140
|
+
if (!offline) {
|
|
141
|
+
try {
|
|
142
|
+
const out = execSync(`pip show ${name} 2>/dev/null`, { encoding: "utf-8", timeout: 10000 });
|
|
143
|
+
const match = out.match(/^License:\s*(.+)$/m);
|
|
144
|
+
if (match) detectedLicense = normalizeSpdx(match[1]);
|
|
145
|
+
} catch { /* skip */ }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
results.push({
|
|
149
|
+
name,
|
|
150
|
+
source: `pip:${name}`,
|
|
151
|
+
type: "pip",
|
|
152
|
+
detectedLicense,
|
|
153
|
+
wasChanged: false,
|
|
154
|
+
isNew: true,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return results;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── cargo scanning ───
|
|
162
|
+
|
|
163
|
+
function scanCargoDeps(repoRoot: string, offline: boolean): ScanResult[] {
|
|
164
|
+
const results: ScanResult[] = [];
|
|
165
|
+
const cargoPath = join(repoRoot, "Cargo.toml");
|
|
166
|
+
if (!existsSync(cargoPath)) return results;
|
|
167
|
+
|
|
168
|
+
const content = readFileSync(cargoPath, "utf-8");
|
|
169
|
+
const depSection = content.match(/\[dependencies\]([\s\S]*?)(\[|$)/);
|
|
170
|
+
if (!depSection) return results;
|
|
171
|
+
|
|
172
|
+
const lines = depSection[1].split("\n");
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
const match = line.match(/^(\S+)\s*=/);
|
|
175
|
+
if (!match) continue;
|
|
176
|
+
const name = match[1];
|
|
177
|
+
|
|
178
|
+
let detectedLicense: LicenseId = "UNKNOWN";
|
|
179
|
+
if (!offline) {
|
|
180
|
+
try {
|
|
181
|
+
const out = execSync(`cargo info ${name} 2>/dev/null`, { encoding: "utf-8", timeout: 10000 });
|
|
182
|
+
const lMatch = out.match(/license:\s*(.+)/i);
|
|
183
|
+
if (lMatch) detectedLicense = normalizeSpdx(lMatch[1]);
|
|
184
|
+
} catch { /* skip */ }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
results.push({
|
|
188
|
+
name,
|
|
189
|
+
source: `cargo:${name}`,
|
|
190
|
+
type: "cargo",
|
|
191
|
+
detectedLicense,
|
|
192
|
+
wasChanged: false,
|
|
193
|
+
isNew: true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return results;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── go scanning ───
|
|
201
|
+
|
|
202
|
+
function scanGoDeps(repoRoot: string, _offline: boolean): ScanResult[] {
|
|
203
|
+
const results: ScanResult[] = [];
|
|
204
|
+
const goModPath = join(repoRoot, "go.mod");
|
|
205
|
+
if (!existsSync(goModPath)) return results;
|
|
206
|
+
|
|
207
|
+
const content = readFileSync(goModPath, "utf-8");
|
|
208
|
+
const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/);
|
|
209
|
+
const lines = requireBlock ? requireBlock[1].split("\n") : [];
|
|
210
|
+
|
|
211
|
+
// Also handle single-line requires
|
|
212
|
+
const singleRequires = content.match(/^require\s+(\S+)\s+/gm) ?? [];
|
|
213
|
+
for (const sr of singleRequires) {
|
|
214
|
+
const m = sr.match(/^require\s+(\S+)/);
|
|
215
|
+
if (m) lines.push(m[1]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const line of lines) {
|
|
219
|
+
const trimmed = line.trim();
|
|
220
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
221
|
+
const parts = trimmed.split(/\s+/);
|
|
222
|
+
const name = parts[0];
|
|
223
|
+
if (!name || name.startsWith(")")) continue;
|
|
224
|
+
|
|
225
|
+
results.push({
|
|
226
|
+
name,
|
|
227
|
+
source: `go:${name}`,
|
|
228
|
+
type: "go",
|
|
229
|
+
detectedLicense: "UNKNOWN", // Go modules need network for license check
|
|
230
|
+
wasChanged: false,
|
|
231
|
+
isNew: true,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return results;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ─── Fork/upstream scanning ───
|
|
239
|
+
|
|
240
|
+
function scanUpstreamLicense(repoRoot: string, offline: boolean): ScanResult | null {
|
|
241
|
+
// Check if there's an upstream remote
|
|
242
|
+
try {
|
|
243
|
+
const remote = execSync("git remote get-url upstream 2>/dev/null", {
|
|
244
|
+
cwd: repoRoot, encoding: "utf-8", timeout: 5000
|
|
245
|
+
}).trim();
|
|
246
|
+
if (!remote) return null;
|
|
247
|
+
|
|
248
|
+
// Fetch upstream (if online)
|
|
249
|
+
if (!offline) {
|
|
250
|
+
try {
|
|
251
|
+
execSync("git fetch upstream --quiet 2>/dev/null", { cwd: repoRoot, timeout: 30000 });
|
|
252
|
+
} catch { /* offline, use cached */ }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Try to read LICENSE from upstream/main or upstream/master
|
|
256
|
+
for (const branch of ["upstream/main", "upstream/master"]) {
|
|
257
|
+
try {
|
|
258
|
+
const text = execSync(`git show ${branch}:LICENSE 2>/dev/null`, {
|
|
259
|
+
cwd: repoRoot, encoding: "utf-8", timeout: 5000
|
|
260
|
+
});
|
|
261
|
+
if (text) {
|
|
262
|
+
const license = detectLicenseFromText(text);
|
|
263
|
+
return {
|
|
264
|
+
name: "upstream",
|
|
265
|
+
source: remote,
|
|
266
|
+
type: "fork",
|
|
267
|
+
detectedLicense: license,
|
|
268
|
+
licenseText: text,
|
|
269
|
+
wasChanged: false,
|
|
270
|
+
isNew: true,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
} catch { /* try next branch */ }
|
|
274
|
+
}
|
|
275
|
+
} catch { /* no upstream remote */ }
|
|
276
|
+
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ─── Main scan ───
|
|
281
|
+
|
|
282
|
+
export function scanAll(opts: ScanOptions): ScanResult[] {
|
|
283
|
+
const { repoRoot, offline = false } = opts;
|
|
284
|
+
const managers = detectPackageManagers(repoRoot);
|
|
285
|
+
const results: ScanResult[] = [];
|
|
286
|
+
|
|
287
|
+
// Always check upstream fork
|
|
288
|
+
const upstream = scanUpstreamLicense(repoRoot, offline);
|
|
289
|
+
if (upstream) results.push(upstream);
|
|
290
|
+
|
|
291
|
+
if (managers.includes("npm")) results.push(...scanNpmDeps(repoRoot, offline));
|
|
292
|
+
if (managers.includes("pip")) results.push(...scanPipDeps(repoRoot, offline));
|
|
293
|
+
if (managers.includes("cargo")) results.push(...scanCargoDeps(repoRoot, offline));
|
|
294
|
+
if (managers.includes("go")) results.push(...scanGoDeps(repoRoot, offline));
|
|
295
|
+
|
|
296
|
+
return results;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Run a full scan and update the ledger. Returns results with change detection.
|
|
301
|
+
*/
|
|
302
|
+
export function scanAndUpdate(opts: ScanOptions): ScanResult[] {
|
|
303
|
+
const { repoRoot } = opts;
|
|
304
|
+
const ledger = readLedger(repoRoot);
|
|
305
|
+
const results = scanAll(opts);
|
|
306
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
307
|
+
|
|
308
|
+
for (const result of results) {
|
|
309
|
+
const existing = findEntry(ledger, result.name);
|
|
310
|
+
|
|
311
|
+
if (existing) {
|
|
312
|
+
result.isNew = false;
|
|
313
|
+
existing.license_current = result.detectedLicense;
|
|
314
|
+
existing.last_checked = today;
|
|
315
|
+
|
|
316
|
+
if (hasLicenseChanged(existing)) {
|
|
317
|
+
existing.status = "changed";
|
|
318
|
+
result.wasChanged = true;
|
|
319
|
+
addAlert(ledger, result.name, existing.license_at_adoption, result.detectedLicense);
|
|
320
|
+
} else {
|
|
321
|
+
existing.status = "clean";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
upsertEntry(ledger, existing);
|
|
325
|
+
} else {
|
|
326
|
+
// New dependency
|
|
327
|
+
const entry: LedgerEntry = {
|
|
328
|
+
name: result.name,
|
|
329
|
+
source: result.source,
|
|
330
|
+
type: result.type,
|
|
331
|
+
license_at_adoption: result.detectedLicense,
|
|
332
|
+
license_current: result.detectedLicense,
|
|
333
|
+
adopted_date: today,
|
|
334
|
+
last_checked: today,
|
|
335
|
+
status: "clean",
|
|
336
|
+
};
|
|
337
|
+
upsertEntry(ledger, entry);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Archive license text if available
|
|
341
|
+
if (result.licenseText) {
|
|
342
|
+
archiveSnapshot(repoRoot, result.name, result.licenseText, today);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
ledger.last_full_scan = new Date().toISOString();
|
|
347
|
+
writeLedger(repoRoot, ledger);
|
|
348
|
+
|
|
349
|
+
return results;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Gate check — returns true if safe to proceed, false if blocked.
|
|
354
|
+
*/
|
|
355
|
+
export function gateCheck(repoRoot: string, offline: boolean): { safe: boolean; results: ScanResult[]; alerts: string[] } {
|
|
356
|
+
const results = scanAndUpdate({ repoRoot, offline });
|
|
357
|
+
const changed = results.filter((r) => r.wasChanged);
|
|
358
|
+
const alerts = changed.map(
|
|
359
|
+
(r) => `🚫 LICENSE CHANGED: ${r.name} — was adopted under different terms, now detected as ${r.detectedLicense}`
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
safe: changed.length === 0,
|
|
364
|
+
results,
|
|
365
|
+
alerts,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
###### WIP Computer
|
|
2
|
+
|
|
3
|
+
# README Formatter
|
|
4
|
+
|
|
5
|
+
Generate or validate READMEs that follow the WIP Computer standard. Badges, title, tagline, "Teach Your AI" block, features, interface coverage table, license.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Generates separate section files (README-init-badges.md, README-init-features.md, etc.) so you can edit any section independently
|
|
10
|
+
- Deploy assembles them into the final README
|
|
11
|
+
- Same pattern as release notes: staging, review, deploy
|
|
12
|
+
- Validates existing READMEs against the standard
|
|
13
|
+
|
|
14
|
+
## Templates
|
|
15
|
+
|
|
16
|
+
All standard content lives in `ai/wip-templates/readme/`. Edit the templates, every tool picks up the changes. No code changes needed.
|
|
17
|
+
|
|
18
|
+
| Template | What it is |
|
|
19
|
+
|----------|-----------|
|
|
20
|
+
| `wip-lic-footer.md` | License section (plain text + markdown formats) |
|
|
21
|
+
| `cla.md` | Contributor License Agreement |
|
|
22
|
+
| `LICENSE.md` | Full dual MIT+AGPLv3 LICENSE file |
|
|
23
|
+
| `prompt.md` | Standard "Teach your AI" install prompt template |
|
|
24
|
+
|
|
25
|
+
Both `wip-readme-format` and `wip-license-guard` read from these templates at runtime.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Generate section files for review
|
|
31
|
+
node tools/wip-readme-format/format.mjs /path/to/repo
|
|
32
|
+
|
|
33
|
+
# Assemble sections into final README
|
|
34
|
+
node tools/wip-readme-format/format.mjs /path/to/repo --deploy
|
|
35
|
+
|
|
36
|
+
# Preview without writing
|
|
37
|
+
node tools/wip-readme-format/format.mjs /path/to/repo --dry-run
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- node (18+)
|
|
43
|
+
|
|
44
|
+
## Interfaces
|
|
45
|
+
|
|
46
|
+
- **CLI**: Command-line tool
|
|
47
|
+
- **Skill**: SKILL.md for agent instructions
|
|
48
|
+
|
|
49
|
+
## Part of [AI DevOps Toolbox](https://github.com/wipcomputer/wip-ai-devops-toolbox)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wip-readme-format
|
|
3
|
+
description: Reformat any repo's README to follow the WIP Computer standard.
|
|
4
|
+
license: MIT
|
|
5
|
+
interface: [cli, skill]
|
|
6
|
+
metadata:
|
|
7
|
+
display-name: "README Formatter"
|
|
8
|
+
version: "1.0.0"
|
|
9
|
+
homepage: "https://github.com/wipcomputer/wip-ai-devops-toolbox"
|
|
10
|
+
author: "Parker Todd Brooks"
|
|
11
|
+
category: repo-management
|
|
12
|
+
capabilities:
|
|
13
|
+
- readme-generation
|
|
14
|
+
- readme-validation
|
|
15
|
+
- section-staging
|
|
16
|
+
requires:
|
|
17
|
+
bins: [node]
|
|
18
|
+
openclaw:
|
|
19
|
+
requires:
|
|
20
|
+
bins: [node]
|
|
21
|
+
install:
|
|
22
|
+
- id: node
|
|
23
|
+
kind: node
|
|
24
|
+
package: "@wipcomputer/wip-readme-format"
|
|
25
|
+
bins: [wip-readme-format]
|
|
26
|
+
label: "Install via npm"
|
|
27
|
+
emoji: "📝"
|
|
28
|
+
compatibility: Requires node. Node.js 18+.
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# README Formatter
|
|
32
|
+
|
|
33
|
+
Reformats a repo's README to follow the WIP Computer standard. Agent-first, human-readable.
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
wip-readme-format /path/to/repo # rewrite README.md
|
|
39
|
+
wip-readme-format /path/to/repo --dry-run # preview without writing
|
|
40
|
+
wip-readme-format /path/to/repo --check # validate, exit 0/1
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## What happens
|
|
44
|
+
|
|
45
|
+
1. Detects all interfaces (CLI, Module, MCP, OC Plugin, Skill, CC Hook)
|
|
46
|
+
2. Reads package.json for name, description, repo URL
|
|
47
|
+
3. Reads SKILL.md for features
|
|
48
|
+
4. Generates the README following the standard:
|
|
49
|
+
- WIP Computer header + interface badges
|
|
50
|
+
- Title + tagline
|
|
51
|
+
- "Teach Your AI" onboarding block
|
|
52
|
+
- Features list
|
|
53
|
+
- Interface coverage table (toolbox repos only)
|
|
54
|
+
- More Info links
|
|
55
|
+
- License block
|
|
56
|
+
5. Moves technical content to TECHNICAL.md (never deleted)
|
|
57
|
+
|
|
58
|
+
## The standard
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
[badges]
|
|
62
|
+
# Tool Name
|
|
63
|
+
Tagline.
|
|
64
|
+
|
|
65
|
+
## Teach Your AI to [verb]
|
|
66
|
+
[onboarding prompt block]
|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
[feature list]
|
|
70
|
+
|
|
71
|
+
## Interface Coverage (toolbox only)
|
|
72
|
+
[auto-generated table]
|
|
73
|
+
|
|
74
|
+
## More Info
|
|
75
|
+
- Technical Documentation ... link
|
|
76
|
+
- Universal Interface Spec ... link
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
[standard block]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Interfaces
|
|
83
|
+
|
|
84
|
+
CLI, Skill
|