@ikon85/agent-workflow-kit 0.36.4 → 0.36.5

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,13 @@ the old way. Decision record:
445
445
 
446
446
  ## Release notes
447
447
 
448
+ ### 0.36.5
449
+
450
+ - changed: `src/lib/updateCandidate.mjs`
451
+ - changed: `src/lib/updateReconcile.mjs`
452
+ - changed: `src/lib/verifyUpdateCandidate.mjs`
453
+ - changed: `src/lib/verifyUpdateCandidateTransaction.mjs`
454
+
448
455
  ### 0.36.4
449
456
 
450
457
  - changed: `scripts/release-delta-guard.mjs`
@@ -1,5 +1,5 @@
1
1
  {
2
- "kitVersion": "0.36.4",
2
+ "kitVersion": "0.36.5",
3
3
  "files": [
4
4
  {
5
5
  "path": ".agents/skills/ask-matt/SKILL.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikon85/agent-workflow-kit",
3
- "version": "0.36.4",
3
+ "version": "0.36.5",
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": {
@@ -351,6 +351,14 @@ async function assertConsumerStillMatchesPreview(consumerRoot, preview) {
351
351
  throw new Error(`consumer changed during verification: ${path}`);
352
352
  }
353
353
  }
354
+ for (const snapshot of preview.userModifiedSnapshots ?? []) {
355
+ const target = join(consumerRoot, snapshot.path);
356
+ const current = await exists(target) ? await sha256File(target) : null;
357
+ const mode = current === null ? null : (await lstat(target)).mode & 0o777;
358
+ if (current !== snapshot.sha256 || mode !== snapshot.mode) {
359
+ throw new Error(`consumer changed during verification: ${snapshot.path}`);
360
+ }
361
+ }
354
362
  }
355
363
 
356
364
  /** Seed only newly declared, decision-free project-layer stubs in a staged candidate. */
@@ -156,6 +156,11 @@ export async function reconcile({ kitRoot, consumerRoot, decide = () => false, d
156
156
  } else if (userEdited) {
157
157
  nextInstalled.push(withInstallRole(prior));
158
158
  result.userModified.push(file.path);
159
+ result.userModifiedSnapshots.push({
160
+ path: file.path,
161
+ sha256: current,
162
+ mode: currentMode,
163
+ });
159
164
  } else {
160
165
  nextInstalled.push(withInstallRole(prior));
161
166
  result.unchanged.push(file.path);
@@ -202,6 +207,7 @@ export async function reconcile({ kitRoot, consumerRoot, decide = () => false, d
202
207
  function emptyResult() {
203
208
  return {
204
209
  unchanged: [], updated: [], conflicts: [], collisions: [], collisionResolutions: [], userModified: [],
210
+ userModifiedSnapshots: [],
205
211
  ownershipStates: [],
206
212
  bridgeRetired: [],
207
213
  added: [], deleted: [], keptDeleted: [], consumerOwned: [], manifestChanged: false,
@@ -61,11 +61,14 @@ export async function verifyCandidateMetadata(candidateRoot, context) {
61
61
  if (!state.isFile()) {
62
62
  throw new Error(`candidate invariant artifact: not a regular file ${entry.path}`);
63
63
  }
64
- if (tracked.origin === KIT_ORIGIN && (state.mode & 0o777) !== entry.mode) {
64
+ const localSnapshot = (context.preview.userModifiedSnapshots ?? [])
65
+ .find(({ path }) => path === entry.path);
66
+ const expectedMode = localSnapshot?.mode ?? entry.mode;
67
+ if (tracked.origin === KIT_ORIGIN && (state.mode & 0o777) !== expectedMode) {
65
68
  throw new Error(`candidate invariant artifact: mode mismatch ${entry.path}`);
66
69
  }
67
- const expectedHash = tracked.origin === CONSUMER_ORIGIN
68
- ? tracked.installedSha256 : entry.sha256;
70
+ const expectedHash = localSnapshot?.sha256 ?? (tracked.origin === CONSUMER_ORIGIN
71
+ ? tracked.installedSha256 : entry.sha256);
69
72
  if (await sha256File(join(candidateRoot, entry.path)) !== expectedHash) {
70
73
  throw new Error(`candidate invariant artifact: hash mismatch ${entry.path}`);
71
74
  }
@@ -44,7 +44,7 @@ export function verifyTransactionPreview(preview, installable, installed) {
44
44
  const installablePaths = new Set(installable.map(({ path }) => path));
45
45
  const owners = new Map();
46
46
  const actionSets = new Map();
47
- for (const key of ['added', 'updated', 'deleted', 'generated', 'keptDeleted']) {
47
+ for (const key of ['added', 'updated', 'deleted', 'generated', 'keptDeleted', 'userModified']) {
48
48
  if (!Array.isArray(preview[key] ?? [])) {
49
49
  throw new Error(`candidate invariant transaction: ${key} must be an array`);
50
50
  }
@@ -54,12 +54,34 @@ export function verifyTransactionPreview(preview, installable, installed) {
54
54
  if (['added', 'updated'].includes(key) && !installablePaths.has(path)) {
55
55
  throw new Error(`candidate invariant transaction: unmanaged ${key} path ${path}`);
56
56
  }
57
+ if (key === 'userModified' && !installablePaths.has(path)) {
58
+ throw new Error(`candidate invariant transaction: unmanaged userModified path ${path}`);
59
+ }
57
60
  if (key === 'deleted' && installablePaths.has(path)) {
58
61
  throw new Error(`candidate invariant transaction: deletes current package path ${path}`);
59
62
  }
60
63
  }
61
64
  actionSets.set(key, local);
62
65
  }
66
+ if (!Array.isArray(preview.userModifiedSnapshots ?? [])) {
67
+ throw new Error('candidate invariant transaction: userModifiedSnapshots must be an array');
68
+ }
69
+ const modified = actionSets.get('userModified');
70
+ const snapshots = new Set();
71
+ for (const snapshot of preview.userModifiedSnapshots ?? []) {
72
+ const path = snapshot?.path;
73
+ claimTransactionPath(path, 'userModifiedSnapshots', snapshots);
74
+ if (!modified.has(path)
75
+ || !HASH.test(snapshot.sha256 ?? '')
76
+ || !Number.isInteger(snapshot.mode)
77
+ || snapshot.mode < 0
78
+ || snapshot.mode > 0o777) {
79
+ throw new Error(`candidate invariant transaction: invalid local snapshot ${path}`);
80
+ }
81
+ }
82
+ if (snapshots.size !== modified.size) {
83
+ throw new Error('candidate invariant transaction: missing local snapshot');
84
+ }
63
85
  if (!Array.isArray(preview.bridgeRetired ?? [])) {
64
86
  throw new Error('candidate invariant transaction: bridgeRetired must be an array');
65
87
  }