yam-flow 2.4.0 → 2.5.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
@@ -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,60 @@
2
2
 
3
3
  All notable changes to yam-flow are documented here.
4
4
 
5
+ ## v2.5.1 - 2026-07-31
6
+
7
+ ### Added
8
+
9
+ - Added bounded stdin readers with a 1 MiB hook limit and a 4 MiB general CLI limit. Malformed or oversized hook payloads now return a valid fail-open response without echoing or acting on rejected input.
10
+ - Added a final read-only `yam doctor --json` verification to yam updates and automatic rollbacks. The updater accepts success only when the command exits cleanly and returns the expected healthy Doctor contract.
11
+ - Added a release-time secret guard over the actual npm dry-run packlist. It scans regular UTF-8 text files for high-signal private-key and provider credential patterns while reporting only pattern, path, and line metadata.
12
+
13
+ ### Improved
14
+
15
+ - Install and Ueye integrity manifests now use locale-independent ordinal ordering. Existing yam 2.5.0-and-earlier receipts retain compatibility with their stored legacy order while new digests are deterministic across locales.
16
+
17
+ ### Security
18
+
19
+ - Rejected stdin payloads are byte-bounded, are not included in diagnostics, and cannot supply hook context or trigger workspace mutation.
20
+ - `verify:self`, `prepack`, `prepublishOnly`, and `release:check` now inherit the npm packlist secret guard; the inner pack inspection disables lifecycle scripts to avoid recursive execution.
21
+ - Updater Doctor output recorded in receipts remains redacted and bounded, and an unhealthy rollback is not reported as an automatic recovery.
22
+
23
+ ### Verification
24
+
25
+ - TypeScript typecheck and build
26
+ - Bounded hook latency/read-only smoke and packaged CLI smoke
27
+ - Locale-independent install/Ueye manifest regression smokes
28
+ - External update/rollback Doctor smoke
29
+ - Npm packlist secret-pattern and redaction smoke
30
+
31
+ ## v2.5.0 - 2026-07-30
32
+
33
+ ### Added
34
+
35
+ - Added read-only `yam update check [--json]` for yam, Scrapling, and Insane Search with official registry/manifest versions and source-revision metadata.
36
+ - Added explicitly authorized component/all update application with per-component receipts, a concurrent-run lock, exact stable versions, verification evidence, and rollback guidance.
37
+ - Added isolated Scrapling version environments with `pip check`, HTTP extraction, browser extraction, and atomic executable switching; the previous environment remains available for rollback.
38
+ - 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.
39
+
40
+ ### Improved
41
+
42
+ - 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.
43
+
44
+ ### Security
45
+
46
+ - 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.
47
+ - External commands use argument arrays without a shell, version/component allowlists, redacted captured output, and fail-closed apply locking.
48
+ - Existing Scrapling links require a matching yam ownership marker, and an unwritable receipt stops `--all` before any later component mutation.
49
+
50
+ ### Verification
51
+
52
+ - `npm run external-updates-smoke`
53
+ - `npm run typecheck`
54
+ - `npm run verify:self`
55
+ - `npm run cli-smoke`
56
+ - `npm run dist:freshness`
57
+ - `npm run package-boundary:check`
58
+
5
59
  ## v2.4.0 - 2026-07-27
6
60
 
7
61
  ### 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
@@ -99,6 +99,8 @@ yam hook status --global
99
99
 
100
100
  The profile installs `UserPromptSubmit` and `Stop` handlers. At `Stop`, yam checks the latest assistant message with the same read-only Study Note Guard used by `yam study-note check`. If changed artifacts lack the required role, execution point, before/after, expected behavior, syntax/structure, verification, limits, or relevant architecture hygiene, Codex receives one correction prompt. A second failed check warns but does not loop forever. If Git change scope is unavailable, the guard reports partial truth and keeps a manual-inspection warning visible instead of claiming the project is clean. The hook never writes the Study Note or runs verification for the agent.
101
101
 
