yam-flow 2.3.0 → 2.3.1

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.md CHANGED
@@ -21,3 +21,21 @@
21
21
  19. Treat security as the first project lens: identify sensitive surfaces early and guide safer defaults before convenience.
22
22
  20. Keep final reports short and concrete, but always include remaining tasks or state that none are meaningful.
23
23
  21. When relevant, include architecture hygiene in the Study Note: avoid dumping unrelated logic into `page.tsx`, one-off component CSS into `global.css`, or structured product data into broad DB `jsonb` blobs when existing project boundaries, scoped styles, typed tables, constraints, or indexes fit better.
24
+
25
+ ## Latest Update Trigger
26
+
27
+ When the user says exactly `yam 최신 업데이트 사항 반영해줘` in a chat for this project, treat that phrase as explicit authorization to update the globally installed package and refresh the installed yam skills.
28
+
29
+ Run these commands sequentially from the yam project directory:
30
+
31
+ ```bash
32
+ npm install -g yam-flow
33
+ yam install
34
+ yam version
35
+ yam status
36
+ ```
37
+
38
+ - Request any environment approval required for network access or writes outside the workspace.
39
+ - Stop at the first failed command, report the blocker, and do not claim that later steps ran.
40
+ - After all four commands pass, report the installed version and skill status, then tell the user that Codex can be restarted to reload the updated skills.
41
+ - Do not restart Codex automatically.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to yam-flow are documented here.
4
4
 
5
+ ## v2.3.1 - 2026-07-21
6
+
7
+ ### Added
8
+
9
+ - Added a transactional skill installer that stages and SHA-256 verifies the complete managed skill set before replacing the active installation.
10
+ - Added `yam.install-receipt.v1` with the installed package version, source identity, timestamp, destination, skill inventory, per-file hashes, and aggregate source digest.
11
+ - Added deterministic install transaction smoke coverage for successful commit, forced mid-commit rollback, drift detection and repair, mirror cleanup, and concurrent-install locking.
12
+
13
+ ### Improved
14
+
15
+ - `yam status` now detects missing files, unexpected files, hash drift, receipt drift, and package-version drift instead of checking only for `SKILL.md` and `references/` existence.
16
+ - Failed commits restore the previous managed skill set and receipt; legacy and retired skill cleanup now participates in the same rollback boundary.
17
+ - Unfinished transaction artifacts are surfaced by `yam status` and block a new install so recovery backups are not silently overwritten.
18
+ - `yam uninstall` removes the install receipt and refuses to run while an install lock is present.
19
+
20
+ ### Verification
21
+
22
+ - `npm run verify:self`
23
+ - `npm run dist:freshness`
24
+ - `npm run package-boundary:check`
25
+ - `npm run cli-smoke`
26
+
5
27
  ## v2.3.0 - 2026-07-13
6
28
 
7
29
  ### Added
package/README.md CHANGED
@@ -96,21 +96,13 @@ cd ~/Documents/Codex/tools/yam
96
96
  yam install
97
97
  ```
98
98
 
99
- This copies each skill plus the shared `references/` directory into `~/.agents/skills/`, which is the user skill root used by this Codex desktop setup.
99
+ `yam install` stages every managed skill plus the shared `references/` directory, verifies the complete staged file manifest with SHA-256, and only then replaces the active set in `~/.agents/skills/`. If any commit or post-install verification step fails, yam restores the previous managed skill set and receipt.
100
100
 
101
- Manual install is also possible by copying selected skill folders into your active Codex user skill root, but make sure each installed skill also receives a `references/` folder.
101
+ A successful install writes `~/.agents/skills/.yam-flow-install-receipt.json`. The receipt records the package version, source identity, install timestamp, destination, managed skill inventory, and per-file hashes. `yam status` independently recomputes source and installed hashes, so a stale package install, local file change, unexpected file, or modified receipt is reported as drift.
102
102
 
103
- Recommended v0:
103
+ An ordinary install error rolls back automatically. An abrupt process or machine termination can leave `.yam-flow-install.lock` and a hidden transaction directory; `yam status` surfaces the unfinished transaction and yam refuses another mutation while recovery state remains. Remove the lock only after confirming no install is running, and preserve any transaction directory until the previous installation state has been inspected.
104
104
 
105
- ```bash
106
- mkdir -p ~/.agents/skills
107
- for skill in quick ueye question scout deep mission; do
108
- rm -rf "$HOME/.agents/skills/$skill"
109
- mkdir -p "$HOME/.agents/skills/$skill"
110
- cp "skills/$skill/SKILL.md" "$HOME/.agents/skills/$skill/SKILL.md"
111
- cp -R references "$HOME/.agents/skills/$skill/references"
112
- done
113
- ```
105
+ Manual copying can still make a skill loadable, but it intentionally remains unverified: without a matching receipt, `yam status` exits non-zero and recommends `yam install`.
114
106
 
115
107
  Restart Codex after installing so skills reload.
116
108
 
@@ -187,6 +179,8 @@ npm install -g yam-flow
187
179
  yam status
188
180
  ```
