@openweave/weave-skills 1.0.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/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/config-loader.d.ts +52 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +123 -0
- package/dist/config-loader.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/skill-registry.d.ts +96 -0
- package/dist/skill-registry.d.ts.map +1 -0
- package/dist/skill-registry.js +218 -0
- package/dist/skill-registry.js.map +1 -0
- package/dist/skills/auto-fix.d.ts +35 -0
- package/dist/skills/auto-fix.d.ts.map +1 -0
- package/dist/skills/auto-fix.js +121 -0
- package/dist/skills/auto-fix.js.map +1 -0
- package/dist/skills/cli-interactive.d.ts +60 -0
- package/dist/skills/cli-interactive.d.ts.map +1 -0
- package/dist/skills/cli-interactive.js +264 -0
- package/dist/skills/cli-interactive.js.map +1 -0
- package/dist/skills/code-review.d.ts +39 -0
- package/dist/skills/code-review.d.ts.map +1 -0
- package/dist/skills/code-review.js +204 -0
- package/dist/skills/code-review.js.map +1 -0
- package/dist/skills/commit-composer.d.ts +51 -0
- package/dist/skills/commit-composer.d.ts.map +1 -0
- package/dist/skills/commit-composer.js +223 -0
- package/dist/skills/commit-composer.js.map +1 -0
- package/dist/skills/container-advisor.d.ts +43 -0
- package/dist/skills/container-advisor.d.ts.map +1 -0
- package/dist/skills/container-advisor.js +274 -0
- package/dist/skills/container-advisor.js.map +1 -0
- package/dist/skills/context-memory.d.ts +44 -0
- package/dist/skills/context-memory.d.ts.map +1 -0
- package/dist/skills/context-memory.js +160 -0
- package/dist/skills/context-memory.js.map +1 -0
- package/dist/skills/dep-audit.d.ts +55 -0
- package/dist/skills/dep-audit.d.ts.map +1 -0
- package/dist/skills/dep-audit.js +248 -0
- package/dist/skills/dep-audit.js.map +1 -0
- package/dist/skills/deploy-provision.d.ts +47 -0
- package/dist/skills/deploy-provision.d.ts.map +1 -0
- package/dist/skills/deploy-provision.js +270 -0
- package/dist/skills/deploy-provision.js.map +1 -0
- package/dist/skills/docs-gen.d.ts +36 -0
- package/dist/skills/docs-gen.d.ts.map +1 -0
- package/dist/skills/docs-gen.js +187 -0
- package/dist/skills/docs-gen.js.map +1 -0
- package/dist/skills/index.d.ts +19 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +55 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/multi-repo.d.ts +50 -0
- package/dist/skills/multi-repo.d.ts.map +1 -0
- package/dist/skills/multi-repo.js +175 -0
- package/dist/skills/multi-repo.js.map +1 -0
- package/dist/skills/onboarding.d.ts +48 -0
- package/dist/skills/onboarding.d.ts.map +1 -0
- package/dist/skills/onboarding.js +245 -0
- package/dist/skills/onboarding.js.map +1 -0
- package/dist/skills/perf-profile.d.ts +36 -0
- package/dist/skills/perf-profile.d.ts.map +1 -0
- package/dist/skills/perf-profile.js +179 -0
- package/dist/skills/perf-profile.js.map +1 -0
- package/dist/skills/pipeline-aware.d.ts +33 -0
- package/dist/skills/pipeline-aware.d.ts.map +1 -0
- package/dist/skills/pipeline-aware.js +226 -0
- package/dist/skills/pipeline-aware.js.map +1 -0
- package/dist/skills/refactor.d.ts +33 -0
- package/dist/skills/refactor.d.ts.map +1 -0
- package/dist/skills/refactor.js +210 -0
- package/dist/skills/refactor.js.map +1 -0
- package/dist/skills/test-gen.d.ts +36 -0
- package/dist/skills/test-gen.d.ts.map +1 -0
- package/dist/skills/test-gen.js +154 -0
- package/dist/skills/test-gen.js.map +1 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill: commit-composer
|
|
3
|
+
*
|
|
4
|
+
* Analyses `git diff --staged` and proposes a Conventional Commits message.
|
|
5
|
+
* No LLM required — pure pattern-matching on diff hunks.
|
|
6
|
+
*
|
|
7
|
+
* Conventional format: <type>(<scope>): <description>
|
|
8
|
+
* [optional body]
|
|
9
|
+
* [optional footer: BREAKING CHANGE: ...]
|
|
10
|
+
*
|
|
11
|
+
* Types detected: feat, fix, docs, style, refactor, test, chore, perf, ci, build
|
|
12
|
+
*
|
|
13
|
+
* Input (via SkillContext.graph):
|
|
14
|
+
* - `ctx.graph['diff']` — staged diff string (injectable for tests)
|
|
15
|
+
* - `ctx.graph['stagedFiles']` — string[] (overrides ctx.git?.stagedFiles)
|
|
16
|
+
*
|
|
17
|
+
* Output data:
|
|
18
|
+
* - CommitComposerResult
|
|
19
|
+
*/
|
|
20
|
+
import { execSync } from 'node:child_process';
|
|
21
|
+
export function parseDiffStats(diff) {
|
|
22
|
+
const additions = (diff.match(/^\+[^+]/gm) ?? []).length;
|
|
23
|
+
const deletions = (diff.match(/^-[^-]/gm) ?? []).length;
|
|
24
|
+
const filesChanged = [];
|
|
25
|
+
for (const m of diff.matchAll(/^(?:---|\+\+\+) (?:a\/|b\/)?(.+)$/gm)) {
|
|
26
|
+
const f = m[1].trim();
|
|
27
|
+
if (f !== '/dev/null' && !filesChanged.includes(f)) {
|
|
28
|
+
filesChanged.push(f);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { additions, deletions, filesChanged };
|
|
32
|
+
}
|
|
33
|
+
export function detectScope(files) {
|
|
34
|
+
if (files.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
// pnpm workspace package scope
|
|
37
|
+
const pkgMatch = files[0].match(/^(?:packages|apps)\/([^/]+)\//);
|
|
38
|
+
if (pkgMatch) {
|
|
39
|
+
const pkg = pkgMatch[1];
|
|
40
|
+
// strip weave- prefix for brevity
|
|
41
|
+
return pkg.replace(/^weave-/, '');
|
|
42
|
+
}
|
|
43
|
+
// top-level directory scope
|
|
44
|
+
const topDir = files[0].split('/')[0];
|
|
45
|
+
if (topDir && topDir !== files[0])
|
|
46
|
+
return topDir;
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const TYPE_RULES = [
|
|
50
|
+
{
|
|
51
|
+
type: 'test',
|
|
52
|
+
patterns: [/\.test\.[tj]sx?$/, /\.spec\.[tj]sx?$/, /__tests__\//],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'docs',
|
|
56
|
+
patterns: [/\.md$/, /docs\//],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'ci',
|
|
60
|
+
patterns: [/\.github\//, /\.gitlab-ci\.yml/, /ci\//],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: 'build',
|
|
64
|
+
patterns: [/tsconfig/, /package\.json$/, /vite\.config/, /webpack/, /rollup/],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'style',
|
|
68
|
+
patterns: [/\.css$/, /\.scss$/, /\.less$/, /\.svg$/, /\.png$/],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'chore',
|
|
72
|
+
patterns: [/\.gitignore$/, /\.npmrc$/, /\.env/, /pnpm-lock/],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
export function detectType(files, diff) {
|
|
76
|
+
// If all files match a specific type, use it
|
|
77
|
+
for (const rule of TYPE_RULES) {
|
|
78
|
+
if (files.length > 0 && files.every((f) => rule.patterns.some((p) => p.test(f)))) {
|
|
79
|
+
return rule.type;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Check diff content for semantic clues
|
|
83
|
+
const addedLines = diff
|
|
84
|
+
.split('\n')
|
|
85
|
+
.filter((l) => l.startsWith('+') && !l.startsWith('+++'))
|
|
86
|
+
.join('\n');
|
|
87
|
+
const isFeature = /\bexport\s+(function|class|const|interface|type)\b/.test(addedLines) ||
|
|
88
|
+
/\bnew\s+\w+\s*\(/.test(addedLines);
|
|
89
|
+
const isFix = /\b(fix|bug|error|exception|catch|throw)\b/i.test(addedLines) ||
|
|
90
|
+
/\b(null|undefined)\s*\?\?/.test(addedLines);
|
|
91
|
+
const isRefactor = /\bextract|rename|move|reorganize|simplify\b/i.test(addedLines);
|
|
92
|
+
const isPerf = /\bcache|memoize|debounce|throttle|optimize\b/i.test(addedLines);
|
|
93
|
+
if (isPerf)
|
|
94
|
+
return 'perf';
|
|
95
|
+
if (isFix)
|
|
96
|
+
return 'fix';
|
|
97
|
+
if (isRefactor)
|
|
98
|
+
return 'refactor';
|
|
99
|
+
if (isFeature)
|
|
100
|
+
return 'feat';
|
|
101
|
+
return 'chore';
|
|
102
|
+
}
|
|
103
|
+
export function detectBreakingChange(diff) {
|
|
104
|
+
const removedLines = diff
|
|
105
|
+
.split('\n')
|
|
106
|
+
.filter((l) => l.startsWith('-') && !l.startsWith('---'))
|
|
107
|
+
.join('\n');
|
|
108
|
+
// Removed exports = potentially breaking
|
|
109
|
+
if (/^-\s*export\s+(function|class|const|interface|type)\s+\w+/m.test(removedLines)) {
|
|
110
|
+
return { breaking: true, note: 'Removed public export — downstream consumers may break' };
|
|
111
|
+
}
|
|
112
|
+
// Removed function parameters
|
|
113
|
+
if (/^-.*function\s+\w+\s*\([^)]+\)/m.test(removedLines)) {
|
|
114
|
+
return { breaking: true, note: 'Changed function signature' };
|
|
115
|
+
}
|
|
116
|
+
// BREAKING CHANGE in commit note markers already in diff
|
|
117
|
+
if (/BREAKING[\s_-]CHANGE/i.test(diff)) {
|
|
118
|
+
return { breaking: true, note: 'Breaking change marked in source' };
|
|
119
|
+
}
|
|
120
|
+
return { breaking: false, note: null };
|
|
121
|
+
}
|
|
122
|
+
export function buildDescription(type, _scope, _stats, files) {
|
|
123
|
+
const mainFile = files[0] ?? 'codebase';
|
|
124
|
+
const base = mainFile.replace(/^.*\//, '').replace(/\.[tj]sx?$/, '');
|
|
125
|
+
const descriptions = {
|
|
126
|
+
feat: `add ${base}`,
|
|
127
|
+
fix: `resolve issue in ${base}`,
|
|
128
|
+
docs: `update documentation`,
|
|
129
|
+
style: `apply style changes`,
|
|
130
|
+
refactor: `refactor ${base}`,
|
|
131
|
+
test: `add tests for ${base}`,
|
|
132
|
+
chore: `update project config`,
|
|
133
|
+
perf: `improve performance of ${base}`,
|
|
134
|
+
ci: `update CI/CD configuration`,
|
|
135
|
+
build: `update build configuration`,
|
|
136
|
+
};
|
|
137
|
+
return descriptions[type];
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Skill
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
export const commitComposerSkill = {
|
|
143
|
+
id: 'commit-composer',
|
|
144
|
+
name: 'Commit Message Composer',
|
|
145
|
+
description: 'Analyses staged git diff and proposes a Conventional Commits message with type, scope and description.',
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
enabled: true,
|
|
148
|
+
tags: ['git', 'dx', 'commit'],
|
|
149
|
+
async execute(ctx) {
|
|
150
|
+
const graph = (ctx.graph ?? {});
|
|
151
|
+
// --- Get diff ---
|
|
152
|
+
let diff = '';
|
|
153
|
+
if (typeof graph['diff'] === 'string') {
|
|
154
|
+
diff = graph['diff'];
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
try {
|
|
158
|
+
diff = execSync('git diff --staged', {
|
|
159
|
+
cwd: ctx.projectRoot,
|
|
160
|
+
encoding: 'utf8',
|
|
161
|
+
timeout: 10_000,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
output: 'Failed to read staged diff — is this a git repository with staged changes?',
|
|
168
|
+
error: 'git diff --staged failed',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!diff.trim()) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
output: 'No staged changes found. Stage files with `git add` before composing a commit.',
|
|
176
|
+
error: 'empty diff',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const stats = parseDiffStats(diff);
|
|
180
|
+
// --- File list ---
|
|
181
|
+
const stagedFiles = Array.isArray(graph['stagedFiles'])
|
|
182
|
+
? graph['stagedFiles']
|
|
183
|
+
: (ctx.git?.stagedFiles ?? stats.filesChanged);
|
|
184
|
+
const type = detectType(stagedFiles, diff);
|
|
185
|
+
const scope = detectScope(stagedFiles);
|
|
186
|
+
const description = buildDescription(type, scope, stats, stagedFiles);
|
|
187
|
+
const { breaking, note } = detectBreakingChange(diff);
|
|
188
|
+
// Build message
|
|
189
|
+
const header = scope
|
|
190
|
+
? `${type}(${scope}): ${description}`
|
|
191
|
+
: `${type}: ${description}`;
|
|
192
|
+
const bodyLines = [];
|
|
193
|
+
if (stats.filesChanged.length > 1) {
|
|
194
|
+
bodyLines.push(`Changed ${stats.filesChanged.length} files (+${stats.additions}/-${stats.deletions} lines)`);
|
|
195
|
+
}
|
|
196
|
+
if (breaking && note) {
|
|
197
|
+
bodyLines.push('', `BREAKING CHANGE: ${note}`);
|
|
198
|
+
}
|
|
199
|
+
const suggestedMessage = bodyLines.length > 0
|
|
200
|
+
? `${header}\n\n${bodyLines.join('\n')}`
|
|
201
|
+
: header;
|
|
202
|
+
const result = {
|
|
203
|
+
suggestedMessage,
|
|
204
|
+
type,
|
|
205
|
+
scope,
|
|
206
|
+
description,
|
|
207
|
+
breakingChange: breaking,
|
|
208
|
+
breakingChangeNote: note,
|
|
209
|
+
changedFiles: stagedFiles,
|
|
210
|
+
stats: {
|
|
211
|
+
additions: stats.additions,
|
|
212
|
+
deletions: stats.deletions,
|
|
213
|
+
filesChanged: stats.filesChanged.length,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
success: true,
|
|
218
|
+
output: `Suggested commit: "${header}"${breaking ? ' ⚠️ BREAKING CHANGE' : ''}`,
|
|
219
|
+
data: result,
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
//# sourceMappingURL=commit-composer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-composer.js","sourceRoot":"","sources":["../../src/skills/commit-composer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAwC9C,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACzD,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACxD,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,qCAAqC,CAAC,EAAE,CAAC;QACrE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,+BAA+B;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACjE,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACxB,kCAAkC;QAClC,OAAO,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,4BAA4B;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,MAAM,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IAEjD,OAAO,IAAI,CAAC;AACd,CAAC;AAOD,MAAM,UAAU,GAAe;IAC7B;QACE,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,CAAC;KAClE;IACD;QACE,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;KAC9B;IACD;QACE,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,CAAC,YAAY,EAAE,kBAAkB,EAAE,MAAM,CAAC;KACrD;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,CAAC,UAAU,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,CAAC;KAC9E;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC;KAC/D;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,CAAC,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC;KAC7D;CACF,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAe,EAAE,IAAY;IACtD,6CAA6C;IAC7C,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjF,OAAO,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAED,wCAAwC;IACxC,MAAM,UAAU,GAAG,IAAI;SACpB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;SACxD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,SAAS,GACb,oDAAoD,CAAC,IAAI,CAAC,UAAU,CAAC;QACrE,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEtC,MAAM,KAAK,GACT,4CAA4C,CAAC,IAAI,CAAC,UAAU,CAAC;QAC7D,2BAA2B,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAE/C,MAAM,UAAU,GAAG,8CAA8C,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnF,MAAM,MAAM,GAAG,+CAA+C,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEhF,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAClC,IAAI,SAAS;QAAE,OAAO,MAAM,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,YAAY,GAAG,IAAI;SACtB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;SACxD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,yCAAyC;IACzC,IAAI,4DAA4D,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACpF,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,wDAAwD,EAAE,CAAC;IAC5F,CAAC;IACD,8BAA8B;IAC9B,IAAI,iCAAiC,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACzD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,4BAA4B,EAAE,CAAC;IAChE,CAAC;IACD,yDAAyD;IACzD,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,kCAAkC,EAAE,CAAC;IACtE,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAsB,EACtB,MAAqB,EACrB,MAAiB,EACjB,KAAe;IAEf,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;IACxC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAErE,MAAM,YAAY,GAAqC;QACrD,IAAI,EAAE,OAAO,IAAI,EAAE;QACnB,GAAG,EAAE,oBAAoB,IAAI,EAAE;QAC/B,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,qBAAqB;QAC5B,QAAQ,EAAE,YAAY,IAAI,EAAE;QAC5B,IAAI,EAAE,iBAAiB,IAAI,EAAE;QAC7B,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE,0BAA0B,IAAI,EAAE;QACtC,EAAE,EAAE,4BAA4B;QAChC,KAAK,EAAE,4BAA4B;KACpC,CAAC;IAEF,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,MAAM,CAAC,MAAM,mBAAmB,GAAgB;IAC9C,EAAE,EAAE,iBAAiB;IACrB,IAAI,EAAE,yBAAyB;IAC/B,WAAW,EACT,wGAAwG;IAC1G,OAAO,EAAE,OAAO;IAChB,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC;IAE7B,KAAK,CAAC,OAAO,CAAC,GAAiB;QAC7B,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;QAE3D,mBAAmB;QACnB,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YACtC,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC;gBACH,IAAI,GAAG,QAAQ,CAAC,mBAAmB,EAAE;oBACnC,GAAG,EAAE,GAAG,CAAC,WAAW;oBACpB,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,MAAM;iBAChB,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,4EAA4E;oBACpF,KAAK,EAAE,0BAA0B;iBAClC,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACjB,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,gFAAgF;gBACxF,KAAK,EAAE,YAAY;aACpB,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAEnC,oBAAoB;QACpB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YACrD,CAAC,CAAE,KAAK,CAAC,aAAa,CAAc;YACpC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;QAEjD,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;QACvC,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;QACtE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAEtD,gBAAgB;QAChB,MAAM,MAAM,GAAG,KAAK;YAClB,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,MAAM,WAAW,EAAE;YACrC,CAAC,CAAC,GAAG,IAAI,KAAK,WAAW,EAAE,CAAC;QAE9B,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,SAAS,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,YAAY,CAAC,MAAM,YAAY,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,SAAS,CAAC,CAAC;QAC/G,CAAC;QACD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC;YAC3C,CAAC,CAAC,GAAG,MAAM,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACxC,CAAC,CAAC,MAAM,CAAC;QAEX,MAAM,MAAM,GAAyB;YACnC,gBAAgB;YAChB,IAAI;YACJ,KAAK;YACL,WAAW;YACX,cAAc,EAAE,QAAQ;YACxB,kBAAkB,EAAE,IAAI;YACxB,YAAY,EAAE,WAAW;YACzB,KAAK,EAAE;gBACL,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,YAAY,EAAE,KAAK,CAAC,YAAY,CAAC,MAAM;aACxC;SACF,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,sBAAsB,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,EAAE;YAChF,IAAI,EAAE,MAAM;SACb,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill: container-advisor
|
|
3
|
+
*
|
|
4
|
+
* Audits Dockerfile(s) against a best-practice checklist and produces a
|
|
5
|
+
* structured report with pass/fail per check plus remediation snippets.
|
|
6
|
+
*
|
|
7
|
+
* Checks:
|
|
8
|
+
* 1. Multi-stage build — at least 2 FROM instructions
|
|
9
|
+
* 2. Non-root user — USER instruction present (not root/0)
|
|
10
|
+
* 3. HEALTHCHECK — HEALTHCHECK instruction defined
|
|
11
|
+
* 4. Pinned base image — FROM uses a tag other than "latest" or no-tag
|
|
12
|
+
* 5. Minimal COPY scope — no bare `COPY . .` (copies entire context)
|
|
13
|
+
* 6. No sudo usage — RUN sudo ... is a smell
|
|
14
|
+
* 7. No secrets in ENV — ENV key=value with secret-like name
|
|
15
|
+
* 8. Explicit WORKDIR — WORKDIR instruction set before COPY/RUN
|
|
16
|
+
* 9. No `apt-get upgrade` — unpinned system upgrade → non-reproducible
|
|
17
|
+
* 10. Combined RUN layers — multiple consecutive RUN instructions (layer bloat)
|
|
18
|
+
*
|
|
19
|
+
* Input options (ctx.graph):
|
|
20
|
+
* - `dockerfileContent` {string} — inject Dockerfile text (tests)
|
|
21
|
+
* - `dockerfilePath` {string} — path relative to projectRoot
|
|
22
|
+
*/
|
|
23
|
+
import type { SkillModule } from '../types.js';
|
|
24
|
+
export type CheckStatus = 'pass' | 'fail' | 'warn' | 'skip';
|
|
25
|
+
export interface DockerCheck {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
status: CheckStatus;
|
|
29
|
+
severity: 'error' | 'warning' | 'info';
|
|
30
|
+
details: string;
|
|
31
|
+
remediation: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ContainerReport {
|
|
34
|
+
dockerfilePath: string;
|
|
35
|
+
checks: DockerCheck[];
|
|
36
|
+
score: number;
|
|
37
|
+
errors: number;
|
|
38
|
+
warnings: number;
|
|
39
|
+
summary: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function auditDockerfile(content: string, filePath?: string): ContainerReport;
|
|
42
|
+
export declare const containerAdvisorSkill: SkillModule;
|
|
43
|
+
//# sourceMappingURL=container-advisor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-advisor.d.ts","sourceRoot":"","sources":["../../src/skills/container-advisor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAA6B,MAAM,aAAa,CAAC;AAM1E,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AA2LD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAe,GAAG,eAAe,CAgCzF;AAMD,eAAO,MAAM,qBAAqB,EAAE,WAoDnC,CAAC"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill: container-advisor
|
|
3
|
+
*
|
|
4
|
+
* Audits Dockerfile(s) against a best-practice checklist and produces a
|
|
5
|
+
* structured report with pass/fail per check plus remediation snippets.
|
|
6
|
+
*
|
|
7
|
+
* Checks:
|
|
8
|
+
* 1. Multi-stage build — at least 2 FROM instructions
|
|
9
|
+
* 2. Non-root user — USER instruction present (not root/0)
|
|
10
|
+
* 3. HEALTHCHECK — HEALTHCHECK instruction defined
|
|
11
|
+
* 4. Pinned base image — FROM uses a tag other than "latest" or no-tag
|
|
12
|
+
* 5. Minimal COPY scope — no bare `COPY . .` (copies entire context)
|
|
13
|
+
* 6. No sudo usage — RUN sudo ... is a smell
|
|
14
|
+
* 7. No secrets in ENV — ENV key=value with secret-like name
|
|
15
|
+
* 8. Explicit WORKDIR — WORKDIR instruction set before COPY/RUN
|
|
16
|
+
* 9. No `apt-get upgrade` — unpinned system upgrade → non-reproducible
|
|
17
|
+
* 10. Combined RUN layers — multiple consecutive RUN instructions (layer bloat)
|
|
18
|
+
*
|
|
19
|
+
* Input options (ctx.graph):
|
|
20
|
+
* - `dockerfileContent` {string} — inject Dockerfile text (tests)
|
|
21
|
+
* - `dockerfilePath` {string} — path relative to projectRoot
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Individual check implementations
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function checkMultiStage(lines) {
|
|
29
|
+
const fromCount = lines.filter((l) => /^FROM\s+/i.test(l.trim())).length;
|
|
30
|
+
return fromCount >= 2 ? 'pass' : 'fail';
|
|
31
|
+
}
|
|
32
|
+
function checkNonRootUser(lines) {
|
|
33
|
+
const userLine = lines.find((l) => /^USER\s+/i.test(l.trim()));
|
|
34
|
+
if (!userLine)
|
|
35
|
+
return 'fail';
|
|
36
|
+
const val = userLine.replace(/^USER\s+/i, '').trim().toLowerCase();
|
|
37
|
+
if (val === 'root' || val === '0')
|
|
38
|
+
return 'fail';
|
|
39
|
+
return 'pass';
|
|
40
|
+
}
|
|
41
|
+
function checkHealthcheck(lines) {
|
|
42
|
+
return lines.some((l) => /^HEALTHCHECK\s+/i.test(l.trim())) ? 'pass' : 'warn';
|
|
43
|
+
}
|
|
44
|
+
function checkPinnedBase(lines) {
|
|
45
|
+
const froms = lines.filter((l) => /^FROM\s+/i.test(l.trim()));
|
|
46
|
+
for (const from of froms) {
|
|
47
|
+
const image = from.replace(/^FROM\s+/i, '').split(/\s+/)[0];
|
|
48
|
+
if (image === 'scratch')
|
|
49
|
+
continue; // scratch is fine
|
|
50
|
+
if (!image.includes(':'))
|
|
51
|
+
return 'fail'; // no tag at all
|
|
52
|
+
if (image.endsWith(':latest'))
|
|
53
|
+
return 'fail'; // latest is unpinned
|
|
54
|
+
}
|
|
55
|
+
return 'pass';
|
|
56
|
+
}
|
|
57
|
+
function checkCopyScope(lines) {
|
|
58
|
+
// Bare "COPY . ." or "COPY . /" copies entire context
|
|
59
|
+
return lines.some((l) => /^COPY\s+\.\s+[./]/i.test(l.trim())) ? 'warn' : 'pass';
|
|
60
|
+
}
|
|
61
|
+
function checkNoSudo(lines) {
|
|
62
|
+
return lines.some((l) => /^RUN\b.+\bsudo\b/i.test(l.trim())) ? 'warn' : 'pass';
|
|
63
|
+
}
|
|
64
|
+
function checkNoSecretsInEnv(lines) {
|
|
65
|
+
const SECRET_KEYS = /\b(password|passwd|secret|api_?key|token|private_?key|auth)\s*=/i;
|
|
66
|
+
return lines.some((l) => /^ENV\s+/i.test(l.trim()) && SECRET_KEYS.test(l)) ? 'fail' : 'pass';
|
|
67
|
+
}
|
|
68
|
+
function checkWorkdir(lines) {
|
|
69
|
+
const instrs = lines.map((l) => l.trim()).filter((l) => /^(COPY|RUN|WORKDIR)\s+/i.test(l));
|
|
70
|
+
// WORKDIR should come before first COPY or RUN
|
|
71
|
+
const firstCopyRun = instrs.findIndex((l) => /^(COPY|RUN)\s+/i.test(l));
|
|
72
|
+
const firstWorkdir = instrs.findIndex((l) => /^WORKDIR\s+/i.test(l));
|
|
73
|
+
if (firstWorkdir === -1)
|
|
74
|
+
return 'fail';
|
|
75
|
+
if (firstCopyRun === -1)
|
|
76
|
+
return 'pass';
|
|
77
|
+
return firstWorkdir < firstCopyRun ? 'pass' : 'warn';
|
|
78
|
+
}
|
|
79
|
+
function checkNoAptUpgrade(lines) {
|
|
80
|
+
return lines.some((l) => /apt-get\s+upgrade\b/i.test(l)) ? 'warn' : 'pass';
|
|
81
|
+
}
|
|
82
|
+
function checkRunLayers(lines) {
|
|
83
|
+
let consecutive = 0;
|
|
84
|
+
let maxConsecutive = 0;
|
|
85
|
+
for (const l of lines) {
|
|
86
|
+
if (/^RUN\s+/i.test(l.trim())) {
|
|
87
|
+
consecutive++;
|
|
88
|
+
maxConsecutive = Math.max(maxConsecutive, consecutive);
|
|
89
|
+
}
|
|
90
|
+
else if (l.trim().length > 0 && !l.trim().startsWith('#')) {
|
|
91
|
+
consecutive = 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return maxConsecutive >= 3 ? 'warn' : 'pass';
|
|
95
|
+
}
|
|
96
|
+
const CHECKS = [
|
|
97
|
+
{
|
|
98
|
+
id: 'multi-stage',
|
|
99
|
+
title: 'Multi-stage build',
|
|
100
|
+
severity: 'error',
|
|
101
|
+
run: checkMultiStage,
|
|
102
|
+
failDetails: 'Only one FROM instruction found — single-stage build ships build tools to production.',
|
|
103
|
+
warnDetails: '',
|
|
104
|
+
remediation: 'Split into builder and final stages:\n FROM node:22-alpine AS builder\n RUN npm ci && npm run build\n FROM node:22-alpine\n COPY --from=builder /app/dist ./dist',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'non-root-user',
|
|
108
|
+
title: 'Non-root USER',
|
|
109
|
+
severity: 'error',
|
|
110
|
+
run: checkNonRootUser,
|
|
111
|
+
failDetails: 'Container runs as root — a container escape would grant host root access.',
|
|
112
|
+
warnDetails: '',
|
|
113
|
+
remediation: 'Add before CMD/ENTRYPOINT:\n RUN addgroup -S appgroup && adduser -S appuser -G appgroup\n USER appuser',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'healthcheck',
|
|
117
|
+
title: 'HEALTHCHECK defined',
|
|
118
|
+
severity: 'warning',
|
|
119
|
+
run: checkHealthcheck,
|
|
120
|
+
failDetails: '',
|
|
121
|
+
warnDetails: 'No HEALTHCHECK instruction — orchestrators (Docker Swarm, ECS) cannot detect unhealthy containers.',
|
|
122
|
+
remediation: 'Add:\n HEALTHCHECK --interval=30s --timeout=5s --retries=3 \\\n CMD wget -qO- http://localhost:3000/health || exit 1',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'pinned-base',
|
|
126
|
+
title: 'Pinned base image version',
|
|
127
|
+
severity: 'error',
|
|
128
|
+
run: checkPinnedBase,
|
|
129
|
+
failDetails: 'Base image uses `:latest` or has no tag — builds are not reproducible.',
|
|
130
|
+
warnDetails: '',
|
|
131
|
+
remediation: 'Use a specific digest or version tag:\n FROM node:22.12.0-alpine3.21',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'copy-scope',
|
|
135
|
+
title: 'Scoped COPY (no `COPY . .`)',
|
|
136
|
+
severity: 'warning',
|
|
137
|
+
run: checkCopyScope,
|
|
138
|
+
failDetails: '',
|
|
139
|
+
warnDetails: '`COPY . .` copies the entire build context including secrets, .git, node_modules.',
|
|
140
|
+
remediation: 'Use a .dockerignore and copy only what the image needs:\n COPY package.json pnpm-lock.yaml ./\n COPY src/ ./src/',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'no-sudo',
|
|
144
|
+
title: 'No sudo in RUN',
|
|
145
|
+
severity: 'warning',
|
|
146
|
+
run: checkNoSudo,
|
|
147
|
+
failDetails: '',
|
|
148
|
+
warnDetails: '`sudo` in RUN layers is a smell — the build user should already have required permissions.',
|
|
149
|
+
remediation: 'Grant permissions during image build or switch USER instead of using sudo at runtime.',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'no-secrets-env',
|
|
153
|
+
title: 'No secrets in ENV',
|
|
154
|
+
severity: 'error',
|
|
155
|
+
run: checkNoSecretsInEnv,
|
|
156
|
+
failDetails: 'Secret-like key found in ENV instruction — secrets baked into layers are extractable via `docker history`.',
|
|
157
|
+
warnDetails: '',
|
|
158
|
+
remediation: 'Pass secrets at runtime via environment variables or Docker secrets:\n docker run -e API_KEY=$API_KEY myimage',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'explicit-workdir',
|
|
162
|
+
title: 'WORKDIR set before COPY/RUN',
|
|
163
|
+
severity: 'warning',
|
|
164
|
+
run: checkWorkdir,
|
|
165
|
+
failDetails: 'No WORKDIR set — files are placed in root (/) by default.',
|
|
166
|
+
warnDetails: 'WORKDIR defined after COPY/RUN — early instructions run in an unexpected directory.',
|
|
167
|
+
remediation: 'Set WORKDIR early:\n WORKDIR /app',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'no-apt-upgrade',
|
|
171
|
+
title: 'No `apt-get upgrade`',
|
|
172
|
+
severity: 'warning',
|
|
173
|
+
run: checkNoAptUpgrade,
|
|
174
|
+
failDetails: '',
|
|
175
|
+
warnDetails: '`apt-get upgrade` upgrades all packages to unspecified versions — breaks reproducibility.',
|
|
176
|
+
remediation: 'Pin specific package versions or use a freshly pulled base image instead.',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'run-layers',
|
|
180
|
+
title: 'Minimal RUN layers',
|
|
181
|
+
severity: 'warning',
|
|
182
|
+
run: checkRunLayers,
|
|
183
|
+
failDetails: '',
|
|
184
|
+
warnDetails: '3+ consecutive RUN instructions detected — each adds a layer that increases image size.',
|
|
185
|
+
remediation: 'Chain RUN instructions with &&:\n RUN npm ci \\\n && npm run build \\\n && rm -rf node_modules',
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Main audit function
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
export function auditDockerfile(content, filePath = 'Dockerfile') {
|
|
192
|
+
const lines = content.split('\n');
|
|
193
|
+
const checks = [];
|
|
194
|
+
for (const def of CHECKS) {
|
|
195
|
+
const status = def.run(lines);
|
|
196
|
+
let details = '';
|
|
197
|
+
if (status === 'fail')
|
|
198
|
+
details = def.failDetails;
|
|
199
|
+
else if (status === 'warn')
|
|
200
|
+
details = def.warnDetails;
|
|
201
|
+
checks.push({
|
|
202
|
+
id: def.id,
|
|
203
|
+
title: def.title,
|
|
204
|
+
status,
|
|
205
|
+
severity: def.severity,
|
|
206
|
+
details,
|
|
207
|
+
remediation: status !== 'pass' ? def.remediation : '',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const errors = checks.filter((c) => c.status === 'fail' && c.severity === 'error').length;
|
|
211
|
+
const warnings = checks.filter((c) => (c.status === 'fail' || c.status === 'warn') && c.severity === 'warning').length;
|
|
212
|
+
const passed = checks.filter((c) => c.status === 'pass').length;
|
|
213
|
+
const score = Math.round((passed / checks.length) * 100);
|
|
214
|
+
const summary = errors > 0
|
|
215
|
+
? `❌ ${errors} error(s), ${warnings} warning(s) — score: ${score}/100`
|
|
216
|
+
: warnings > 0
|
|
217
|
+
? `⚠️ ${warnings} warning(s) — score: ${score}/100`
|
|
218
|
+
: `✅ All checks passed — score: ${score}/100`;
|
|
219
|
+
return { dockerfilePath: filePath, checks, score, errors, warnings, summary };
|
|
220
|
+
}
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Skill
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
export const containerAdvisorSkill = {
|
|
225
|
+
id: 'container-advisor',
|
|
226
|
+
name: 'Container Advisor',
|
|
227
|
+
description: 'Audits Dockerfiles against 10 best-practice checks (multi-stage, non-root, HEALTHCHECK, pinned base, etc.)',
|
|
228
|
+
version: '1.0.0',
|
|
229
|
+
enabled: false,
|
|
230
|
+
tags: ['devops', 'docker'],
|
|
231
|
+
async execute(ctx) {
|
|
232
|
+
const opts = ctx.graph ?? {};
|
|
233
|
+
let content = '';
|
|
234
|
+
let filePath = 'Dockerfile';
|
|
235
|
+
if (typeof opts['dockerfileContent'] === 'string') {
|
|
236
|
+
content = opts['dockerfileContent'];
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const rel = typeof opts['dockerfilePath'] === 'string' ? opts['dockerfilePath'] : 'Dockerfile';
|
|
240
|
+
filePath = join(ctx.projectRoot, rel);
|
|
241
|
+
if (!existsSync(filePath)) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
output: `❌ Dockerfile not found at ${filePath}`,
|
|
245
|
+
error: 'dockerfile not found',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
content = readFileSync(filePath, 'utf-8');
|
|
249
|
+
}
|
|
250
|
+
const report = auditDockerfile(content, filePath);
|
|
251
|
+
const lines = [
|
|
252
|
+
`🐳 Container Advisor — ${report.dockerfilePath}`,
|
|
253
|
+
` ${report.summary}`,
|
|
254
|
+
'',
|
|
255
|
+
];
|
|
256
|
+
for (const c of report.checks) {
|
|
257
|
+
const icon = c.status === 'pass' ? '✅' : c.status === 'fail' ? '❌' : '⚠️ ';
|
|
258
|
+
lines.push(` ${icon} ${c.title}`);
|
|
259
|
+
if (c.details)
|
|
260
|
+
lines.push(` ${c.details}`);
|
|
261
|
+
if (c.remediation) {
|
|
262
|
+
lines.push(` 💡 Remediation:`);
|
|
263
|
+
for (const rl of c.remediation.split('\n'))
|
|
264
|
+
lines.push(` ${rl}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
success: report.errors === 0,
|
|
269
|
+
output: lines.join('\n'),
|
|
270
|
+
data: report,
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=container-advisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-advisor.js","sourceRoot":"","sources":["../../src/skills/container-advisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill: context-memory
|
|
3
|
+
*
|
|
4
|
+
* Persists architectural decisions, team agreements and agent reasoning sessions
|
|
5
|
+
* into the WeaveGraph knowledge graph for long-term cross-session memory.
|
|
6
|
+
*
|
|
7
|
+
* Actions:
|
|
8
|
+
* - save — persist a new memory entry as a graph node
|
|
9
|
+
* - load — retrieve entries by id or tag
|
|
10
|
+
* - list — list all persisted memory entries (optionally filtered by type/tag)
|
|
11
|
+
*
|
|
12
|
+
* Input (via SkillContext.graph):
|
|
13
|
+
* - `ctx.graph['action']` — 'save' | 'load' | 'list' (default: 'list')
|
|
14
|
+
* - `ctx.graph['entry']` — MemoryEntry (required for 'save')
|
|
15
|
+
* - `ctx.graph['query']` — string keyword for 'load'
|
|
16
|
+
* - `ctx.graph['store']` — Record<string,MemoryEntry> injectable in-memory store for tests
|
|
17
|
+
*
|
|
18
|
+
* Output data:
|
|
19
|
+
* - ContextMemoryResult
|
|
20
|
+
*/
|
|
21
|
+
import type { SkillModule } from '../types.js';
|
|
22
|
+
export type MemoryEntryType = 'decision' | 'agreement' | 'reasoning' | 'constraint' | 'pattern' | 'note';
|
|
23
|
+
export interface MemoryEntry {
|
|
24
|
+
id: string;
|
|
25
|
+
type: MemoryEntryType;
|
|
26
|
+
title: string;
|
|
27
|
+
content: string;
|
|
28
|
+
tags: string[];
|
|
29
|
+
createdAt: string;
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ContextMemoryResult {
|
|
33
|
+
action: 'save' | 'load' | 'list';
|
|
34
|
+
saved?: MemoryEntry;
|
|
35
|
+
entries: MemoryEntry[];
|
|
36
|
+
total: number;
|
|
37
|
+
query?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function generateId(entry: Omit<MemoryEntry, 'id'>): string;
|
|
40
|
+
export declare function matchesQuery(entry: MemoryEntry, query: string): boolean;
|
|
41
|
+
export declare function saveEntry(store: Map<string, MemoryEntry>, raw: Partial<MemoryEntry>, sessionId?: string): MemoryEntry;
|
|
42
|
+
export declare function listEntries(store: Map<string, MemoryEntry>, query?: string, type?: MemoryEntryType): MemoryEntry[];
|
|
43
|
+
export declare const contextMemorySkill: SkillModule;
|
|
44
|
+
//# sourceMappingURL=context-memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-memory.d.ts","sourceRoot":"","sources":["../../src/skills/context-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAA6B,MAAM,aAAa,CAAC;AAM1E,MAAM,MAAM,eAAe,GACvB,UAAU,GACV,WAAW,GACX,WAAW,GACX,YAAY,GACZ,SAAS,GACT,MAAM,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,MAAM,CAOjE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAQvE;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAC/B,GAAG,EAAE,OAAO,CAAC,WAAW,CAAC,EACzB,SAAS,CAAC,EAAE,MAAM,GACjB,WAAW,CAgBb;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAC/B,KAAK,CAAC,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,eAAe,GACrB,WAAW,EAAE,CAKf;AAsBD,eAAO,MAAM,kBAAkB,EAAE,WAiGhC,CAAC"}
|