102
+ Hook stdin is limited to 1 MiB. Malformed JSON or an oversized payload produces a valid fail-open response without echoing the rejected content, deriving hook context from it, or mutating the workspace. Other CLI commands that read piped text use a 4 MiB limit and fail closed when it is exceeded.
103
+
102
104
  `yam hook status` reports stale or missing command targets as `broken` and exits nonzero. Run `yam hook enable <profile> --global` again to create a timestamped backup, preserve unrelated hooks, and migrate that profile to the current installed path and event coverage. Restart Codex or open a new task after changing hooks.
103
105
 
104
106
  ## Install
@@ -120,7 +122,7 @@ yam install --replace-user-skill quick
120
122
 
121
123
  Repeat `--replace-user-skill <name>` for each reviewed conflict. Unproven legacy, retired, and Codex mirror directories are preserved rather than deleted by name.
122
124
 
123
- 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.
125
+ 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. Integrity paths and digests use locale-independent ordinal ordering; receipts written by yam 2.5.0 and earlier are also checked against their stored legacy order during upgrade. `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.
124
126
 
125
127
  The receipt is local integrity provenance, not a cryptographic signature or a security boundary against another process running as the same OS user. An actor that can rewrite both the receipt and every recorded file can forge local ownership evidence.
126
128
 
@@ -130,6 +132,54 @@ Manual copying can still make a skill loadable, but it intentionally remains unv
130
132
 
131
133
  Restart Codex after installing so skills reload.
132
134
 
135
+ ## External Updates
136
+
137
+ Update checks are read-only and never authorize a mutation by themselves:
138
+
139
+ ```bash
140
+ yam update check
141
+ yam update check --json
142
+ ```
143
+
144
+ Apply requires an explicit component or `--all`:
145
+
146
+ ```bash
147
+ yam update apply --component scrapling
148
+ yam update apply --component insane-search
149
+ yam update apply --component yam
150
+ yam update apply --all --json
151
+ ```
152
+
153
+ `--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. A yam update is successful only after the installed version and status checks plus a final read-only `yam doctor --json` return the expected healthy contract. Doctor failure triggers the existing rollback path, and rollback is considered automatic only when its own version, status, and Doctor checks pass. 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.
154
+
155
+ 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.
156
+
157
+ 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.
158
+
159
+ ### Scrapling Usage
160
+
161
+ The isolated executable can be used directly from the shell:
162
+
163
+ ```bash
164
+ scrapling --version
165
+ scrapling extract get https://example.com example.txt --css-selector h1
166
+ scrapling extract stealthy-fetch https://example.com example-browser.txt --css-selector h1 --headless
167
+ ```
168
+
169
+ From Python inside the installed environment:
170
+
171
+ ```python
172
+ from scrapling.fetchers import Fetcher, StealthyFetcher
173
+
174
+ page = Fetcher.get("https://example.com")
175
+ print(page.css("h1::text").get())
176
+
177
+ browser_page = StealthyFetcher.fetch("https://example.com", headless=True)
178
+ print(browser_page.css("h1::text").get())
179
+ ```
180
+
181
+ 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.
182
+
133
183
  ## Uninstall
134
184
 