189
181
 
182
+ After `yam status` reports all skills and the install receipt as `ok`, restart Codex so the refreshed skills reload.
183
+
190
184
  Publishing still requires confirming the final npm package name and account access.
191
185
 
192
186
  ## Trust Kernel
package/ROADMAP.md CHANGED
@@ -8,6 +8,7 @@ Implemented:
8
8
  - Six routes: quick, ueye, question, scout, deep, mission.
9
9
  - Shared references.
10
10
  - Local CLI: list, status, verify, doctor, examples, path, install, uninstall.
11
+ - Transactional skill install with rollback, SHA-256 receipt, install lock, and status drift detection. Done.
11
12
  - CLI helpers: detect, budget, template, tune-log, version.
12
13
  - Optional advisory `yam-lite` hook with status/enable/disable. Done.
13
14
  - Opt-in project memory: init, add, list, summary, resolve.
@@ -94,6 +95,7 @@ Tasks:
94
95
 
95
96
  - Add symlink-friendly local install.
96
97
  - Add version command. Done.
98
+ - Add staged transactional install, rollback, receipt, and drift-aware status. Done.
97
99
  - Consider npm packaging only after route behavior stabilizes.
98
100
 
99
101
  ### 8. Scout / Research Workflow
package/dist/bin/yam.js CHANGED
@@ -9,6 +9,7 @@ import process from 'node:process';
9
9
  import { fileURLToPath } from 'node:url';
