@ikon85/agent-workflow-kit 0.27.0 → 0.28.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/.agents/skills/ask-matt/SKILL.md +2 -2
- package/.agents/skills/grill-me/SKILL.md +1 -1
- package/.agents/skills/grill-with-docs/SKILL.md +1 -1
- package/.agents/skills/kit-update/SKILL.md +12 -0
- package/.agents/skills/orchestrate-wave/SKILL.md +68 -21
- package/.agents/skills/orchestrate-wave/references/builder-contract.md +6 -4
- package/.agents/skills/retro/SKILL.md +24 -1
- package/.agents/skills/scale-check/SKILL.md +2 -2
- package/.agents/skills/setup-workflow/SKILL.md +44 -1
- package/.agents/skills/setup-workflow/workflow-overview.md +5 -5
- package/.agents/skills/tdd/SKILL.md +68 -14
- package/.agents/skills/to-issues/SKILL.md +2 -2
- package/.agents/skills/verify-spike/SKILL.md +1 -1
- package/.agents/skills/wrapup/SKILL.md +14 -2
- package/.claude/hooks/drift-guard.py +5 -2
- package/.claude/hooks/kit-origin-edit-hint.py +64 -0
- package/.claude/skills/ask-matt/SKILL.md +2 -2
- package/.claude/skills/grill-me/SKILL.md +1 -1
- package/.claude/skills/grill-with-docs/SKILL.md +1 -1
- package/.claude/skills/kit-update/SKILL.md +12 -0
- package/.claude/skills/orchestrate-wave/SKILL.md +68 -21
- package/.claude/skills/orchestrate-wave/references/builder-contract.md +6 -4
- package/.claude/skills/retro/SKILL.md +23 -0
- package/.claude/skills/scale-check/SKILL.md +2 -2
- package/.claude/skills/setup-workflow/SKILL.md +44 -1
- package/.claude/skills/setup-workflow/workflow-overview.md +5 -5
- package/.claude/skills/skill-manifest.json +1 -1
- package/.claude/skills/tdd/SKILL.md +68 -14
- package/.claude/skills/to-issues/SKILL.md +2 -2
- package/.claude/skills/verify-spike/SKILL.md +1 -1
- package/.claude/skills/wrapup/SKILL.md +14 -2
- package/README.md +51 -7
- package/agent-workflow-kit.package.json +50 -34
- package/docs/adr/0001-consumer-divergence-policy.md +49 -0
- package/docs/agents/wave-anchor-template.md +1 -1
- package/package.json +1 -1
- package/scripts/board-sync.py +184 -5
- package/scripts/pr-body-check.py +34 -6
- package/scripts/pr_body_e2e.py +83 -0
- package/scripts/release-parity.mjs +34 -0
- package/scripts/release-parity.test.mjs +37 -0
- package/scripts/test_board_sync.py +208 -0
- package/scripts/test_census_backstop.py +56 -3
- package/scripts/test_orchestrate_wave_contract.py +116 -0
- package/scripts/test_pr_body_check.py +245 -0
- package/scripts/test_retro_wrapup_contract.py +111 -0
- package/scripts/test_tdd_contract.py +78 -0
- package/src/cli.mjs +49 -10
- package/src/commands/diff.mjs +5 -2
- package/src/commands/init.mjs +12 -0
- package/src/commands/own.mjs +16 -0
- package/src/commands/uninstall.mjs +8 -1
- package/src/commands/update.mjs +39 -11
- package/src/lib/bundle.mjs +4 -0
- package/src/lib/consumerPath.mjs +30 -0
- package/src/lib/manifest.mjs +18 -0
- package/src/lib/ownedDiff.mjs +88 -0
- package/src/lib/updateCandidate.mjs +14 -0
- package/src/lib/updateReconcile.mjs +45 -6
|
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { writeAtomic } from './atomicWrite.mjs';
|
|
7
|
+
import { validateConsumerFile } from './consumerPath.mjs';
|
|
7
8
|
import { sha256File } from './hash.mjs';
|
|
8
9
|
import { CONSUMER_MANIFEST_NAME, indexByPath, readManifest } from './manifest.mjs';
|
|
9
10
|
|
|
@@ -63,7 +64,20 @@ export async function activateCandidate({
|
|
|
63
64
|
async function assertConsumerStillMatchesPreview(consumerRoot, preview) {
|
|
64
65
|
const manifest = await readManifest(join(consumerRoot, CONSUMER_MANIFEST_NAME));
|
|
65
66
|
const installed = indexByPath(manifest, 'installed');
|
|
67
|
+
const replacements = new Set(
|
|
68
|
+
preview.collisionResolutions
|
|
69
|
+
.filter(({ outcome }) => outcome === 'replace')
|
|
70
|
+
.map(({ path }) => path),
|
|
71
|
+
);
|
|
72
|
+
for (const collision of preview.collisionResolutions) {
|
|
73
|
+
await validateConsumerFile(consumerRoot, collision.path);
|
|
74
|
+
const current = await sha256File(join(consumerRoot, collision.path));
|
|
75
|
+
if (current !== collision.destinationSha256) {
|
|
76
|
+
throw new Error(`consumer changed during verification: ${collision.path}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
66
79
|
for (const path of preview.added) {
|
|
80
|
+
if (replacements.has(path)) continue;
|
|
67
81
|
if (await exists(join(consumerRoot, path))) throw new Error(`consumer changed during verification: ${path}`);
|
|
68
82
|
}
|
|
69
83
|
for (const path of [...preview.updated, ...preview.deleted]) {
|
|
@@ -3,9 +3,10 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { sha256File } from './hash.mjs';
|
|
4
4
|
import { lineDiff, writeAtomic } from './atomicWrite.mjs';
|
|
5
5
|
import { hookReferenced } from './settings.mjs';
|
|
6
|
+
import { validateConsumerFile } from './consumerPath.mjs';
|
|
6
7
|
import {
|
|
7
8
|
CONSUMER_MANIFEST_NAME, PACKAGE_MANIFEST_NAME, emptyConsumerManifest,
|
|
8
|
-
CONSUMER_INSTALL_ROLE, filesForInstallRole,
|
|
9
|
+
CONSUMER_INSTALL_ROLE, CONSUMER_ORIGIN, KIT_ORIGIN, filesForInstallRole,
|
|
9
10
|
indexByPath, readManifest, writeManifest,
|
|
10
11
|
} from './manifest.mjs';
|
|
11
12
|
|
|
@@ -28,7 +29,40 @@ export async function reconcile({ kitRoot, consumerRoot, decide = () => false, d
|
|
|
28
29
|
for (const file of installable) {
|
|
29
30
|
const dest = join(consumerRoot, file.path);
|
|
30
31
|
const prior = installedIdx.get(file.path);
|
|
31
|
-
|
|
32
|
+
if (prior?.origin === CONSUMER_ORIGIN) {
|
|
33
|
+
nextInstalled.push(withInstallRole(prior));
|
|
34
|
+
result.consumerOwned.push(file.path);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const present = await exists(dest);
|
|
38
|
+
if (!prior && present) {
|
|
39
|
+
await validateConsumerFile(consumerRoot, file.path);
|
|
40
|
+
const decision = await decide('collision', file.path);
|
|
41
|
+
if (decision === false || decision === null || decision === undefined) {
|
|
42
|
+
if (dryRun) {
|
|
43
|
+
result.collisions.push(file.path);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`collision decision required for ${file.path}`);
|
|
47
|
+
}
|
|
48
|
+
if (decision !== 'keep-as-owned' && decision !== 'replace') {
|
|
49
|
+
throw new Error(`collision decision for ${file.path} must be keep-as-owned or replace`);
|
|
50
|
+
}
|
|
51
|
+
const destinationSha256 = await sha256File(dest);
|
|
52
|
+
result.collisionResolutions.push({
|
|
53
|
+
path: file.path, outcome: decision, destinationSha256,
|
|
54
|
+
});
|
|
55
|
+
if (decision === 'keep-as-owned') {
|
|
56
|
+
nextInstalled.push(entry(file, destinationSha256, CONSUMER_ORIGIN));
|
|
57
|
+
result.consumerOwned.push(file.path);
|
|
58
|
+
} else {
|
|
59
|
+
if (!dryRun) await writeAtomic(dest, await readFile(join(kitRoot, file.path)), file.mode);
|
|
60
|
+
nextInstalled.push(entry(file, file.sha256));
|
|
61
|
+
result.added.push(file.path);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const current = present ? await sha256File(dest) : null;
|
|
32
66
|
if (!prior || current === null) {
|
|
33
67
|
if (!dryRun) await writeAtomic(dest, await readFile(join(kitRoot, file.path)), file.mode);
|
|
34
68
|
nextInstalled.push(entry(file, file.sha256));
|
|
@@ -56,6 +90,11 @@ export async function reconcile({ kitRoot, consumerRoot, decide = () => false, d
|
|
|
56
90
|
|
|
57
91
|
for (const prior of consumer.installed) {
|
|
58
92
|
if (pkgIdx.has(prior.path)) continue;
|
|
93
|
+
if (prior.origin === CONSUMER_ORIGIN) {
|
|
94
|
+
nextInstalled.push(withInstallRole(prior));
|
|
95
|
+
result.consumerOwned.push(prior.path);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
59
98
|
const dest = join(consumerRoot, prior.path);
|
|
60
99
|
const current = (await exists(dest)) ? await sha256File(dest) : null;
|
|
61
100
|
const userEdited = current !== null && current !== prior.installedSha256;
|
|
@@ -86,15 +125,15 @@ export async function reconcile({ kitRoot, consumerRoot, decide = () => false, d
|
|
|
86
125
|
|
|
87
126
|
function emptyResult() {
|
|
88
127
|
return {
|
|
89
|
-
unchanged: [], updated: [], conflicts: [], userModified: [],
|
|
90
|
-
added: [], deleted: [], keptDeleted: [], manifestChanged: false,
|
|
128
|
+
unchanged: [], updated: [], conflicts: [], collisions: [], collisionResolutions: [], userModified: [],
|
|
129
|
+
added: [], deleted: [], keptDeleted: [], consumerOwned: [], manifestChanged: false,
|
|
91
130
|
};
|
|
92
131
|
}
|
|
93
132
|
|
|
94
|
-
function entry(file, installedSha256) {
|
|
133
|
+
function entry(file, installedSha256, origin = KIT_ORIGIN) {
|
|
95
134
|
return {
|
|
96
135
|
path: file.path, kind: file.kind, ownerSkill: file.ownerSkill, surface: file.surface,
|
|
97
|
-
installedSha256, origin
|
|
136
|
+
installedSha256, origin, installRole: CONSUMER_INSTALL_ROLE,
|
|
98
137
|
};
|
|
99
138
|
}
|
|
100
139
|
|