yam-flow 2.4.0 → 2.5.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.md CHANGED
@@ -24,18 +24,20 @@
24
24
 
25
25
  ## Latest Update Trigger
26
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.
27
+ When the user says exactly `yam 최신 업데이트 사항 반영해줘` in a chat for this project, treat that phrase as explicit authorization to check and apply reviewed updates for yam, Scrapling, and Insane Search.
28
28
 
29
29
  Run these commands sequentially from the yam project directory:
30
30
 
31
31
  ```bash
32
- npm install -g yam-flow
33
- yam install
32
+ yam update check --json
33
+ yam update apply --all --json
34
34
  yam version
35
35
  yam status
36
36
  ```
37
37
 
38
38
  - Request any environment approval required for network access or writes outside the workspace.
39
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.
40
+ - Treat that exact phrase as one-time authorization for this run only; do not create an unattended updater, hook, or automation.
41
+ - Never edit `.codex/plugins/cache` directly. If Insane Search cannot be updated in place through the official Codex CLI, report `manual_plugin_update_required` and stop.
42
+ - After all four commands pass, summarize every component receipt, report the installed yam version and skill status, then tell the user that Codex can be restarted to reload updated skills/plugins.
41
43
  - Do not restart Codex automatically.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to yam-flow are documented here.
4
4
 
5
+ ## v2.5.0 - 2026-07-30
6
+
7
+ ### Added
8
+
9
+ - Added read-only `yam update check [--json]` for yam, Scrapling, and Insane Search with official registry/manifest versions and source-revision metadata.
10
+ - Added explicitly authorized component/all update application with per-component receipts, a concurrent-run lock, exact stable versions, verification evidence, and rollback guidance.
11
+ - Added isolated Scrapling version environments with `pip check`, HTTP extraction, browser extraction, and atomic executable switching; the previous environment remains available for rollback.
12
+ - Added deterministic temp-only updater smoke coverage, including receipt-failure symlink rollback, manual Codex plugin fallback, strict no-remove/cache behavior, and yam-last `--all` ordering.
13
+
14
+ ### Improved
15
+
16
+ - The build now preserves executable mode on `dist/bin/yam.js`, and dist freshness fails if a global npm install would expose a non-executable `yam` target.
17
+
18
+ ### Security
19
+
20
+ - Insane Search updates pin the official manifest to the exact observed Git revision and use only official Codex CLI commands. A missing safe in-place path returns `manual_plugin_update_required`; an unreadable post-upgrade state fails closed before `add`; yam never removes the plugin first or writes `.codex/plugins/cache` directly.
21
+ - External commands use argument arrays without a shell, version/component allowlists, redacted captured output, and fail-closed apply locking.
22
+ - Existing Scrapling links require a matching yam ownership marker, and an unwritable receipt stops `--all` before any later component mutation.
23
+
24
+ ### Verification
25
+
26
+ - `npm run external-updates-smoke`
27
+ - `npm run typecheck`
28
+ - `npm run verify:self`
29
+ - `npm run cli-smoke`
30
+ - `npm run dist:freshness`
31
+ - `npm run package-boundary:check`
32
+
5
33
  ## v2.4.0 - 2026-07-27
6
34
 
7
35
  ### Added
package/COMMANDS.md CHANGED
@@ -143,6 +143,19 @@ yam measure quick --files 3 --commands 1 --report-lines 5 --seconds 40
143
143
  yam template tuning