135
185
  ```bash
@@ -207,6 +257,8 @@ yam status
207
257
 
208
258
  After `yam status` reports all skills and the install receipt as `ok`, restart Codex so the refreshed skills reload.
209
259
 
260
+ Before packaging or publishing, `verify:self` inspects the exact file list returned by `npm pack --dry-run --json --ignore-scripts`. The release gate scans regular UTF-8 text files up to 2 MiB for high-signal private-key, npm, GitHub, OpenAI, Anthropic, and AWS credential patterns. Findings expose only the pattern id, package-relative path, and line number; matched text is never printed. Binary, symlink, non-regular, unreadable, outside-root, and oversized entries are reported as skipped rather than silently represented as scanned.
261
+
210
262
  Publishing still requires confirming the final npm package name and account access.
211
263
 
212
264
  ## Trust Kernel
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
@@ -171,7 +172,7 @@ Tasks:
171
172
 
172
173
  - Add `.yam/memory/records/*.json` storage. Done.
173
174
  - Add `yam memory init/add/list/summary/resolve`. Done.
174
- - Add lightweight secret-pattern blocking. Done.
175
+ - Add lightweight secret-pattern blocking, including an actual npm dry-run packlist release guard with redacted findings. Done.
175
176
  - Teach context reuse to prefer `summary.md`, not full record reads. Done.
176
177
  - Keep memory opt-in only. Done.
177
178
 
@@ -307,6 +308,7 @@ Tasks:
307
308
  - Keep `yam-lite` advisory-only through `UserPromptSubmit`. Done.
308
309
  - Add Study Note `UserPromptSubmit` reminder plus one-pass `Stop` completion gate. Done.
309
310
  - Detect broken hook targets and migrate stale paths without replacing unrelated hooks. Done.
311
+ - Bound hook/general stdin and fail open safely for rejected hook payloads. Done.
310
312
  - Add `references/tool-trust-layer.md`. Done.
311
313
  - Add read-only `yam tools doctor`. Done.
312
314
  - Add `yam proof` summary command. Done.
@@ -317,6 +319,8 @@ Tasks:
317
319
  - Add shallow `.sql` destructive keyword scan. Done.
318
320
  - Add `yam proof write`. Done.
319
321
  - Prepare npm/npx package metadata and dry-run packaging. Done.
322
+ - Make integrity-critical install and Ueye manifest ordering locale-independent. Done.
323
+ - Require a final read-only Doctor contract for yam update and rollback success. Done.
320
324
  - Add small route-scoped trust kernel: truth caps, fake/real policy, runtime truth matrix, and completion proof object. Done.
321
325
 
322
326
  ### 16. Deferred Triggers
package/dist/bin/yam.js CHANGED
@@ -11,6 +11,8 @@ 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';
15
+ import { BoundedInputError, GENERAL_STDIN_MAX_BYTES, HOOK_STDIN_MAX_BYTES, readBoundedStdinJson, readBoundedStdinText } from '../lib/bounded-input.js';
14
16
  function errorMessage(error) {
15
17
  return error instanceof Error ? error.message : String(error);
16
18
  }
@@ -146,6 +148,9 @@ Usage:
146
148
  yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
147
149
  yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
148
150
  yam mission gate [--expected-thread id] [--receipt file] [--json]
151
+ yam update check [--json]
152
+ yam update apply --component yam|scrapling|insane-search [--json]
153
+ yam update apply --all [--json]
149
154
  yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
150
155
  yam release report [--json]
151
156
  yam safety [text...]
@@ -2004,7 +2009,20 @@ async function hookRun(args = []) {
2004
2009
  console.log(JSON.stringify({ continue: true }));
2005
2010
  return;
2006
2011
  }
2007
- const input = await readStdinJson();
2012
+ let input;
2013
+ try {
2014
+ input = await readStdinJson();
2015
+ }
2016
+ catch (error) {
2017
+ if (!(error instanceof BoundedInputError))
2018
+ throw error;
2019
+ const code = error.code === 'invalid_json' ? 'invalid_hook_json' : error.code;
2020
+ console.log(JSON.stringify({
2021
+ continue: true,
2022
+ systemMessage: `yam-${profile} hook input rejected: ${code}; continuing without using or echoing the payload.`
2023
+ }));
2024
+ return;
2025
+ }
2008
2026
  const event = input?.hook_event_name || input?.hookEventName || input?.event || 'UserPromptSubmit';
2009
2027
  const cwd = String(input?.cwd || process.cwd());
2010
2028
  if (profile === 'study-note' && event === 'Stop') {
@@ -2025,18 +2043,7 @@ async function hookRun(args = []) {
2025
2043
  console.log(JSON.stringify(output));
2026
2044
  }
2027
2045
  async function readStdinJson() {
2028
- const chunks = [];
2029
- for await (const chunk of process.stdin)
2030
- chunks.push(Buffer.from(chunk));
2031
- const text = Buffer.concat(chunks).toString('utf8').trim();
2032
- if (!text)
2033
- return {};
2034
- try {
2035
- return JSON.parse(text);
2036
- }
2037
- catch {
2038
- return {};
2039
- }
2046
+ return readBoundedStdinJson(process.stdin, HOOK_STDIN_MAX_BYTES);
2040
2047
  }
2041
2048
  function extractPrompt(input = {}) {
2042
2049
  const candidates = [
@@ -2667,10 +2674,7 @@ function redactSensitiveText(text = '') {
2667
2674
  async function readStdinTextIfAvailable() {
2668
2675
  if (process.stdin.isTTY)
2669
2676
  return '';
2670
- const chunks = [];
2671
- for await (const chunk of process.stdin)
2672
- chunks.push(Buffer.from(chunk));
2673
- return Buffer.concat(chunks).toString('utf8').trim();
2677
+ return (await readBoundedStdinText(process.stdin, GENERAL_STDIN_MAX_BYTES)).trim();
2674
2678
  }
2675
2679
  async function loop(args = []) {
2676
2680
  const subcommand = args[0] || 'help';
@@ -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,12 @@
1
+ export declare const HOOK_STDIN_MAX_BYTES: number;
2
+ export declare const GENERAL_STDIN_MAX_BYTES: number;
3
+ export type BoundedInputErrorCode = 'input_too_large' | 'invalid_json';
4
+ export declare class BoundedInputError extends Error {
5
+ readonly code: BoundedInputErrorCode;
6
+ readonly maxBytes?: number;
7
+ constructor(code: BoundedInputErrorCode, options?: {
8
+ maxBytes?: number;
9
+ });
10
+ }
11
+ export declare function readBoundedStdinText(input: NodeJS.ReadableStream & AsyncIterable<string | Buffer>, maxBytes: number): Promise<string>;
12
+ export declare function readBoundedStdinJson(input: NodeJS.ReadableStream & AsyncIterable<string | Buffer>, maxBytes: number): Promise<Record<string, unknown>>;
@@ -0,0 +1,48 @@
1
+ export const HOOK_STDIN_MAX_BYTES = 1024 * 1024;
2
+ export const GENERAL_STDIN_MAX_BYTES = 4 * 1024 * 1024;
3
+ export class BoundedInputError extends Error {
4
+ code;
5
+ maxBytes;
6
+ constructor(code, options = {}) {
7
+ const detail = code === 'input_too_large' && options.maxBytes
8
+ ? `stdin input exceeds the ${options.maxBytes}-byte limit`
9
+ : 'stdin input is not a valid JSON object';
10
+ super(`[${code}] ${detail}`);
11
+ this.name = 'BoundedInputError';
12
+ this.code = code;
13
+ this.maxBytes = options.maxBytes;
14
+ }
15
+ }
16
+ export async function readBoundedStdinText(input, maxBytes) {
17
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
18
+ throw new Error('stdin byte limit must be a positive safe integer');
19
+ }
20
+ const chunks = [];
21
+ let totalBytes = 0;
22
+ for await (const chunk of input) {
23
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
24
+ totalBytes += bytes.byteLength;
25
+ if (totalBytes > maxBytes) {
26
+ throw new BoundedInputError('input_too_large', { maxBytes });
27
+ }
28
+ chunks.push(bytes);
29
+ }
30
+ return Buffer.concat(chunks, totalBytes).toString('utf8');
31
+ }
32
+ export async function readBoundedStdinJson(input, maxBytes) {
33
+ const text = (await readBoundedStdinText(input, maxBytes)).trim();
34
+ if (!text)
35
+ return {};
36
+ try {
37
+ const value = JSON.parse(text);
38
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
39
+ throw new BoundedInputError('invalid_json');
40
+ }
41
+ return value;
42
+ }
43
+ catch (error) {
44
+ if (error instanceof BoundedInputError)
45
+ throw error;
46
+ throw new BoundedInputError('invalid_json');
47
+ }
48
+ }
@@ -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 {};