10
10
  import { TRUTH_STATUSES, buildLoopReport, buildMediaGenerationProof, buildMissionCompletionGate, buildMissionPatchEnvelope, buildMissionSubagentReceipt, buildRollbackHint, buildRuntimeBackendEvidence, buildStudyNote, buildUeyeDesignCompletionGate, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
11
11
  import { archiveUeyeRevision, inspectImageFile, upsertUeyeAsset, verifyUeyeAssetManifest, verifyUeyeRevisionHistory } from '../lib/ueye-artifacts.js';
12
+ import { INSTALL_LOCK_NAME, INSTALL_RECEIPT_NAME, inspectSkillInstallation, installSkillSetTransactional } from '../lib/skill-installation.js';
12
13
  function errorMessage(error) {
13
14
  return error instanceof Error ? error.message : String(error);
14
15
  }
@@ -167,73 +168,77 @@ async function exists(file) {
167
168
  async function rmrf(target) {
168
169
  await fsp.rm(target, { recursive: true, force: true });
169
170
  }
170
- async function copyDir(source, target) {
171
- await fsp.mkdir(target, { recursive: true });
172
- const entries = await fsp.readdir(source, { withFileTypes: true });
173
- for (const entry of entries) {
174
- const from = path.join(source, entry.name);
175
- const to = path.join(target, entry.name);
176
- if (entry.isDirectory()) {
177
- await copyDir(from, to);
178
- }
179
- else if (entry.isFile()) {
180
- await fsp.copyFile(from, to);
181
- }
182
- }
183
- }
184
- async function installSkill(skill) {
185
- const source = path.join(ROOT, 'skills', skill);
186
- const target = path.join(DEST, skill);
187
- const references = path.join(ROOT, 'references');
188
- if (!await exists(path.join(source, 'SKILL.md'))) {
189
- throw new Error(`missing skill source: ${source}`);
190
- }
191
- await rmrf(target);
192
- await fsp.mkdir(target, { recursive: true });
193
- await fsp.copyFile(path.join(source, 'SKILL.md'), path.join(target, 'SKILL.md'));
194
- await copyDir(references, path.join(target, 'references'));
195
- }
196
171
  async function install() {
197
- await fsp.mkdir(DEST, { recursive: true });
198
- for (const skill of SKILLS) {
199
- await installSkill(skill);
200
- }
201
- for (const legacySkill of LEGACY_SKILLS) {
202
- await rmrf(path.join(DEST, legacySkill));
203
- }
204
- for (const retiredSkill of RETIRED_SKILLS) {
205
- await rmrf(path.join(DEST, retiredSkill));
206
- }
207
- if (CODEX_MIRROR !== DEST && fs.existsSync(CODEX_MIRROR)) {
208
- for (const skill of [...SKILLS, ...LEGACY_SKILLS, ...RETIRED_SKILLS]) {
209
- await rmrf(path.join(CODEX_MIRROR, skill));
210
- }
211
- }
172
+ const result = await installSkillSetTransactional({
173
+ sourceRoot: ROOT,
174
+ destination: DEST,
175
+ codexMirror: CODEX_MIRROR,
176
+ packageName: String(PACKAGE_JSON.name || 'yam-flow'),
177
+ version: VERSION,
178
+ skills: SKILLS,
179
+ legacySkills: LEGACY_SKILLS,
180
+ retiredSkills: RETIRED_SKILLS
181
+ });
212
182
  console.log(`yam installed to ${DEST}`);
183
+ console.log(`install receipt: ${result.receiptPath}`);
184
+ console.log(`integrity: ${result.installedFiles} files, sha256:${result.sourceDigest}`);
185
+ for (const warning of result.cleanupWarnings)
186
+ console.warn(`warning: ${warning}`);
213
187
  console.log('Restart Codex to reload skills.');
214
188
  }
215
189
  async function uninstall() {
190
+ const lockPath = path.join(DEST, INSTALL_LOCK_NAME);
191
+ if (await exists(lockPath)) {
192
+ throw new Error(`cannot uninstall while an install lock exists: ${lockPath}`);
193
+ }
216
194
  for (const skill of [...SKILLS, ...LEGACY_SKILLS, ...RETIRED_SKILLS]) {
217
195
  await rmrf(path.join(DEST, skill));
218
196
  if (CODEX_MIRROR !== DEST) {
219
197
  await rmrf(path.join(CODEX_MIRROR, skill));
220
198
  }
221
199
  }
200
+ await rmrf(path.join(DEST, INSTALL_RECEIPT_NAME));
222
201
  console.log(`yam removed from ${DEST}`);
223
202
  if (CODEX_MIRROR !== DEST)
224
203
  console.log(`yam mirror entries removed from ${CODEX_MIRROR}`);
225
204
  console.log('Restart Codex to unload skills.');
226
205
  }
227
206
  async function status({ quiet = false } = {}) {
228
- let missing = 0;
229
- for (const skill of SKILLS) {
230
- const ok = await exists(path.join(DEST, skill, 'SKILL.md')) && await exists(path.join(DEST, skill, 'references'));
231
- if (!quiet)
232
- console.log(`${ok ? 'ok ' : 'missing'} ${skill}`);
233
- if (!ok)
234
- missing += 1;
207
+ const report = await inspectSkillInstallation({
208
+ sourceRoot: ROOT,
209
+ destination: DEST,
210
+ packageName: String(PACKAGE_JSON.name || 'yam-flow'),
211
+ version: VERSION,
212
+ skills: SKILLS
213
+ });
214
+ if (!quiet) {
215
+ for (const skill of report.skills) {
216
+ const label = skill.status === 'ok' ? 'ok ' : skill.status === 'missing' ? 'missing' : 'drift ';
217
+ console.log(`${label} ${skill.skill}`);
218
+ }
219
+ if (report.receiptStatus === 'ok' && report.receipt) {
220
+ console.log(`ok install receipt ${report.receipt.package.name}@${report.receipt.package.version}`);
221
+ console.log(` sha256:${report.receipt.integrity.source_digest}`);
222
+ }
223
+ else {
224
+ console.log(`${report.receiptStatus === 'missing' ? 'missing' : 'drift '} install receipt`);
225
+ }
226
+ if (report.issues.length) {
227
+ console.log('');
228
+ console.log('Install integrity issues:');
229
+ for (const issue of report.issues.slice(0, 12))
230
+ console.log(`- ${issue}`);
231
+ if (report.issues.length > 12)
232
+ console.log(`- ... ${report.issues.length - 12} more issue(s)`);
233
+ if (report.recoveryArtifacts.length) {
234
+ console.log('Confirm no install is running, then inspect and preserve the transaction backup before retrying.');
235
+ }
236
+ else {
237
+ console.log('Run `yam install` to restore the package-bundled skill set.');
238
+ }
239
+ }
235
240
  }
236
- return missing;
241
+ return report.ok ? 0 : Math.max(1, report.issues.length);
237
242
  }
238
243
  async function list() {
239
244
  const manifest = await loadManifest();
@@ -279,6 +284,8 @@ async function verify({ quiet = false } = {}) {
279
284
  return failOrReport('yam verify', issues, quiet);
280
285
  if (manifest.name !== 'yam')
281
286
  issues.push('manifest name must be yam');
287
+ if (manifest.version !== VERSION)
288
+ issues.push(`manifest version must match package version: ${manifest.version || 'missing'} != ${VERSION}`);
282
289
  if (manifest.defaultHooks !== false)
283
290
  issues.push('manifest defaultHooks must be false');
284
291
  if (!Array.isArray(manifest.routes) || manifest.routes.length !== SKILLS.length) {
@@ -0,0 +1,76 @@
1
+ export declare const INSTALL_RECEIPT_NAME = ".yam-flow-install-receipt.json";
2
+ export declare const INSTALL_LOCK_NAME = ".yam-flow-install.lock";
3
+ export declare const INSTALL_RECEIPT_SCHEMA = "yam.install-receipt.v1";
4
+ export declare const INSTALL_TRANSACTION_PREFIX = ".yam-flow-install-";
5
+ export type InstallFileDigest = {
6
+ path: string;
7
+ bytes: number;
8
+ sha256: string;
9
+ };
10
+ export type InstallReceipt = {
11
+ schema: typeof INSTALL_RECEIPT_SCHEMA;
12
+ package: {
13
+ name: string;
14
+ version: string;
15
+ };
16
+ source: {
17
+ kind: 'package-bundled-skills';
18
+ identity: string;
19
+ };
20
+ destination: string;
21
+ installed_at: string;
22
+ transaction_id: string;
23
+ skills: string[];
24
+ integrity: {
25
+ algorithm: 'sha256';
26
+ file_count: number;
27
+ source_digest: string;
28
+ files: InstallFileDigest[];
29
+ };
30
+ };
31
+ export type InstallFailpointEvent = {
32
+ phase: 'after-stage' | 'after-backup' | 'skill-installed' | 'receipt-installed' | 'mirror-cleaned' | 'before-verify';
33
+ skill?: string;
34
+ };
35
+ export type TransactionalInstallOptions = {
36
+ sourceRoot: string;
37
+ destination: string;
38
+ codexMirror?: string;
39
+ packageName: string;
40
+ version: string;
41
+ skills: string[];
42
+ legacySkills?: string[];
43
+ retiredSkills?: string[];
44
+ now?: () => Date;
45
+ transactionId?: string;
46
+ failpoint?: (event: InstallFailpointEvent) => void | Promise<void>;
47
+ };
48
+ export type InstallationInspectionOptions = Pick<TransactionalInstallOptions, 'sourceRoot' | 'destination' | 'packageName' | 'version' | 'skills'> & {
49
+ ignoreRecoveryArtifacts?: boolean;
50
+ };
51
+ export type SkillInstallStatus = {
52
+ skill: string;
53
+ status: 'ok' | 'missing' | 'drift';
54
+ issues: string[];
55
+ };
56
+ export type InstallationInspection = {
57
+ ok: boolean;
58
+ destination: string;
59
+ receiptPath: string;
60
+ receiptStatus: 'ok' | 'missing' | 'drift';
61
+ receipt?: InstallReceipt;
62
+ sourceDigest?: string;
63
+ recoveryArtifacts: string[];
64
+ skills: SkillInstallStatus[];
65
+ issues: string[];
66
+ };
67
+ export type TransactionalInstallResult = {
68
+ receipt: InstallReceipt;
69
+ receiptPath: string;
70
+ installedFiles: number;
71
+ sourceDigest: string;
72
+ cleanupWarnings: string[];
73
+ };
74
+ export declare function findInstallRecoveryArtifacts(root: string): Promise<string[]>;
75
+ export declare function inspectSkillInstallation(options: InstallationInspectionOptions): Promise<InstallationInspection>;
76
+ export declare function installSkillSetTransactional(options: TransactionalInstallOptions): Promise<TransactionalInstallResult>;