144
144
  ```
145
145
 
146
+ ## External Updates
147
+
148
+ ```bash
149
+ yam update check
150
+ yam update check --json
151
+ yam update apply --component scrapling --json
152
+ yam update apply --component insane-search --json
153
+ yam update apply --component yam --json
154
+ yam update apply --all --json
155
+ ```
156
+
157
+ `yam update check` is read-only. Every `apply` needs explicit authorization, uses a concurrent-run lock, records a component receipt, and stops on failed/manual results. `--all` runs yam last. Insane Search is never remove-first and `.codex/plugins/cache` is never edited directly.
158
+
146
159
  ## Tool Doctor / Proof / Safety
147
160
 
148
161
  Read-only readiness and evidence helpers. These do not install, deploy, query databases, or run verification by themselves.
package/README.md CHANGED
@@ -130,6 +130,54 @@ Manual copying can still make a skill loadable, but it intentionally remains unv
130
130
 
131
131
  Restart Codex after installing so skills reload.
132
132
 
133
+ ## External Updates
134
+
135
+ Update checks are read-only and never authorize a mutation by themselves:
136
+
137
+ ```bash
138
+ yam update check
139
+ yam update check --json
140
+ ```
141
+
142
+ Apply requires an explicit component or `--all`:
143
+
144
+ ```bash
145
+ yam update apply --component scrapling
146
+ yam update apply --component insane-search
147
+ yam update apply --component yam
148
+ yam update apply --all --json
149
+ ```
150
+
151
+ `--all` processes Scrapling, then Insane Search, then yam so the running updater replaces itself last. It stops at the first failed or manual-only component. Each attempt writes a component receipt under `~/.local/state/yam/update-receipts/` with the old/new version, source revision, checks, side effects, outcome, and rollback guidance. A lock prevents concurrent apply runs.
152
+
153
+ Scrapling updates install an exact stable PyPI version into a new versioned virtual environment. `pip check`, ordinary HTTP extraction, and headless browser extraction must pass before the executable symlink changes atomically; the previous environment is retained. An existing environment must have a matching `.yam-scrapling-install.json` ownership marker before yam will replace its link. Insane Search uses only the official `codex plugin` commands. yam never edits `.codex/plugins/cache`, never removes the plugin first, and returns `manual_plugin_update_required` when the installed Codex CLI cannot update it safely in place.
154
+
155
+ There is no unattended updater. The exact project trigger in `AGENTS.md` grants one-time authorization for a checked `--all` run, and Codex is never restarted automatically.
156
+
157
+ ### Scrapling Usage
158
+
159
+ The isolated executable can be used directly from the shell:
160
+
161
+ ```bash
162
+ scrapling --version
163
+ scrapling extract get https://example.com example.txt --css-selector h1
164
+ scrapling extract stealthy-fetch https://example.com example-browser.txt --css-selector h1 --headless
165
+ ```
166
+
167
+ From Python inside the installed environment:
168
+
169
+ ```python
170
+ from scrapling.fetchers import Fetcher, StealthyFetcher
171
+
172
+ page = Fetcher.get("https://example.com")
173
+ print(page.css("h1::text").get())
174
+
175
+ browser_page = StealthyFetcher.fetch("https://example.com", headless=True)
176
+ print(browser_page.css("h1::text").get())
177
+ ```
178
+
179
+ Use `Fetcher` for ordinary HTTP pages and `StealthyFetcher` only when a real browser or stronger anti-bot behavior is needed; the browser path is heavier.
180
+
133
181
  ## Uninstall
134
182
 
135
183
  ```bash
package/ROADMAP.md CHANGED
@@ -97,6 +97,7 @@ Tasks:
97
97
  - Add symlink-friendly local install.
98
98
  - Add version command. Done.
99
99
  - Add staged transactional install, rollback, receipt, and drift-aware status. Done.
100
+ - Add checked component updates for yam, Scrapling, and Insane Search with explicit authorization, receipts, and rollback guidance. Done.
100
101
  - Consider npm packaging only after route behavior stabilizes.
101
102
 
102
103
  ### 8. Scout / Research Workflow
package/dist/bin/yam.js CHANGED
@@ -11,6 +11,7 @@ import { fileURLToPath } from 'node:url';
11
11
  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';
12
12
  import { archiveUeyeRevision, inspectImageFile, upsertUeyeAsset, verifyUeyeAssetManifest, verifyUeyeRevisionHistory } from '../lib/ueye-artifacts.js';
13
13
  import { inspectSkillInstallation, installSkillSetTransactional, uninstallSkillSetSafely } from '../lib/skill-installation.js';
14
+ import { EXTERNAL_UPDATE_COMPONENTS, applyExternalUpdates, checkExternalUpdates } from '../lib/external-updates.js';
14
15
  function errorMessage(error) {
15
16
  return error instanceof Error ? error.message : String(error);
16
17
  }
@@ -146,6 +147,9 @@ Usage:
146
147
  yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
147
148
  yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
148
149
  yam mission gate [--expected-thread id] [--receipt file] [--json]
150
+ yam update check [--json]
151
+ yam update apply --component yam|scrapling|insane-search [--json]
152
+ yam update apply --all [--json]
149
153
  yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
150
154
  yam release report [--json]
151
155
  yam safety [text...]
@@ -3556,6 +3560,86 @@ async function mission(args = []) {
3556
3560
  console.error(`unknown mission command: ${subcommand}`);
3557
3561
  return missionUsage();
3558
3562
  }
