yam-flow 2.8.1 → 2.8.3

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.
@@ -0,0 +1,165 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ const hash = (value) => createHash('sha256').update(value).digest('hex');
7
+ const MAX_FILES = 4000;
8
+ const MAX_BYTES = 2 * 1024 * 1024;
9
+ function git(dir, args) {
10
+ const result = spawnSync('git', args, { cwd: dir, encoding: 'utf8', timeout: 750, maxBuffer: MAX_BYTES,
11
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } });
12
+ if (result.status !== 0)
13
+ throw new Error('git_scope_unavailable');
14
+ return result.stdout;
15
+ }
16
+ function snapshot(dir) {
17
+ const root = fs.realpathSync(git(dir, ['rev-parse', '--show-toplevel']).trim());
18
+ const config = policy(root);
19
+ const files = Object.create(null);
20
+ let bytesRead = 0;
21
+ const tracked = git(root, ['ls-files', '--stage', '-z']).split('\0').filter(Boolean);
22
+ for (const row of tracked) {
23
+ const tab = row.indexOf('\t');
24
+ if (selected(row.slice(tab + 1), config))
25
+ files[row.slice(tab + 1)] = hash(row.slice(0, tab));
26
+ }
27
+ const rows = git(root, ['status', '--short', '-z', '--untracked-files=all']).split('\0');
28
+ for (let i = 0; i < rows.length; i++) {
29
+ const row = rows[i];
30
+ if (!row)
31
+ continue;
32
+ const name = row.slice(3);
33
+ if (/[RC]/.test(row.slice(0, 2)))
34
+ i++;
35
+ if (!selected(name, config))
36
+ continue;
37
+ const target = path.resolve(root, name);
38
+ if (!target.startsWith(root + path.sep))
39
+ throw new Error('unsafe_path');
40
+ // Do not follow symlinks, including symlinked parent directories.
41
+ let parent = path.dirname(target);
42
+ while (parent !== root) {
43
+ try {
44
+ if (fs.lstatSync(parent).isSymbolicLink())
45
+ throw new Error('unsafe_path');
46
+ }
47
+ catch (error) {
48
+ if (error.code !== 'ENOENT')
49
+ throw error;
50
+ }
51
+ parent = path.dirname(parent);
52
+ }
53
+ let content = 'missing';
54
+ try {
55
+ const stat = fs.lstatSync(target);
56
+ if (stat.isSymbolicLink())
57
+ content = 'link:' + hash(fs.readlinkSync(target));
58
+ else if (stat.isFile() && (bytesRead += stat.size) <= MAX_BYTES)
59
+ content = hash(fs.readFileSync(target));
60
+ else
61
+ throw new Error('scope_limit');
62
+ }
63
+ catch (error) {
64
+ if (error.code !== 'ENOENT')
65
+ throw error;
66
+ }
67
+ files[name] = hash(JSON.stringify([row.slice(0, 2), files[name] || '', content]));
68
+ }
69
+ if (Object.keys(files).length > MAX_FILES)
70
+ throw new Error('scope_limit');
71
+ return { root, files };
72
+ }
73
+ function policy(root) {
74
+ const target = path.join(root, '.yam', 'study-note.json');
75
+ let value = {};
76
+ if (fs.existsSync(target)) {
77
+ if (fs.lstatSync(path.dirname(target)).isSymbolicLink() || fs.lstatSync(target).isSymbolicLink()
78
+ || fs.statSync(target).size > 16384)
79
+ throw new Error('invalid_policy');
80
+ value = JSON.parse(fs.readFileSync(target, 'utf8'));
81
+ if (!value || !['advisory', 'strict', undefined].includes(value.mode))
82
+ throw new Error('invalid_policy');
83
+ }
84
+ const list = (key) => {
85
+ const result = value[key] || [];
86
+ if (!Array.isArray(result) || result.length > 100 || result.some((x) => typeof x !== 'string'
87
+ || !x || x.startsWith('/') || x.includes('..') || /[\\\0]/.test(x) || x.replace(/\/\*\*$/, '').includes('*')))
88
+ throw new Error('invalid_policy');
89
+ return result;
90
+ };
91
+ return { include: list('include'), exclude: list('exclude'), strict: value.mode === 'strict' };
92
+ }
93
+ const matches = (name, pattern) => pattern.endsWith('/**')
94
+ ? name.startsWith(pattern.slice(0, -2)) : name === pattern;
95
+ function selected(name, config) {
96
+ if (config.include.some(p => matches(name, p)))
97
+ return true;
98
+ if (config.exclude.some(p => matches(name, p)))
99
+ return false;
100
+ return !/^(dist\/|\.yam\/(mission|security|logs|screenshots)\/)/.test(name)
101
+ && !/\.tgz$/.test(name);
102
+ }
103
+ function statePath(root, session) {
104
+ const dir = path.join(os.tmpdir(), `yam-study-note-${process.getuid?.() ?? 'user'}`);
105
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
106
+ const stat = fs.lstatSync(dir);
107
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (process.getuid && stat.uid !== process.getuid())
108
+ || (process.platform !== 'win32' && (stat.mode & 0o077)))
109
+ throw new Error('unsafe_state');
110
+ return path.join(dir, hash(root + '\0' + session) + '.json');
111
+ }
112
+ /** Hash-only, bounded scope evidence; not a security attestation. */
113
+ export function studyNoteTurnScope(input, cwd, begin = false) {
114
+ try {
115
+ const session = String(input.session_id || input.sessionId || input.thread_id || '');
116
+ const turn = String(input.turn_id || input.turnId || '');
117
+ if (!session || session.length > 512 || turn.length > 512)
118
+ throw new Error('session_unavailable');
119
+ const current = snapshot(cwd);
120
+ const config = policy(current.root);
121
+ const target = statePath(current.root, session);
122
+ if (begin) {
123
+ const prompt = String(input.prompt || input.user_prompt || '');
124
+ // Deep/Mission alone do not make a read-only task strict. Risk intent or explicit strict does.
125
+ const strict = config.strict || /(?:study-note\s+strict|strict\s+study.note|commit|push|deploy|publish|security|migration|database|\bDB\b|커밋|푸시|배포|보안|마이그레이션|데이터베이스)/i.test(prompt);
126
+ const state = { schema: 'yam.study-note-baseline.v1', created: Date.now(), turn, strict, ...current };
127
+ try {
128
+ const previous = fs.lstatSync(target);
129
+ if (!previous.isFile() || previous.isSymbolicLink() || previous.nlink !== 1
130
+ || (process.getuid && previous.uid !== process.getuid()))
131
+ throw new Error('unsafe_state');
132
+ }
133
+ catch (error) {
134
+ if (error.code !== 'ENOENT')
135
+ throw error;
136
+ }
137
+ const fd = fs.openSync(target, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | (fs.constants.O_NOFOLLOW || 0), 0o600);
138
+ try {
139
+ fs.writeFileSync(fd, JSON.stringify(state));
140
+ }
141
+ finally {
142
+ fs.closeSync(fd);
143
+ }
144
+ return { available: true, files: [], source: 'turn_baseline', mode: strict ? 'strict' : 'advisory', reason: '' };
145
+ }
146
+ const stat = fs.lstatSync(target);
147
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_BYTES)
148
+ throw new Error('invalid_baseline');
149
+ const before = JSON.parse(fs.readFileSync(target, 'utf8'));
150
+ if (before.schema !== 'yam.study-note-baseline.v1' || before.root !== current.root
151
+ || !before.files || typeof before.files !== 'object' || Array.isArray(before.files)
152
+ || !Number.isFinite(before.created) || typeof before.strict !== 'boolean'
153
+ || Object.keys(before.files).length > MAX_FILES
154
+ || Object.values(before.files).some(value => typeof value !== 'string' || !/^[a-f0-9]{64}$/.test(value))
155
+ || Date.now() - before.created > 24 * 60 * 60 * 1000 || before.created > Date.now()
156
+ || (turn && before.turn !== turn))
157
+ throw new Error('stale_baseline');
158
+ const files = [...new Set([...Object.keys(before.files), ...Object.keys(current.files)])]
159
+ .filter(name => before.files[name] !== current.files[name] && selected(name, config));
160
+ return { available: true, files, source: 'turn_baseline', mode: config.strict || before.strict ? 'strict' : 'advisory', reason: '' };
161
+ }
162
+ catch {
163
+ return { available: false, files: [], source: 'turn_baseline', mode: 'advisory', reason: 'baseline_unavailable; inspect changed artifacts manually' };
164
+ }
165
+ }
@@ -63,7 +63,7 @@ export declare function createVerificationClosureReceipt(input: {
63
63
  schema: "yam.verification-closure-write.v1";
64
64
  receipt_path: string;
65
65
  receipt: VerificationClosureReceipt;
66
- truth_status: "blocked" | "partial";
66
+ truth_status: "partial" | "blocked";
67
67
  next_action: string;
68
68
  }>;
69
69
  export declare function verifyVerificationClosureReceipt(value: unknown): {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yam-flow",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "description": "Progressive proof-first agent flow: start fast, deepen deliberately, stay honest by design.",
5
5
  "type": "module",
6
6
  "author": "0kim0bos",
@@ -60,7 +60,7 @@
60
60
  "doctor": "node ./dist/bin/yam.js doctor",
61
61
  "list": "node ./dist/bin/yam.js list",
62
62
  "examples": "node ./dist/bin/yam.js examples",
63
- "verify:self": "npm run build && node --check ./dist/bin/yam.js && node --check ./dist/lib/trust-kernel.js && node --check ./dist/lib/skill-installation.js && node --check ./dist/lib/external-updates.js && node --check ./dist/lib/next-step.js && node --check ./dist/lib/scout-receipt.js && node --check ./dist/lib/release-registry-status.js && node --check ./dist/lib/release-auth-readiness.js && node --check ./dist/lib/gate-result.js && node --check ./dist/lib/benchmark-promotion.js && node --check ./dist/lib/design-production.js && node --check ./dist/lib/verification-closure.js && node --check ./dist/lib/media-provider-receipt.js && npm run security-smoke && npm run security-integration-smoke && npm run hook-latency-smoke && npm run next-step-smoke && npm run scout-receipt-smoke && npm run release:registry-parser:smoke && npm run release:auth-smoke && node ./scripts/external-updates-smoke.mjs && node ./scripts/install-transaction-smoke.mjs && node ./scripts/trust-adoptions-smoke.mjs && node ./scripts/design-production-smoke.mjs && node ./scripts/phase-adoptions-smoke.mjs && npm run package-secrets:check && npm run package-secrets-smoke && node ./dist/bin/yam.js verify && node ./dist/bin/yam.js doctor",
63
+ "verify:self": "npm run build && node --check ./dist/bin/yam.js && node --check ./dist/lib/trust-kernel.js && node --check ./dist/lib/skill-installation.js && node --check ./dist/lib/external-updates.js && node --check ./dist/lib/hook-readiness.js && node --check ./dist/lib/memory-records.js && node --check ./dist/lib/next-step.js && node --check ./dist/lib/scout-receipt.js && node --check ./dist/lib/release-registry-status.js && node --check ./dist/lib/release-auth-readiness.js && node --check ./dist/lib/gate-result.js && node --check ./dist/lib/benchmark-promotion.js && node --check ./dist/lib/design-production.js && node --check ./dist/lib/verification-closure.js && node --check ./dist/lib/media-provider-receipt.js && npm run security-smoke && npm run security-integration-smoke && npm run hook-latency-smoke && npm run hook-readiness-smoke && npm run next-step-smoke && npm run scout-receipt-smoke && npm run release:registry-parser:smoke && npm run release:auth-smoke && node ./scripts/external-updates-smoke.mjs && node ./scripts/install-transaction-smoke.mjs && node ./scripts/trust-adoptions-smoke.mjs && node ./scripts/design-production-smoke.mjs && node ./scripts/phase-adoptions-smoke.mjs && npm run skill-content:check && npm run skill-content-smoke && npm run memory-completeness-smoke && npm run package-secrets:check && npm run package-secrets-smoke && node ./dist/bin/yam.js verify && node ./dist/bin/yam.js doctor",
64
64
  "prepare": "npm run build",
65
65
  "prepack": "npm run verify:self",
66
66
  "prepublishOnly": "npm run verify:self",
@@ -69,9 +69,14 @@
69
69
  "forbidden-names:check": "node ./scripts/check-forbidden-names.mjs",
70
70
  "workflow-pins:check": "node ./scripts/check-workflow-pins.mjs",
71
71
  "study-note:check": "node ./dist/bin/yam.js study-note check --json",
72
+ "study-note-turn-smoke": "node ./scripts/study-note-turn-smoke.mjs",
72
73
  "package-boundary:check": "node ./scripts/check-package-boundary.mjs",
73
74
  "package-secrets:check": "node ./scripts/check-package-secrets.mjs",
74
75
  "package-secrets-smoke": "node ./scripts/package-secrets-smoke.mjs",
76
+ "skill-content:check": "node ./scripts/check-skill-content.mjs",
77
+ "skill-content-smoke": "node ./scripts/skill-content-smoke.mjs",
78
+ "memory-completeness-smoke": "node ./scripts/memory-completeness-smoke.mjs",
79
+ "hook-readiness-smoke": "node ./scripts/hook-readiness-smoke.mjs",
75
80
  "dist:freshness": "node ./scripts/check-dist-freshness.mjs",
76
81
  "registry:check": "node ./scripts/check-registry-version.mjs",
77
82
  "registry:check:smoke": "node ./scripts/check-registry-version.mjs --self-test",
@@ -82,7 +87,7 @@
82
87
  "trust-adoptions-smoke": "npm run build && node ./scripts/trust-adoptions-smoke.mjs",
83
88
  "design-production-smoke": "npm run build && node ./scripts/design-production-smoke.mjs",
84
89
  "phase-adoptions-smoke": "npm run build && node ./scripts/phase-adoptions-smoke.mjs",
85
- "hook-latency-smoke": "node ./scripts/hook-latency-smoke.mjs",
90
+ "hook-latency-smoke": "node ./scripts/hook-latency-smoke.mjs && node ./scripts/study-note-turn-smoke.mjs",
86
91
  "install-transaction-smoke": "npm run build && node ./scripts/install-transaction-smoke.mjs",
87
92
  "next-step-smoke": "node ./scripts/next-step-smoke.mjs",
88
93
  "scout-receipt-smoke": "node ./scripts/scout-receipt-smoke.mjs",
@@ -40,6 +40,7 @@ Use it only for known wrong decisions, repeated mistakes, direction changes, and
40
40
 
41
41
  Rules:
42
42
 
43
+ - Work on the current branch by default. Creating/switching branches, creating worktrees and creating PRs each need an explicit user request or applicable project rule. Commit/push, release bumps and a `codex/` naming convention do not authorize these actions. Branch protection specifies acceptance conditions, not a mandate to create a branch; when blocked, report and ask instead of changing workflow or protections. Preserve existing work; if detached HEAD prevents a requested commit, clarify the target before changing it.
43
44
  - Do not regenerate a broad plan when `yam.project.md` already answers the direction question.
44
45
  - Do not reread architecture docs unless the task touches architecture.
45
46
  - Do not reread full design docs for tiny UI work when the pack has the relevant style direction.
@@ -21,7 +21,7 @@ Always keep final reports short. Include these when they apply:
21
21
  - Publish blocker evidence when npm/release work fails.
22
22
  - Structured diagnostic next action, when a check found a concrete next step.
23
23
  - Study Note whenever code, config, release metadata, documentation, or project artifacts changed, even if no yam skill was explicitly invoked. This item is required for changed artifacts, not optional.
24
- - Next step immediately after Study Note for changed-artifact reports. It must come from a quick whole-process scan and include ordered fix-first and planned actions, not only leftovers.
24
+ - Next step immediately after Study Note for changed-artifact reports. Ordinary advisory work may use one concrete action or no remaining work; strict reports use the whole-process scan and ordered fix-first/planned contract.
25
25
  - Remaining tasks.
26
26
  - Fix-first items before planned tasks.
27
27
  - Recommended direction and why that next step matters when the handoff would otherwise be ambiguous.
@@ -90,6 +90,8 @@ Include:
90
90
  - One ordered sequence with all fix-first items before planned work.
91
91
  - Why each action matters, owner route and scope, blockers and safe retry, evidence level and stamp, expected side effects, and truth status.
92
92
 
93
+ These detailed fields are for strict reports and structured receipts. Ordinary advisory work can summarize the next action (or none) without mandatory labels or numbered steps.
94
+
93
95
  This is broader than `Remaining Tasks`: it may recommend the next useful work after the current scope is complete. Do not invent filler when nothing meaningful remains. Use `references/next-step.md`; reusable JSON receipts use `yam.next-step.v1`.
94
96
 
95
97
  ## Handoff Direction
@@ -8,6 +8,8 @@ When code, config, documentation, release metadata, or another project artifact
8
8
 
9
9
  ## Whole-process scan
10
10
 
11
+ For ordinary advisory hook work, keep Next step to a concrete remaining action or “no remaining work”; numbered ownership/safety/truth fields are not mandatory. The detailed contract below applies to strict report validation and structured receipts. Do not manufacture a task just to satisfy a report shape.
12
+
11
13
  Before writing the sequence, quickly re-check:
12
14
 
13
15
  - the original goal and acceptance criteria;
@@ -13,6 +13,8 @@ Assess security applicability during new development and changes. Before commit,
13
13
 
14
14
  Commit, push, and deployment are distinct actions. A remote CI failure generally blocks protected-branch merge or deployment; it does not mean the preceding Git push was rejected. Local hooks can be bypassed or absent. Do not claim that `--no-verify` is automatically observable; future CI must independently check incoming revisions. Direct deployments must use the same required gate or access restrictions, otherwise enforcement remains incomplete.
15
15
 
16
+ Branch protection is an acceptance boundary, not authorization to create a branch, worktree or PR. Keep the current branch unless the user or an explicit applicable project rule requests a different workflow. If direct push or merge is blocked, preserve work, report the exact blocker and ask for direction. Do not silently create a PR, change protection settings or bypass the security gate.
17
+
16
18
  ## Fixed areas and concrete summaries
17
19
 
18
20
  Every run includes each ID exactly once with a concrete explanation, a status, and the applicable evidence or reason. An ID or short heading alone does not satisfy the explanation requirement. These summaries are the canonical minimum; add project-specific targets without removing their security meaning.
@@ -16,6 +16,10 @@ This is final-report writing guidance, not a new CLI JSON schema. The current CL
16
16
 
17
17
  Keep the default note short: 4-7 lines for ordinary work, longer only for `$deep`, `$mission`, release, DB, runtime, or learning-heavy work.
18
18
 
19
+ Automatic hook scope is turn-based, not the existing dirty tree. Ordinary work is advisory: summarize the touched artifact/role and change, verification/limits, then a short Next step (or no remaining work). The hook checks only the compact minimum; the fuller guidance below remains useful when relevant. Deep/Mission names alone do not activate strict. Risk intent or explicit/project strict policy requires the detailed contract only if selected artifacts changed. Explicit `yam study-note check` retains strict whole-tree validation.
20
+
21
+ Known generated mission/security receipts, logs and screenshots are not product changes by default; `.yam/study-note.json` includes override excludes. Security evidence is still required by the independent stage security gate. Unavailable or expired baseline means partial/manual inspection, not no changes. Plain, Markdown heading and standalone bold Study Note / Next step titles are accepted.
22
+
19
23
  Include:
20
24
 
21
25
  - Touched code/artifact: the file, function, component, config, or artifact that changed.
@@ -18,6 +18,7 @@ Use for:
18
18
 
19
19
  ## Principles
20
20
 
21
+ - Keep the current branch. Create/switch branches, create worktrees or create PRs only on explicit user request or an applicable project rule; commit/push, version bumps, route selection and protection alone do not authorize them. If blocked, report and ask instead of silently changing workflow, protections or bypassing checks.
21
22
  - Direction before execution.
22
23
  - Token economy still matters, even in deep mode.
23
24
  - Reuse `yam.project.md` before broad context reading when present.
@@ -24,6 +24,7 @@ Do not use for:
24
24
 
25
25
  ## Principles
26
26
 
27
+ - Keep the current branch. Create/switch branches, create worktrees or create PRs only on explicit user request or an applicable project rule; commit/push, version bumps, route selection and protection alone do not authorize them. If blocked, report and ask instead of silently changing workflow, protections or bypassing checks.
27
28
  - Direction before execution.
28
29
  - Mission is explicit-only; never auto-escalate small tasks into mission.
29
30
  - Start from the user's approved plan or ask for one if the plan is missing.
@@ -17,6 +17,7 @@ Do not use for:
17
17
  - Risky verification or broad debugging. Use `$deep`.
18
18
 
19
19
  Principles:
20
+ - Keep the current branch. Create/switch branches, create worktrees or create PRs only on explicit user request or an applicable project rule; commit/push, version bumps, route selection and protection alone do not authorize them. If blocked, report and ask instead of silently changing workflow, protections or bypassing checks.
20
21
  - Direction before execution.
21
22
  - Answer the question first.
22
23
  - Keep context tiny.
@@ -22,6 +22,7 @@ Do not use for:
22
22
 
23
23
  ## Principles
24
24
 
25
+ - Keep the current branch. Create/switch branches, create worktrees or create PRs only on explicit user request or an applicable project rule; commit/push, version bumps, route selection and protection alone do not authorize them. If blocked, report and ask instead of silently changing workflow, protections or bypassing checks.
25
26
  - Direction before execution.
26
27
  - Context-reuse first.
27
28
  - Token economy is part of quality.
@@ -84,7 +85,7 @@ Keep it compact:
84
85
 
85
86
  - What changed or what the scan found.
86
87
  - Study Note when code, config, docs, or project artifacts changed: what changed, what role it has, where it runs or is read, what was wrong or missing, before/after behavior, what behavior is expected, one useful syntax/structure insight, what was verified, and what remains uncertain.
87
- - Next step immediately after Study Note when artifacts changed; use `references/next-step.md` to record the whole-process scan, critical opinion, recommendations, and ordered fix-first then planned actions.
88
+ - Next step immediately after Study Note when artifacts changed; ordinary advisory work uses one concrete action or no remaining work. Use the detailed `references/next-step.md` contract only for risky/strict reports or structured receipts.
88
89
  - Architecture hygiene when relevant: flag if the change risks dumping unrelated logic into `page.tsx`, one-off component CSS into `global.css`, or structured product data into broad DB `jsonb` blobs.
89
90
  - What was checked.
90
91
  - What was skipped, blocked, or still risky.
@@ -22,6 +22,7 @@ Use for:
22
22
 
23
23
  ## Principles
24
24
 
25
+ - Keep the current branch. Create/switch branches, create worktrees or create PRs only on explicit user request or an applicable project rule; commit/push, version bumps, route selection and protection alone do not authorize them. If blocked, report and ask instead of silently changing workflow, protections or bypassing checks.
25
26
  - Scout broadly enough to see the field, then compress the judgment.
26
27
  - Token economy is part of quality.
27
28
  - Reuse `yam.project.md` before broad context reading when present.
@@ -22,6 +22,7 @@ Do not use for:
22
22
 
23
23
  ## Principles
24
24
 
25
+ - Keep the current branch. Create/switch branches, create worktrees or create PRs only on explicit user request or an applicable project rule; commit/push, version bumps, route selection and protection alone do not authorize them. If blocked, report and ask instead of silently changing workflow, protections or bypassing checks.
25
26
  - Direction before execution.
26
27
  - Visual evidence before visual claims.
27
28
  - Ueye is the tight UX/UI/design route, not Quick with screenshots.