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 +18 -0
- package/CHANGELOG.md +22 -0
- package/README.md +6 -12
- package/ROADMAP.md +2 -0
- package/dist/bin/yam.js +56 -49
- package/dist/lib/skill-installation.d.ts +76 -0
- package/dist/lib/skill-installation.js +554 -0
- package/package.json +3 -2
- package/src/bin/yam.ts +55 -50
- package/src/lib/skill-installation.ts +638 -0
- package/yam.manifest.json +2 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
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>;
|