3563
+ async function updateCommand(args = []) {
3564
+ const subcommand = args[0] || 'help';
3565
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
3566
+ return updateUsage();
3567
+ if (subcommand === 'check') {
3568
+ const rest = args.slice(1);
3569
+ const invalid = rest.filter((arg) => arg !== '--json');
3570
+ if (invalid.length || rest.filter((arg) => arg === '--json').length > 1) {
3571
+ throw new Error(`invalid update check option: ${invalid[0] || '--json repeated'}`);
3572
+ }
3573
+ const report = await checkExternalUpdates(VERSION);
3574
+ printJsonOrHuman(report, rest.includes('--json'), 'External update check');
3575
+ if (!report.success)
3576
+ process.exitCode = 1;
3577
+ return;
3578
+ }
3579
+ if (subcommand !== 'apply') {
3580
+ throw new Error(`unknown update command: ${subcommand}`);
3581
+ }
3582
+ const parsed = parseUpdateApplyArgs(args.slice(1));
3583
+ const report = await applyExternalUpdates(VERSION, {
3584
+ component: parsed.component,
3585
+ all: parsed.all
3586
+ });
3587
+ printJsonOrHuman(report, parsed.json, 'External update apply');
3588
+ if (!report.success)
3589
+ process.exitCode = 1;
3590
+ }
3591
+ function parseUpdateApplyArgs(args) {
3592
+ let component;
3593
+ let all = false;
3594
+ let json = false;
3595
+ for (let index = 0; index < args.length; index += 1) {
3596
+ const arg = String(args[index] || '');
3597
+ if (arg === '--json') {
3598
+ if (json)
3599
+ throw new Error('--json may be supplied only once');
3600
+ json = true;
3601
+ continue;
3602
+ }
3603
+ if (arg === '--all') {
3604
+ if (all)
3605
+ throw new Error('--all may be supplied only once');
3606
+ all = true;
3607
+ continue;
3608
+ }
3609
+ if (arg === '--component' || arg.startsWith('--component=')) {
3610
+ if (component)
3611
+ throw new Error('--component may be supplied only once');
3612
+ const value = arg === '--component' ? String(args[index + 1] || '') : arg.slice('--component='.length);
3613
+ if (arg === '--component')
3614
+ index += 1;
3615
+ if (!EXTERNAL_UPDATE_COMPONENTS.includes(value)) {
3616
+ throw new Error('component must be one of: yam, scrapling, insane-search');
3617
+ }
3618
+ component = value;
3619
+ continue;
3620
+ }
3621
+ throw new Error(`invalid update apply option: ${arg}`);
3622
+ }
3623
+ if ((all && component) || (!all && !component)) {
3624
+ throw new Error('choose exactly one of --component yam|scrapling|insane-search or --all');
3625
+ }
3626
+ return { component, all, json };
3627
+ }
3628
+ function updateUsage() {
3629
+ console.log(`yam update
3630
+
3631
+ Usage:
3632
+ yam update check [--json]
3633
+ yam update apply --component yam|scrapling|insane-search [--json]
3634
+ yam update apply --all [--json]
3635
+
3636
+ Safety:
3637
+ check is read-only. apply requires an explicit component or --all.
3638
+ --all updates Scrapling and Insane Search before yam, then stops at the first failed/manual component.
3639
+ Insane Search is updated only through the official Codex CLI; plugin caches are never edited directly.
3640
+ Every apply attempt records component checks, outcome, side effects, and rollback guidance.
3641
+ `);
3642
+ }
3559
3643
  function missionUsage() {
3560
3644
  console.log(`yam mission
3561
3645
 
@@ -5313,6 +5397,7 @@ function showRequestedHelp(args) {
5313
5397
  media: mediaUsage,
5314
5398
  runtime: runtimeUsage,
5315
5399
  mission: missionUsage,
5400
+ update: updateUsage,
5316
5401
  benchmark: benchmarkUsage,
5317
5402
  memory: memoryUsage
5318
5403
  };
@@ -5360,6 +5445,8 @@ async function main() {
5360
5445
  return runtime(process.argv.slice(3));
5361
5446
  if (command === 'mission')
5362
5447
  return mission(process.argv.slice(3));
5448
+ if (command === 'update')
5449
+ return updateCommand(process.argv.slice(3));
5363
5450
  if (command === 'benchmark')
5364
5451
  return benchmark(process.argv.slice(3));
5365
5452
  if (command === 'release')
@@ -0,0 +1,103 @@
1
+ export declare const EXTERNAL_UPDATE_COMPONENTS: readonly ["yam", "scrapling", "insane-search"];
2
+ export type ExternalUpdateComponent = typeof EXTERNAL_UPDATE_COMPONENTS[number];
3
+ type JsonRecord = Record<string, any>;
4
+ export interface ExternalCommandResult {
5
+ ok: boolean;
6
+ status: number | null;
7
+ stdout: string;
8
+ stderr: string;
9
+ }
10
+ export interface ExternalCommandOptions {
11
+ cwd?: string;
12
+ env?: NodeJS.ProcessEnv;
13
+ timeout?: number;
14
+ }
15
+ export interface ExternalUpdatePaths {
16
+ stateDir: string;
17
+ receiptDir: string;
18
+ lockFile: string;
19
+ scraplingRoot: string;
20
+ scraplingBin: string;
21
+ }
22
+ export interface ExternalUpdateDependencies {
23
+ run?: (command: string, args: string[], options?: ExternalCommandOptions) => Promise<ExternalCommandResult> | ExternalCommandResult;
24
+ fetchJson?: (url: string) => Promise<any>;
25
+ now?: () => Date;
26
+ homeDir?: string;
27
+ env?: NodeJS.ProcessEnv;
28
+ paths?: Partial<ExternalUpdatePaths>;
29
+ removeCandidate?: (candidate: string, root: string) => Promise<void>;
30
+ }
31
+ export interface ExternalComponentCheck {
32
+ component: ExternalUpdateComponent;
33
+ installed_version: string;
34
+ latest_version: string;
35
+ update_available: boolean;
36
+ status: 'up_to_date' | 'update_available' | 'not_installed' | 'check_failed';
37
+ source: string;
38
+ source_revision?: {
39
+ local: string;
40
+ remote: string;
41
+ drift: boolean;
42
+ note: string;
43
+ };
44
+ executable?: string;
45
+ error?: string;
46
+ }
47
+ export interface ExternalComponentReceipt {
48
+ schema: 'yam.external-update-receipt.v1';
49
+ generated_at: string;
50
+ component: ExternalUpdateComponent;
51
+ outcome: 'up_to_date' | 'updated' | 'failed' | 'manual_plugin_update_required';
52
+ previous_version: string;
53
+ requested_version: string;
54
+ installed_version: string;
55
+ source: string;
56
+ source_revision: JsonRecord | null;
57
+ checks: Array<{
58
+ id: string;
59
+ status: 'passed' | 'failed' | 'skipped';
60
+ note: string;
61
+ }>;
62
+ rollback_hint: {
63
+ automatic: boolean;
64
+ previous_target: string;
65
+ guidance: string;
66
+ };
67
+ side_effects: string[];
68
+ error: string;
69
+ truth_status: 'verified' | 'partial' | 'blocked';
70
+ persistence?: 'written' | 'failed';
71
+ receipt_path?: string;
72
+ receipt_error?: string;
73
+ }
74
+ export declare function checkExternalUpdates(currentYamVersion: string, dependencies?: ExternalUpdateDependencies): Promise<{
75
+ schema: string;
76
+ generated_at: string;
77
+ mutation_authorized: boolean;
78
+ components: [ExternalComponentCheck, ExternalComponentCheck, ExternalComponentCheck];
79
+ update_available: boolean;
80
+ success: boolean;
81
+ truth_status: string;
82
+ next_action: string;
83
+ }>;
84
+ export declare function applyExternalUpdates(currentYamVersion: string, selection: {
85
+ component?: ExternalUpdateComponent;
86
+ all?: boolean;
87
+ }, dependencies?: ExternalUpdateDependencies): Promise<{
88
+ schema: string;
89
+ generated_at: string;
90
+ mutation_authorized: boolean;
91
+ requested_components: ("yam" | "scrapling" | "insane-search")[];
92
+ applied_components: ("yam" | "scrapling" | "insane-search")[];
93
+ receipts: ExternalComponentReceipt[];
94
+ stopped_early: boolean;
95
+ lock: {
96
+ path: string;
97
+ released: boolean;
98
+ };
99
+ success: boolean;
100
+ truth_status: string;
101
+ next_action: string;
102
+ }>;
103
+ export {};