@ikon85/agent-workflow-kit 0.36.2 → 0.36.4
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
CHANGED
|
@@ -445,6 +445,16 @@ the old way. Decision record:
|
|
|
445
445
|
|
|
446
446
|
## Release notes
|
|
447
447
|
|
|
448
|
+
### 0.36.4
|
|
449
|
+
|
|
450
|
+
- changed: `scripts/release-delta-guard.mjs`
|
|
451
|
+
- changed: `scripts/release-delta-guard.test.mjs`
|
|
452
|
+
- changed: `src/lib/updateReconcile.mjs`
|
|
453
|
+
|
|
454
|
+
### 0.36.3
|
|
455
|
+
|
|
456
|
+
- Metadata-only release.
|
|
457
|
+
|
|
448
458
|
### 0.36.2
|
|
449
459
|
|
|
450
460
|
- changed: `.claude/skills/to-issues/SKILL.md`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"kitVersion": "0.36.
|
|
2
|
+
"kitVersion": "0.36.4",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": ".agents/skills/ask-matt/SKILL.md",
|
|
@@ -2683,7 +2683,7 @@
|
|
|
2683
2683
|
"path": "scripts/release-delta-guard.mjs",
|
|
2684
2684
|
"kind": "script",
|
|
2685
2685
|
"installRole": "maintainer",
|
|
2686
|
-
"sha256": "
|
|
2686
|
+
"sha256": "566d7bae5526af2f8863d7a06dfa3016c9383b241b616cd43757eb7b46555895",
|
|
2687
2687
|
"mode": 420,
|
|
2688
2688
|
"origin": "kit"
|
|
2689
2689
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikon85/agent-workflow-kit",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.4",
|
|
4
4
|
"description": "Portable AI-agent workflow skills (plan → execute → land → learn) for Claude Code & Codex — grilling, TDD, diagnosis, two-axis code review, cross-model Codex review, design & domain-modeling, plus a skill router (ask-matt). npx init/update/diff/uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Block shipped changes whose version or checked manifest does not match a fresh build. */
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, mkdtemp, readFile, readdir, rm } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -24,6 +24,12 @@ const describe = (delta) => ['added', 'removed', 'changed']
|
|
|
24
24
|
.filter((kind) => delta[kind].length)
|
|
25
25
|
.map((kind) => `${kind}: ${delta[kind].join(', ')}`).join('; ');
|
|
26
26
|
|
|
27
|
+
function mergeDeltas(...deltas) {
|
|
28
|
+
return Object.fromEntries(['added', 'removed', 'changed'].map((kind) => [
|
|
29
|
+
kind, [...new Set(deltas.flatMap((delta) => delta[kind]))].sort(),
|
|
30
|
+
]));
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export function recommendBump(delta) {
|
|
28
34
|
if (delta.removed.length) return 'major';
|
|
29
35
|
if (delta.added.length) return 'minor';
|
|
@@ -40,7 +46,11 @@ function bumpKind(before, after) {
|
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export function assessRelease(input) {
|
|
43
|
-
const
|
|
49
|
+
const bundleDelta = manifestDelta(input.baseManifest, input.builtManifest);
|
|
50
|
+
const packageDelta = input.basePackagePayload && input.currentPackagePayload
|
|
51
|
+
? manifestDelta(input.basePackagePayload, input.currentPackagePayload)
|
|
52
|
+
: { added: [], removed: [], changed: [] };
|
|
53
|
+
const delta = mergeDeltas(bundleDelta, packageDelta);
|
|
44
54
|
const checkedDrift = manifestDelta(input.checkedManifest, input.builtManifest);
|
|
45
55
|
const payloadDrift = manifestDelta(input.builtManifest, input.payloadManifest);
|
|
46
56
|
const errors = [];
|
|
@@ -99,6 +109,18 @@ export function resolveBaseTag({ repoRoot, baseVersion, tagPrefix = 'v' }) {
|
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
export async function packedPayloadManifest({ repoRoot, manifest }) {
|
|
112
|
+
const payload = await packedPackagePayload({ repoRoot });
|
|
113
|
+
const indexed = byPath(payload);
|
|
114
|
+
return {
|
|
115
|
+
kitVersion: manifest.kitVersion,
|
|
116
|
+
files: manifest.files.map((entry) => ({
|
|
117
|
+
...entry,
|
|
118
|
+
sha256: indexed.get(entry.path),
|
|
119
|
+
})),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function packedPackagePayload({ repoRoot }) {
|
|
102
124
|
const tempRoot = await mkdtemp(join(tmpdir(), 'awkit-package-payload-'));
|
|
103
125
|
try {
|
|
104
126
|
const packOutput = execFileSync(
|
|
@@ -110,17 +132,38 @@ export async function packedPayloadManifest({ repoRoot, manifest }) {
|
|
|
110
132
|
await mkdir(unpackDir);
|
|
111
133
|
execFileSync('tar', ['-xzf', join(tempRoot, filename), '-C', unpackDir]);
|
|
112
134
|
return {
|
|
113
|
-
|
|
114
|
-
files: await Promise.all(manifest.files.map(async (entry) => ({
|
|
115
|
-
...entry,
|
|
116
|
-
sha256: sha256(await readFile(join(unpackDir, 'package', entry.path))),
|
|
117
|
-
}))),
|
|
135
|
+
files: await packageFiles(join(unpackDir, 'package')),
|
|
118
136
|
};
|
|
119
137
|
} finally {
|
|
120
138
|
await rm(tempRoot, { recursive: true, force: true });
|
|
121
139
|
}
|
|
122
140
|
}
|
|
123
141
|
|
|
142
|
+
async function packageFiles(root, relative = '') {
|
|
143
|
+
const files = [];
|
|
144
|
+
for (const entry of await readdir(join(root, relative), { withFileTypes: true })) {
|
|
145
|
+
const path = relative ? `${relative}/${entry.name}` : entry.name;
|
|
146
|
+
if (entry.isDirectory()) files.push(...await packageFiles(root, path));
|
|
147
|
+
else if (entry.isFile()) {
|
|
148
|
+
files.push({ path, sha256: sha256(await readFile(join(root, path))) });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function packagePayloadAtRef(repoRoot, ref) {
|
|
155
|
+
const archiveRoot = await mkdtemp(join(tmpdir(), 'awkit-package-base-'));
|
|
156
|
+
try {
|
|
157
|
+
const archive = execFileSync('git', ['archive', '--format=tar', ref], {
|
|
158
|
+
cwd: repoRoot, maxBuffer: 50 * 1024 * 1024,
|
|
159
|
+
});
|
|
160
|
+
execFileSync('tar', ['-xf', '-', '-C', archiveRoot], { input: archive });
|
|
161
|
+
return await packedPackagePayload({ repoRoot: archiveRoot });
|
|
162
|
+
} finally {
|
|
163
|
+
await rm(archiveRoot, { recursive: true, force: true });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
124
167
|
export async function checkReleaseDelta({ repoRoot, baseRef = 'origin/main' } = {}) {
|
|
125
168
|
repoRoot ??= join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
126
169
|
const distDir = await mkdtemp(join(tmpdir(), 'awkit-release-guard-'));
|
|
@@ -129,6 +172,7 @@ export async function checkReleaseDelta({ repoRoot, baseRef = 'origin/main' } =
|
|
|
129
172
|
const currentPackage = JSON.parse(await readFile(join(repoRoot, 'package.json'), 'utf8'));
|
|
130
173
|
const builtManifest = JSON.parse(await readFile(join(distDir, 'agent-workflow-kit.package.json'), 'utf8'));
|
|
131
174
|
const baseVersion = gitShowJson(repoRoot, baseRef, 'package.json').version;
|
|
175
|
+
const currentPackagePayload = await packedPackagePayload({ repoRoot });
|
|
132
176
|
return assessRelease({
|
|
133
177
|
baseVersion,
|
|
134
178
|
baseTag: resolveBaseTag({ repoRoot, baseVersion }),
|
|
@@ -136,7 +180,15 @@ export async function checkReleaseDelta({ repoRoot, baseRef = 'origin/main' } =
|
|
|
136
180
|
baseManifest: gitShowJson(repoRoot, baseRef, 'agent-workflow-kit.package.json'),
|
|
137
181
|
checkedManifest: JSON.parse(await readFile(join(repoRoot, 'agent-workflow-kit.package.json'), 'utf8')),
|
|
138
182
|
builtManifest,
|
|
139
|
-
payloadManifest:
|
|
183
|
+
payloadManifest: {
|
|
184
|
+
kitVersion: builtManifest.kitVersion,
|
|
185
|
+
files: builtManifest.files.map((entry) => ({
|
|
186
|
+
...entry,
|
|
187
|
+
sha256: byPath(currentPackagePayload).get(entry.path),
|
|
188
|
+
})),
|
|
189
|
+
},
|
|
190
|
+
basePackagePayload: await packagePayloadAtRef(repoRoot, baseRef),
|
|
191
|
+
currentPackagePayload,
|
|
140
192
|
});
|
|
141
193
|
} finally { await rm(distDir, { recursive: true, force: true }); }
|
|
142
194
|
}
|
|
@@ -39,6 +39,25 @@ test('an unbumped shipped change is blocked with its concrete delta', () => {
|
|
|
39
39
|
assert.match(result.errors.join('\n'), /version remains 1\.2\.3/);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
test('an npm runtime change outside the Consumer manifest still requires a release', () => {
|
|
43
|
+
const consumer = { kitVersion: '1.2.3', files: [file('skill.md', 'same')] };
|
|
44
|
+
const result = assessRelease({
|
|
45
|
+
baseVersion: '1.2.3',
|
|
46
|
+
currentVersion: '1.2.3',
|
|
47
|
+
baseManifest: consumer,
|
|
48
|
+
builtManifest: consumer,
|
|
49
|
+
checkedManifest: consumer,
|
|
50
|
+
payloadManifest: consumer,
|
|
51
|
+
basePackagePayload: { files: [file('src/cli.mjs', 'old')] },
|
|
52
|
+
currentPackagePayload: { files: [file('src/cli.mjs', 'new')] },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.equal(result.ok, false);
|
|
56
|
+
assert.deepEqual(result.delta.changed, ['src/cli.mjs']);
|
|
57
|
+
assert.equal(result.recommendedBump, 'patch');
|
|
58
|
+
assert.match(result.errors.join('\n'), /version remains 1\.2\.3/);
|
|
59
|
+
});
|
|
60
|
+
|
|
42
61
|
test('dead checked-manifest entries are rejected', () => {
|
|
43
62
|
const result = assessRelease({
|
|
44
63
|
baseVersion: '1.2.3', currentVersion: '1.3.0',
|
package/src/commands/update.mjs
CHANGED
|
@@ -135,7 +135,7 @@ async function previewReadinessAdoption(context) {
|
|
|
135
135
|
candidatePreview.generated = readiness.generated;
|
|
136
136
|
candidatePreview.migrations = readiness.migrations;
|
|
137
137
|
candidatePreview.migrated = readiness.migrated;
|
|
138
|
-
if (!readiness.migrationConflicts.length) {
|
|
138
|
+
if (!readiness.migrationConflicts.length && !candidatePreview.conflicts.length) {
|
|
139
139
|
await verifyCandidateSchema(candidateRoot, {
|
|
140
140
|
pkg, preview: candidatePreview, priorReadinessManifest, nextReadinessManifest,
|
|
141
141
|
});
|
|
@@ -143,7 +143,8 @@ export async function reconcile({ kitRoot, consumerRoot, decide = () => false, d
|
|
|
143
143
|
const userEdited = current !== prior.installedSha256;
|
|
144
144
|
const currentMode = (await lstat(dest)).mode & 0o777;
|
|
145
145
|
const upstreamChanged = file.sha256 !== prior.installedSha256
|
|
146
|
-
|| currentMode !== file.mode
|
|
146
|
+
|| currentMode !== file.mode
|
|
147
|
+
|| packageMetadataChanged(file, prior);
|
|
147
148
|
if (!userEdited && upstreamChanged) {
|
|
148
149
|
if (!dryRun) await writeAtomic(dest, await readFile(join(kitRoot, file.path)), file.mode);
|
|
149
150
|
nextInstalled.push(entry(file, file.sha256));
|
|
@@ -185,7 +186,8 @@ export async function reconcile({ kitRoot, consumerRoot, decide = () => false, d
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
result.manifestChanged = consumer.
|
|
189
|
+
result.manifestChanged = consumer.kitVersion !== pkg.kitVersion ||
|
|
190
|
+
consumer.installRole !== CONSUMER_INSTALL_ROLE ||
|
|
189
191
|
result.bridgeRetired.length > 0 ||
|
|
190
192
|
nextInstalled.some((next) => installedIdx.get(next.path)?.installRole !== next.installRole);
|
|
191
193
|
|
|
@@ -227,6 +229,12 @@ function withInstallRole(installed, installRole = CONSUMER_INSTALL_ROLE) {
|
|
|
227
229
|
return { ...installed, installRole };
|
|
228
230
|
}
|
|
229
231
|
|
|
232
|
+
function packageMetadataChanged(file, installed) {
|
|
233
|
+
return ['kind', 'ownerSkill', 'surface'].some(
|
|
234
|
+
(key) => (file[key] ?? null) !== (installed[key] ?? null),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
230
238
|
async function projectExtensionEvidence(consumerRoot, path) {
|
|
231
239
|
const match = /^docs\/agents\/skills\/([a-z0-9-]+)\.md$/.exec(path);
|
|
232
240
|
if (!match) return null;
|