@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.
Files changed (59) hide show
  1. package/.agents/skills/ask-matt/SKILL.md +2 -2
  2. package/.agents/skills/grill-me/SKILL.md +1 -1
  3. package/.agents/skills/grill-with-docs/SKILL.md +1 -1
  4. package/.agents/skills/kit-update/SKILL.md +12 -0
  5. package/.agents/skills/orchestrate-wave/SKILL.md +68 -21
  6. package/.agents/skills/orchestrate-wave/references/builder-contract.md +6 -4
  7. package/.agents/skills/retro/SKILL.md +24 -1
  8. package/.agents/skills/scale-check/SKILL.md +2 -2
  9. package/.agents/skills/setup-workflow/SKILL.md +44 -1
  10. package/.agents/skills/setup-workflow/workflow-overview.md +5 -5
  11. package/.agents/skills/tdd/SKILL.md +68 -14
  12. package/.agents/skills/to-issues/SKILL.md +2 -2
  13. package/.agents/skills/verify-spike/SKILL.md +1 -1
  14. package/.agents/skills/wrapup/SKILL.md +14 -2
  15. package/.claude/hooks/drift-guard.py +5 -2
  16. package/.claude/hooks/kit-origin-edit-hint.py +64 -0
  17. package/.claude/skills/ask-matt/SKILL.md +2 -2
  18. package/.claude/skills/grill-me/SKILL.md +1 -1
  19. package/.claude/skills/grill-with-docs/SKILL.md +1 -1
  20. package/.claude/skills/kit-update/SKILL.md +12 -0
  21. package/.claude/skills/orchestrate-wave/SKILL.md +68 -21
  22. package/.claude/skills/orchestrate-wave/references/builder-contract.md +6 -4
  23. package/.claude/skills/retro/SKILL.md +23 -0
  24. package/.claude/skills/scale-check/SKILL.md +2 -2
  25. package/.claude/skills/setup-workflow/SKILL.md +44 -1
  26. package/.claude/skills/setup-workflow/workflow-overview.md +5 -5
  27. package/.claude/skills/skill-manifest.json +1 -1
  28. package/.claude/skills/tdd/SKILL.md +68 -14
  29. package/.claude/skills/to-issues/SKILL.md +2 -2
  30. package/.claude/skills/verify-spike/SKILL.md +1 -1
  31. package/.claude/skills/wrapup/SKILL.md +14 -2
  32. package/README.md +51 -7
  33. package/agent-workflow-kit.package.json +50 -34
  34. package/docs/adr/0001-consumer-divergence-policy.md +49 -0
  35. package/docs/agents/wave-anchor-template.md +1 -1
  36. package/package.json +1 -1
  37. package/scripts/board-sync.py +184 -5
  38. package/scripts/pr-body-check.py +34 -6
  39. package/scripts/pr_body_e2e.py +83 -0
  40. package/scripts/release-parity.mjs +34 -0
  41. package/scripts/release-parity.test.mjs +37 -0
  42. package/scripts/test_board_sync.py +208 -0
  43. package/scripts/test_census_backstop.py +56 -3
  44. package/scripts/test_orchestrate_wave_contract.py +116 -0
  45. package/scripts/test_pr_body_check.py +245 -0
  46. package/scripts/test_retro_wrapup_contract.py +111 -0
  47. package/scripts/test_tdd_contract.py +78 -0
  48. package/src/cli.mjs +49 -10
  49. package/src/commands/diff.mjs +5 -2
  50. package/src/commands/init.mjs +12 -0
  51. package/src/commands/own.mjs +16 -0
  52. package/src/commands/uninstall.mjs +8 -1
  53. package/src/commands/update.mjs +39 -11
  54. package/src/lib/bundle.mjs +4 -0
  55. package/src/lib/consumerPath.mjs +30 -0
  56. package/src/lib/manifest.mjs +18 -0
  57. package/src/lib/ownedDiff.mjs +88 -0
  58. package/src/lib/updateCandidate.mjs +14 -0
  59. 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
- const current = (await exists(dest)) ? await sha256File(dest) : null;
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: 'kit', installRole: CONSUMER_INSTALL_ROLE,
136
+ installedSha256, origin, installRole: CONSUMER_INSTALL_ROLE,
98
137
  };
99
138
  }
100
139