@readystack/romu-hozon-kikan-lint-jp 1.0.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/LICENSE.txt +15 -0
- package/README.md +54 -0
- package/cli.js +161 -0
- package/engine.js +63 -0
- package/license.js +83 -0
- package/package.json +42 -0
- package/rules.json +232 -0
- package/strings.json +10 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
労務書類 保存期間チェック(法定帳簿の保存年数・起算日) — Licence
|
|
2
|
+
|
|
3
|
+
Free scope
|
|
4
|
+
開いている保存期間表1枚を検査し、法定年数より短い行・起算日の誤り・早すぎる廃棄予定日を行番号つきで示します(キー不要)。 is free for any use, personal or commercial, for as long as you keep the
|
|
5
|
+
extension installed, and the online check at the product page is free without registration.
|
|
6
|
+
|
|
7
|
+
Paid scope
|
|
8
|
+
ワークスペース内の規程・CSVを一括検査し、条文根拠と罰則つきの監査用レポートをMarkdown/CSVで書き出します。 is the paid part. It asks for a licence key issued at purchase (one seat, one key). The key is checked against the payment provider and
|
|
9
|
+
cached locally for 30 days so it keeps working offline.
|
|
10
|
+
|
|
11
|
+
Redistribution
|
|
12
|
+
You may not resell, re-host or bundle this extension or its rule set. The rule set and the
|
|
13
|
+
engine are provided as-is; check results are advisory and do not constitute legal advice.
|
|
14
|
+
|
|
15
|
+
(c) ReadyStack — https://getreadystack.com
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 労務書類 保存期間チェック
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
法定帳簿の保存期間を文書保存期間表で検査 — 労基法109条・安衛則51条・石綿則41条・雇用保険法施行規則143条・健保則34条(20 rules)
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npx @readystack/romu-hozon-kikan-lint-jp file
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Node 18+. The same 20 rules as the VS Code extension, from a terminal or CI.
|
|
14
|
+
|
|
15
|
+
## Free
|
|
16
|
+
|
|
17
|
+
- 開いている保存期間表1枚を検査し、法定年数より短い行・起算日の誤り・早すぎる廃棄予定日を行番号と条文つきで示す(キー不要)。
|
|
18
|
+
- `--rules` lists every rule
|
|
19
|
+
|
|
20
|
+
## With a licence ($29 once)
|
|
21
|
+
|
|
22
|
+
- ワークスペース内の規程・CSVを一括検査し、条文根拠と罰則つきの監査用レポートをMarkdown/CSVで書き出す。
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
@readystack/romu-hozon-kikan-lint-jp --dir ./templates --report html --out report.html
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
社労士の顧問契約は小規模事業所でも月額¥20,000前後が一般的な相場。
|
|
29
|
+
|
|
30
|
+
## Use from an AI agent (MCP)
|
|
31
|
+
|
|
32
|
+
Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "mcpServers": { "romu-hozon-kikan-lint-jp": { "command": "npx", "args": ["-y", "@readystack/romu-hozon-kikan-lint-jp", "--mcp"] } } }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Tools: `check_text` and `check_file` (free) · `check_dir` (licence). The agent gets every finding with the line number.
|
|
39
|
+
|
|
40
|
+
## Use in CI
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
- name: 労務書類 保存期間チェック
|
|
44
|
+
run: npx -y @readystack/romu-hozon-kikan-lint-jp --dir . --ci
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
(container: `docker run --rm -v "$PWD:/work" getreadystack/romu-hozon-kikan-lint-jp --dir /work --ci`)
|
|
48
|
+
|
|
49
|
+
The folder sweep, reports and CI mode need one licence — one payment, no subscription. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
|
|
50
|
+
|
|
51
|
+
[Get a licence](https://buy.polar.sh/polar_cl_mp6bP7Gyw9i49qH5jlbJODra5FcgmdOi3T4eh0lWeGK)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
<!-- romu hozon kikan lint jp -->
|
package/cli.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cli.js — the same rules as the VS Code extension, run from a terminal or a container. Generated by adapters.py.
|
|
3
|
+
'use strict';
|
|
4
|
+
const fs = require('fs'), path = require('path');
|
|
5
|
+
const RULES = require('./rules.json');
|
|
6
|
+
const S = require('./strings.json');
|
|
7
|
+
const lic = require('./license.js');
|
|
8
|
+
const ENGINE = require('./engine.js'); // s144 — scaffold products: the same engine the VS Code extension runs
|
|
9
|
+
const RULE_LIST = Array.isArray(ENGINE.RULES) ? ENGINE.RULES : (Array.isArray(RULES) ? RULES : []);
|
|
10
|
+
const RULE_N = ENGINE.RULE_COUNT || RULE_LIST.length;
|
|
11
|
+
function scan(text, file) {
|
|
12
|
+
const r = ENGINE.engine.check(String(text), { today: new Date().toISOString().slice(0, 10), path: file || '' });
|
|
13
|
+
return (r && r.findings || []).map((f) => ({ line: parseInt(f.line, 10) || 1, msg: String(f.msg || f.message || f.check || ''), fix: f.fix || null, sev: (String(f.sev || 'error').toLowerCase().startsWith('err') ? 'error' : 'warn') }));
|
|
14
|
+
}
|
|
15
|
+
function isText(p) { try { const b = fs.readFileSync(p); if (b.length > 2 * 1024 * 1024) return false; const s = b.subarray(0, 4096); for (const x of s) if (x === 0) return false; return true; } catch (e) { return false; } }
|
|
16
|
+
function walk(dir, exts, out) {
|
|
17
|
+
let ents = []; try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { return out; }
|
|
18
|
+
for (const e of ents) {
|
|
19
|
+
if (['node_modules', '.git', '.hg', '.svn', '.venv', 'venv', '.tox', '.cache', '.next', '.nuxt', '.terraform', '__pycache__'].includes(e.name)) continue; // s158 — .github · .gitlab-ci.yml · .env · .well-known 은 본다 (옛 줄은 점으로 시작하는 것을 전부 건너뛰어 워크플로 검사기가 .github/workflows 를 못 봤다)
|
|
20
|
+
const p = path.join(dir, e.name);
|
|
21
|
+
if (e.isDirectory()) walk(p, exts, out);
|
|
22
|
+
else if ((!exts.length || exts.includes(path.extname(e.name).toLowerCase())) && isText(p)) out.push(p);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); }
|
|
27
|
+
function csvq(s) { return '"' + String(s == null ? '' : s).replace(/"/g, '""') + '"'; }
|
|
28
|
+
function render(rows, fmt) {
|
|
29
|
+
if (fmt === 'json') return JSON.stringify({ tool: S.name, rules: RULE_N, files: rows }, null, 1);
|
|
30
|
+
if (fmt === 'csv') { const o = ['file,line,severity,message,fix']; for (const r of rows) for (const h of r.hits) o.push([csvq(r.file), h.line, h.sev, csvq(h.msg), csvq(h.fix)].join(',')); return o.join('\n') + '\n'; }
|
|
31
|
+
if (fmt === 'html') {
|
|
32
|
+
const n = rows.reduce((a, r) => a + r.hits.length, 0);
|
|
33
|
+
let o = '<!doctype html><meta charset="utf-8"><title>' + esc(S.name) + ' report</title><style>body{font:14px system-ui;margin:24px}table{border-collapse:collapse}td,th{border:1px solid #ddd;padding:4px 8px;font-size:13px}code{font-family:ui-monospace,monospace}</style>';
|
|
34
|
+
o += '<h1>' + esc(S.name) + '</h1><p>' + rows.length + ' files · ' + n + ' findings · ' + RULE_N + ' rules</p><table><tr><th>file</th><th>line</th><th>sev</th><th>message</th><th>fix</th></tr>';
|
|
35
|
+
for (const r of rows) for (const h of r.hits) o += '<tr><td><code>' + esc(r.file) + '</code></td><td>' + h.line + '</td><td>' + esc(h.sev) + '</td><td>' + esc(h.msg) + '</td><td>' + esc(h.fix || '') + '</td></tr>';
|
|
36
|
+
return o + '</table>';
|
|
37
|
+
}
|
|
38
|
+
let o = ''; for (const r of rows) { o += r.file + '\n'; for (const h of r.hits) o += ' ' + String(h.line).padStart(5) + ' ' + h.sev.padEnd(5) + ' ' + h.msg + (h.fix ? '\n -> ' + h.fix : '') + '\n'; if (!r.hits.length) o += ' (no findings)\n'; }
|
|
39
|
+
return o;
|
|
40
|
+
}
|
|
41
|
+
function trialState() {
|
|
42
|
+
// s158 — ⚑7일 무료 없음: 새 체험은 ⛔열지 않는다 · 이미 시작된 체험(trial.json)만 끝까지 지킨다 (읽기 전용)
|
|
43
|
+
if (process.env.READYSTACK_NO_TRIAL) return { active: false };
|
|
44
|
+
try {
|
|
45
|
+
const p = path.join(path.dirname(lic.storePath()), S.bin + '.trial.json');
|
|
46
|
+
const t = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
47
|
+
return { active: !!(t && t.until) && new Date(t.until + 'T23:59:59Z').getTime() > Date.now(), until: t && t.until };
|
|
48
|
+
} catch (e) { return { active: false }; }
|
|
49
|
+
}
|
|
50
|
+
// ★s158 2026-09-23 — the free run SPEAKS about the rest of the folder (vsix: auto.js · crx: welcome tab).
|
|
51
|
+
// The answer for the named files is complete and free; the next step is shown with the user's OWN numbers:
|
|
52
|
+
// "this folder has N more matching files - M issues in K of them" + the one command that sweeps them.
|
|
53
|
+
// Never blocks, never starts a trial (s158: no free trial - the free answer on the named files is the "try it"), human seat only, 400 files / 1.5 s cap.
|
|
54
|
+
function folderHint(done) {
|
|
55
|
+
if (!process.stderr.isTTY || process.env.CI || process.env.READYSTACK_NO_HINT || !done.length) return;
|
|
56
|
+
const base = path.dirname(path.resolve(done[0]));
|
|
57
|
+
const seen = new Set(done.map((f) => path.resolve(f)));
|
|
58
|
+
const all = walk(base, S.exts || [], []).filter((f) => !seen.has(path.resolve(f)));
|
|
59
|
+
if (!all.length) return;
|
|
60
|
+
const t0 = Date.now(); let issues = 0, hitFiles = 0, scanned = 0;
|
|
61
|
+
for (const f of all.slice(0, 400)) { if (Date.now() - t0 > 1500) break; let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { continue; } scanned++; const h = scan(t, f); if (h.length) { issues += h.length; hitFiles++; } }
|
|
62
|
+
const rel = path.relative(process.cwd(), base) || '.';
|
|
63
|
+
const tail = trialState().active ? 'free during your trial' : '$' + S.price + ' once';
|
|
64
|
+
const more = all.length + ' more matching file' + (all.length > 1 ? 's' : '');
|
|
65
|
+
const found = issues ? ' - ' + (scanned < all.length ? 'at least ' : '') + issues + ' issue' + (issues > 1 ? 's' : '') + ' in ' + hitFiles + ' of them' : ' - no issues found in them';
|
|
66
|
+
process.stderr.write('\n' + (rel === '.' ? 'This folder' : rel) + ' has ' + more + found + '.\n' + (issues ? 'Sweep them all: ' + S.bin + ' --dir ' + (/\s/.test(rel) ? JSON.stringify(rel) : rel) + ' (' + tail + ')\n' : ''));
|
|
67
|
+
}
|
|
68
|
+
let _usePinged = false;
|
|
69
|
+
function pingUse() {
|
|
70
|
+
try {
|
|
71
|
+
if (_usePinged) return; _usePinged = true;
|
|
72
|
+
if (process.env.DO_NOT_TRACK === '1' || process.env.READYSTACK_NO_TELEMETRY || process.env.CI) return;
|
|
73
|
+
if (!(process.stdout.isTTY || process.stdin.isTTY)) return; // human seat only (s152)
|
|
74
|
+
const https = require('https');
|
|
75
|
+
const body = JSON.stringify({ t: 'use', slug: S.bin, src: 'cli', why: 'free' });
|
|
76
|
+
const req = https.request({ hostname: 'getreadystack.com', path: '/api/ev', method: 'POST', timeout: 3000,
|
|
77
|
+
headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'user-agent': 'readystack-cli/' + S.bin } }, function (res) { res.resume(); });
|
|
78
|
+
req.on('timeout', function () { req.destroy(); }); req.on('error', function () {});
|
|
79
|
+
req.write(body); req.end();
|
|
80
|
+
} catch (e) {}
|
|
81
|
+
}
|
|
82
|
+
function mcpServe() {
|
|
83
|
+
process.env.READYSTACK_MCP = '1'; // s152 - 에이전트(Claude Code·Cursor)가 부른 세션은 사람 자리다 · 키 판 핑 src=mcp
|
|
84
|
+
// s144 — MCP server over stdio (newline-delimited JSON-RPC · no dependencies). Free: check_text · check_file. Licence (s158: no free trial): check_dir.
|
|
85
|
+
const rl = require('readline').createInterface({ input: process.stdin });
|
|
86
|
+
const send = (o) => process.stdout.write(JSON.stringify(o) + '\n');
|
|
87
|
+
const tools = [
|
|
88
|
+
{ name: 'check_text', description: S.name + ' - run all ' + RULE_N + ' checks on a text (free)', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'file contents' }, path: { type: 'string', description: 'optional file name for context' } }, required: ['text'] } },
|
|
89
|
+
{ name: 'check_file', description: S.name + ' - run all checks on one file by path (free)', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } },
|
|
90
|
+
{ name: 'check_dir', description: S.name + ' - sweep a folder and return every finding (licence; $' + S.price + ' once)', inputSchema: { type: 'object', properties: { dir: { type: 'string' }, ext: { type: 'string', description: 'optional extension filter, e.g. .html' } }, required: ['dir'] } }
|
|
91
|
+
];
|
|
92
|
+
const result = (id, rows) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: render(rows, 'text') }], structuredContent: { tool: S.name, rules: RULE_N, files: rows } } });
|
|
93
|
+
const fail = (id, text) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text }], isError: true } });
|
|
94
|
+
rl.on('line', async (line) => {
|
|
95
|
+
let m; try { m = JSON.parse(line); } catch (e) { return; }
|
|
96
|
+
const id = m.id, method = m.method;
|
|
97
|
+
if (method === 'initialize') return send({ jsonrpc: '2.0', id, result: { protocolVersion: '2025-06-18', capabilities: { tools: {} }, serverInfo: { name: '@readystack/' + S.bin, version: '1.0.0' } } });
|
|
98
|
+
if (method === 'notifications/initialized' || method === 'ping') { if (id !== undefined) send({ jsonrpc: '2.0', id, result: {} }); return; }
|
|
99
|
+
if (method === 'tools/list') return send({ jsonrpc: '2.0', id, result: { tools } });
|
|
100
|
+
if (method === 'tools/call') {
|
|
101
|
+
const name = (m.params || {}).name, args = (m.params || {}).arguments || {};
|
|
102
|
+
try {
|
|
103
|
+
if (name === 'check_text') return result(id, [{ file: args.path || '(text)', hits: scan(String(args.text || ''), args.path || '') }]);
|
|
104
|
+
if (name === 'check_file') return result(id, [{ file: args.path, hits: scan(fs.readFileSync(args.path, 'utf8'), args.path) }]);
|
|
105
|
+
if (name === 'check_dir') {
|
|
106
|
+
const r = await lic.ensure();
|
|
107
|
+
if (!r.ok && !trialState().active) return fail(id, S.need_key + ' Get a licence ($' + S.price + ', once): ' + lic.BUY_URL);
|
|
108
|
+
const files = walk(args.dir, args.ext ? [args.ext] : (S.exts || []), []);
|
|
109
|
+
return result(id, files.map((f) => { let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { return { file: f, hits: [], error: String(e.message) }; } return { file: f, hits: scan(t, f) }; }));
|
|
110
|
+
}
|
|
111
|
+
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'unknown tool ' + name } });
|
|
112
|
+
} catch (e) { return fail(id, String(e && e.message || e)); }
|
|
113
|
+
}
|
|
114
|
+
if (id !== undefined) send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'method not found: ' + method } });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function help() {
|
|
118
|
+
return [S.name + ' - ' + S.subtitle, '', 'Usage: ' + S.bin + ' <file> [more files] check the files you name (free, every rule)',
|
|
119
|
+
' ' + S.bin + ' --dir <folder> [--ext .html] scan a whole folder (licence)',
|
|
120
|
+
' ' + S.bin + ' ... --report csv|json|html [--out file] export a report (licence)',
|
|
121
|
+
' ' + S.bin + ' ... --ci exit 1 when an error-level finding exists (licence)',
|
|
122
|
+
' ' + S.bin + ' --license <key> store your licence key (or set READYSTACK_LICENSE)',
|
|
123
|
+
' ' + S.bin + ' --rules list the ' + RULE_N + ' rules',
|
|
124
|
+
' ' + S.bin + ' --mcp run as an MCP server (stdio) for Claude Code / Cursor / Windsurf - free checks, folder sweep needs a licence', '',
|
|
125
|
+
'Free: ' + S.free, 'Licence ($' + S.price + ', once): ' + S.paid, 'Get a licence: ' + lic.BUY_URL, ''].join('\n');
|
|
126
|
+
}
|
|
127
|
+
(async function main() {
|
|
128
|
+
try { const _feed = await lic.pullFeed(); if (_feed && Array.isArray(_feed.rules)) { if (Array.isArray(ENGINE.RULES)) for (const r of _feed.rules) ENGINE.RULES.push(r); } } catch (e) {} // ★s134 구독 피드 병합 (키 있는 손님만)
|
|
129
|
+
const a = process.argv.slice(2);
|
|
130
|
+
const get = (k) => { const i = a.indexOf(k); return i >= 0 ? a[i + 1] : null; };
|
|
131
|
+
if (a.includes('--mcp')) { mcpServe(); return; }
|
|
132
|
+
if (!a.length || a.includes('--help') || a.includes('-h')) { process.stdout.write(help()); return; }
|
|
133
|
+
if (a.includes('--rules')) { process.stdout.write((RULE_LIST.length ? RULE_LIST.map((r, i) => String(i + 1).padStart(3) + ' [' + (r.sev || 'warn') + '] ' + (r.message || r.msg || r.id || '')) : Array.from({ length: RULE_N }, (_, i) => String(i + 1).padStart(3) + ' [engine] check ' + (i + 1))).join('\n') + '\n'); return; }
|
|
134
|
+
if (a.includes('--license')) { const r = await lic.ensure(get('--license')); process.stdout.write(r.ok ? 'Licence stored: ' + lic.storePath() + '\n' : 'Licence not accepted (' + r.why + '). Get one: ' + lic.BUY_URL + '\n'); process.exit(r.ok ? 0 : 2); }
|
|
135
|
+
const dir = get('--dir'), fmt = get('--report'), out = get('--out'), ci = a.includes('--ci');
|
|
136
|
+
const exts = a.includes('--ext') ? [get('--ext')] : (S.exts || []);
|
|
137
|
+
const paid = !!(dir || fmt || ci);
|
|
138
|
+
if (paid) {
|
|
139
|
+
const r = await lic.ensure();
|
|
140
|
+
if (!r.ok) {
|
|
141
|
+
const t = trialState(); // s158 — ⚑7일 무료 없음 · 이미 시작된 체험만 지킨다
|
|
142
|
+
if (t.active) process.stderr.write('Trial: the full run is free until ' + t.until + ' — after that $' + S.price + ' once. Get a licence: ' + lic.BUY_URL + '\n');
|
|
143
|
+
else {
|
|
144
|
+
// s158 — the key is asked WITH the customer's own count (endowment · open loop): how many issues this folder holds.
|
|
145
|
+
let own = '';
|
|
146
|
+
if (dir) { try { const all = walk(dir, exts, []); let n = 0, k = 0, sc = 0; const t0 = Date.now();
|
|
147
|
+
for (const f of all.slice(0, 2000)) { if (Date.now() - t0 > 4000) break; let tx = ''; try { tx = fs.readFileSync(f, 'utf8'); } catch (e) { continue; } sc++; const h = scan(tx, f); if (h.length) { n += h.length; k++; } }
|
|
148
|
+
if (n) own = dir + ': ' + (sc < all.length ? 'at least ' : '') + n + ' issue' + (n > 1 ? 's' : '') + ' in ' + k + ' of ' + all.length + ' files.\n'; } catch (e) {} }
|
|
149
|
+
process.stderr.write(own + S.need_key + '\n set READYSTACK_LICENSE=<key> or ' + S.bin + ' --license <key>\n Get a licence ($' + S.price + ' once): ' + lic.BUY_URL + '\n'); process.exit(2);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const files = dir ? walk(dir, exts, []) : a.filter((x, i) => !x.startsWith('--') && !['--dir', '--report', '--out', '--ext', '--license'].includes(a[i - 1]));
|
|
154
|
+
if (!files.length) { process.stderr.write('No files. ' + S.bin + ' --help\n'); process.exit(2); }
|
|
155
|
+
const rows = files.map((f) => { let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { return { file: f, hits: [], error: String(e.message) }; } return { file: f, hits: scan(t) }; });
|
|
156
|
+
const text = render(rows, fmt || 'text');
|
|
157
|
+
if (out) fs.writeFileSync(out, text); else process.stdout.write(text.endsWith('\n') ? text : text + '\n');
|
|
158
|
+
if (!paid) { try { folderHint(files); } catch (e) {} pingUse(); } // s158 — a free run ends with the user's own folder count + one anonymous 'used' count (human seat only)
|
|
159
|
+
const errors = rows.reduce((n, r) => n + r.hits.filter((h) => h.sev === 'error').length, 0);
|
|
160
|
+
if (ci && errors) process.exit(1);
|
|
161
|
+
})().catch((e) => { process.stderr.write(String(e && e.stack || e) + '\n'); process.exit(3); });
|
package/engine.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* 労務書類 保存期間チェック — engine (Node + browser) */
|
|
2
|
+
(function () {
|
|
3
|
+
var root = typeof window !== 'undefined' ? window : globalThis;
|
|
4
|
+
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : root.HOZON_RULES;
|
|
5
|
+
var DOCS = RULES.filter(function (r) { return r.check === 'hozon_nensu'; });
|
|
6
|
+
var KISAN = RULES.filter(function (r) { return r.check === 'kisanbi'; })[0];
|
|
7
|
+
var HAIKI = RULES.filter(function (r) { return r.check === 'haiki_yoteibi'; })[0];
|
|
8
|
+
|
|
9
|
+
function half(s) { return s.replace(/[0-9]/g, function (c) { return String.fromCharCode(c.charCodeAt(0) - 0xFEE0); }); }
|
|
10
|
+
function effective(r) { return r.transitional || r.years; }
|
|
11
|
+
function parseYears(s) {
|
|
12
|
+
if (/永年|永久/.test(s)) return Infinity;
|
|
13
|
+
var t = s.replace(/\d{4}[-\/.]\d{1,2}[-\/.]\d{1,2}/g, ' ').replace(/\d{4}年\d{1,2}月(\d{1,2}日)?/g, ' ');
|
|
14
|
+
var m = t.match(/(?:^|[^\d])(\d{1,2})\s*年(?![度\d月])/);
|
|
15
|
+
if (m) return +m[1];
|
|
16
|
+
var mo = t.match(/(?:^|[^\d])(\d{1,3})\s*(?:ヶ|か|カ|ケ|箇)月/);
|
|
17
|
+
return mo ? +mo[1] / 12 : null;
|
|
18
|
+
}
|
|
19
|
+
function addYears(d, y) { var x = new Date(d.getTime()); x.setUTCFullYear(x.getUTCFullYear() + y); return x; }
|
|
20
|
+
function iso(d) { return d.toISOString().slice(0, 10); }
|
|
21
|
+
function dates(s) {
|
|
22
|
+
var re = new RegExp(HAIKI.date_re, 'g'), out = [], m;
|
|
23
|
+
while ((m = re.exec(s))) { var d = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])); if (!isNaN(d)) out.push(d); }
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function need(r) { return r.transitional ? r.transitional + '年(原則' + r.years + '年・経過措置' + r.transitional + '年)' : r.years + '年'; }
|
|
27
|
+
function fine(r) { return r.fine ? ' 違反は' + r.fine_basis + 'で罰金' + r.fine + '以下。' : ''; }
|
|
28
|
+
|
|
29
|
+
function check(text, opts) {
|
|
30
|
+
opts = opts || {};
|
|
31
|
+
var today = new Date(String(opts.today || new Date().toISOString()).slice(0, 10) + 'T00:00:00Z');
|
|
32
|
+
if (isNaN(today)) today = new Date();
|
|
33
|
+
var lines = half(String(text || '')).split(/\r?\n/), findings = [];
|
|
34
|
+
lines.forEach(function (ln, i) {
|
|
35
|
+
var r = null;
|
|
36
|
+
for (var k = 0; k < DOCS.length; k++) { if (new RegExp(DOCS[k].match).test(ln)) { r = DOCS[k]; break; } }
|
|
37
|
+
if (!r) return;
|
|
38
|
+
var line = i + 1, y = parseYears(ln);
|
|
39
|
+
if (y !== null && y < effective(r)) {
|
|
40
|
+
findings.push({ check: 'hozon_nensu', rule: r.id, sev: 'error', line: line,
|
|
41
|
+
msg: r.doc + ': 保存期間' + (Math.round(y * 10) / 10) + '年は法定' + need(r) + 'より短い(' + r.basis + ')。起算は' + r.kisan + '。' + fine(r) });
|
|
42
|
+
}
|
|
43
|
+
if (KISAN && r.id === KISAN.applies_to && r.kisan_bad && new RegExp(r.kisan_bad).test(ln) && !/退職|死亡|解雇/.test(ln)) {
|
|
44
|
+
findings.push({ check: 'kisanbi', rule: KISAN.id, sev: 'error', line: line,
|
|
45
|
+
msg: r.doc + ': 起算日は' + r.kisan + '(労基則56条)。作成日・入社日から数えると在職中に廃棄期限が来る。' + fine(r) });
|
|
46
|
+
}
|
|
47
|
+
var ds = dates(ln);
|
|
48
|
+
if (ds.length >= 2) {
|
|
49
|
+
var end = addYears(ds[0], effective(r));
|
|
50
|
+
if (ds[1] < end) {
|
|
51
|
+
var days = Math.round((ds[1] - today) / 864e5);
|
|
52
|
+
var when = days <= 0 ? opts.today ? '基準日' + iso(today) + '時点で既に廃棄期日を' + (-days) + '日過ぎている' : '既に廃棄期日を過ぎている' : 'あと' + days + '日で早すぎる廃棄';
|
|
53
|
+
findings.push({ check: 'haiki_yoteibi', rule: HAIKI.id, sev: 'error', line: line,
|
|
54
|
+
msg: r.doc + ': 廃棄予定日' + iso(ds[1]) + 'は法定の保存満了' + iso(end) + '(起算' + iso(ds[0]) + '+' + effective(r) + '年・' + r.basis + ')より前。' + when + '。' + fine(r) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return { findings: findings };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
root.HOZONENGINE = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
|
|
62
|
+
if (typeof module !== 'undefined') module.exports = root.HOZONENGINE;
|
|
63
|
+
})();
|
package/license.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// license.js — Polar licence check for the CLI / container. Generated by adapters.py; do not edit by hand.
|
|
2
|
+
'use strict';
|
|
3
|
+
const https = require('https'), fs = require('fs'), path = require('path'), os = require('os');
|
|
4
|
+
const ORG_ID = 'a5cdf664-d8e7-4f87-8895-056717aaba17';
|
|
5
|
+
const BENEFIT_ID = 'e873c9eb-a651-432a-82d7-c163660a3b78'; // s142 — this product's Polar benefit (s140 law: ask with benefit_id, or one key opens every product)
|
|
6
|
+
const BUY_URL = 'https://buy.polar.sh/polar_cl_mp6bP7Gyw9i49qH5jlbJODra5FcgmdOi3T4eh0lWeGK';
|
|
7
|
+
const SLUG = 'romu-hozon-kikan-lint-jp';
|
|
8
|
+
const GRACE_MS = 30 * 24 * 3600 * 1000; // after a successful check, 30 days work offline
|
|
9
|
+
const RECHECK_MS = 7 * 24 * 3600 * 1000; // re-ask Polar every 7 days (refunds / cancellations)
|
|
10
|
+
function storePath() { return path.join(process.env.READYSTACK_HOME || path.join(os.homedir(), '.config', 'readystack'), SLUG + '.json'); }
|
|
11
|
+
function load() { try { return JSON.parse(fs.readFileSync(storePath(), 'utf8')); } catch (e) { return {}; } }
|
|
12
|
+
function save(o) { try { fs.mkdirSync(path.dirname(storePath()), { recursive: true }); fs.writeFileSync(storePath(), JSON.stringify(o)); } catch (e) { /* read-only home: still works for this run */ } }
|
|
13
|
+
const ALL_BENEFIT_ID = '22692551-5203-4467-b1a3-e33cdba6589d'; // s149 2026-09-17 — 팀 키(전 린터 한 키 · Polar benefit) · 상품 benefit 다음에 한 번 더 묻는다
|
|
14
|
+
function validate(key) {
|
|
15
|
+
return validate1(key, BENEFIT_ID).then(function (r) { return (r.ok || r.offline || !/^[0-9a-f-]{36}$/.test(ALL_BENEFIT_ID)) ? r : validate1(key, ALL_BENEFIT_ID); });
|
|
16
|
+
}
|
|
17
|
+
function validate1(key, ben) {
|
|
18
|
+
return new Promise(function (resolve) {
|
|
19
|
+
if (!ORG_ID) return resolve({ ok: false, offline: false });
|
|
20
|
+
const body = JSON.stringify(/^[0-9a-f-]{36}$/.test(ben) ? { key: key, organization_id: ORG_ID, benefit_id: ben } : { key: key, organization_id: ORG_ID });
|
|
21
|
+
const req = https.request({ hostname: 'api.polar.sh', path: '/v1/customer-portal/license-keys/validate', method: 'POST', timeout: 8000,
|
|
22
|
+
headers: { 'content-type': 'application/json', 'polar-version': '2026-04', 'content-length': Buffer.byteLength(body) } }, function (res) {
|
|
23
|
+
let buf = ''; res.on('data', function (d) { buf += d; });
|
|
24
|
+
res.on('end', function () {
|
|
25
|
+
if (res.statusCode !== 200) return resolve({ ok: false, offline: false });
|
|
26
|
+
try { const j = JSON.parse(buf); resolve({ ok: j && (j.status === 'granted' || j.valid === true || !!j.id), offline: false }); }
|
|
27
|
+
catch (e) { resolve({ ok: false, offline: false }); }
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
req.on('timeout', function () { req.destroy(); resolve({ ok: false, offline: true }); });
|
|
31
|
+
req.on('error', function () { resolve({ ok: false, offline: true }); });
|
|
32
|
+
req.write(body); req.end();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// ★s151 2026-09-19 — 키 판(키가 없거나 거절된 순간)을 익명으로 센다 (슬러그·출처·이유만) · DO_NOT_TRACK=1 · READYSTACK_NO_TELEMETRY 면 안 보낸다 · 실패는 조용히 · 프로세스당 한 번.
|
|
36
|
+
let _pinged = false;
|
|
37
|
+
function pingPaywall(why) {
|
|
38
|
+
try {
|
|
39
|
+
if (_pinged) return; _pinged = true;
|
|
40
|
+
if (process.env.DO_NOT_TRACK === '1' || process.env.READYSTACK_NO_TELEMETRY || process.env.CI) return; // s151: CI(깃허브 액션 등)와 우리 빌드는 손님이 아니다
|
|
41
|
+
if (!(process.stdout.isTTY || process.stdin.isTTY || process.env.READYSTACK_MCP === '1')) return; // s152: 사람 자리(터미널·MCP 세션)에서만 센다 - 발행 1분 뒤 남의 실행기(JP · 우리 3대는 US)가 돌린 no_key 13건은 손님이 아니다
|
|
42
|
+
const body = JSON.stringify({ t: 'paywall', slug: SLUG, src: process.env.READYSTACK_MCP === '1' ? 'mcp' : 'cli', why: why || 'no_key' });
|
|
43
|
+
const req = https.request({ hostname: 'getreadystack.com', path: '/api/ev', method: 'POST', timeout: 3000,
|
|
44
|
+
headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'user-agent': 'readystack-cli/' + SLUG } }, function (res) { res.resume(); });
|
|
45
|
+
req.on('timeout', function () { req.destroy(); }); req.on('error', function () {});
|
|
46
|
+
req.write(body); req.end();
|
|
47
|
+
} catch (e) { /* 세는 것이 실패해도 상품은 돈다 */ }
|
|
48
|
+
}
|
|
49
|
+
async function ensure(explicitKey) {
|
|
50
|
+
const st = load();
|
|
51
|
+
const key = explicitKey || process.env.READYSTACK_LICENSE || st.key;
|
|
52
|
+
if (!key) { pingPaywall('no_key'); return { ok: false, why: 'no_key' }; } // s151
|
|
53
|
+
const age = Date.now() - (st.okAt || 0);
|
|
54
|
+
if (!explicitKey && st.key === key && age < RECHECK_MS) return { ok: true, cached: true };
|
|
55
|
+
const r = await validate(String(key).trim());
|
|
56
|
+
if (r.ok) { save({ key: String(key).trim(), okAt: Date.now() }); return { ok: true }; }
|
|
57
|
+
if (r.offline && st.key === key && age < GRACE_MS) return { ok: true, offline: true };
|
|
58
|
+
if (!r.offline) pingPaywall('invalid'); // s151
|
|
59
|
+
return { ok: false, why: r.offline ? 'offline' : 'invalid' };
|
|
60
|
+
}
|
|
61
|
+
// ★s134 — 구독 규칙 피드(층3 "바뀌면 업데이트"): 키 있는 손님만 · 7일마다 · 오프라인은 캐시. 워커 GET /api/rules/<slug>?key=
|
|
62
|
+
const FEED_URL = 'https://getreadystack.com/api/rules/';
|
|
63
|
+
function pullFeed() {
|
|
64
|
+
const st = load(); const key = process.env.READYSTACK_LICENSE || st.key; const cached = st.feed || null;
|
|
65
|
+
if (!key) return Promise.resolve(cached);
|
|
66
|
+
if (cached && (Date.now() - (st.feedAt || 0)) < RECHECK_MS) return Promise.resolve(cached);
|
|
67
|
+
return new Promise(function (resolve) {
|
|
68
|
+
let req;
|
|
69
|
+
try {
|
|
70
|
+
req = https.get(FEED_URL + encodeURIComponent(SLUG) + '?key=' + encodeURIComponent(key), { timeout: 8000, headers: { 'user-agent': 'readystack-cli' } }, function (res) {
|
|
71
|
+
let buf = ''; res.on('data', function (d) { buf += d; });
|
|
72
|
+
res.on('end', function () {
|
|
73
|
+
if (res.statusCode !== 200) return resolve(cached);
|
|
74
|
+
try { const j = JSON.parse(buf); if (!j || !Array.isArray(j.rules)) return resolve(cached); save(Object.assign(load(), { feed: j, feedAt: Date.now() })); resolve(j); }
|
|
75
|
+
catch (e) { resolve(cached); }
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
} catch (e) { return resolve(cached); }
|
|
79
|
+
req.on('timeout', function () { req.destroy(); resolve(cached); });
|
|
80
|
+
req.on('error', function () { resolve(cached); });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
module.exports = { ensure, BUY_URL, storePath, pullFeed };
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@readystack/romu-hozon-kikan-lint-jp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "法定帳簿の保存期間を文書保存期間表で検査 — 労基法109条・安衛則51条・石綿則41条・雇用保険法施行規則143条・健保則34条(20 rules)",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"法定帳簿 保存期間",
|
|
11
|
+
"保存期間 一覧",
|
|
12
|
+
"賃金台帳 保存期間",
|
|
13
|
+
"労働者名簿",
|
|
14
|
+
"文書管理規程",
|
|
15
|
+
"健康診断 保存",
|
|
16
|
+
"雇用保険 書類 保存",
|
|
17
|
+
"社労士"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://getreadystack.com",
|
|
20
|
+
"funding": "https://buy.polar.sh/polar_cl_mp6bP7Gyw9i49qH5jlbJODra5FcgmdOi3T4eh0lWeGK",
|
|
21
|
+
"bin": {
|
|
22
|
+
"romu-hozon-kikan-lint-jp": "cli.js"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"mcpName": "io.github.jmshinhwa/romu-hozon-kikan-lint-jp",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/jmshinhwa/readystack-themes.git",
|
|
31
|
+
"directory": "romu-hozon-kikan-lint-jp"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"cli.js",
|
|
35
|
+
"license.js",
|
|
36
|
+
"rules.json",
|
|
37
|
+
"strings.json",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE.txt",
|
|
40
|
+
"engine.js"
|
|
41
|
+
]
|
|
42
|
+
}
|
package/rules.json
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "ishiwata_kenshin",
|
|
4
|
+
"check": "hozon_nensu",
|
|
5
|
+
"doc": "石綿健康診断個人票",
|
|
6
|
+
"match": "石綿.*(健康診断|健診)",
|
|
7
|
+
"years": 40,
|
|
8
|
+
"transitional": null,
|
|
9
|
+
"basis": "石綿則41条(安衛法103条)",
|
|
10
|
+
"fine": "¥500,000",
|
|
11
|
+
"fine_basis": "安衛法120条",
|
|
12
|
+
"kisan": "当該労働者が常時従事しなくなった日"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "tokkan_kenshin",
|
|
16
|
+
"check": "hozon_nensu",
|
|
17
|
+
"doc": "特別管理物質の健康診断個人票",
|
|
18
|
+
"match": "特別管理物質",
|
|
19
|
+
"years": 30,
|
|
20
|
+
"transitional": null,
|
|
21
|
+
"basis": "特化則40条2項(安衛法103条)",
|
|
22
|
+
"fine": "¥500,000",
|
|
23
|
+
"fine_basis": "安衛法120条",
|
|
24
|
+
"kisan": "作成日"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "denri_kenshin",
|
|
28
|
+
"check": "hozon_nensu",
|
|
29
|
+
"doc": "電離放射線健康診断個人票",
|
|
30
|
+
"match": "電離放射線.*(健康診断|健診)",
|
|
31
|
+
"years": 30,
|
|
32
|
+
"transitional": null,
|
|
33
|
+
"basis": "電離則57条(安衛法103条)",
|
|
34
|
+
"fine": "¥500,000",
|
|
35
|
+
"fine_basis": "安衛法120条",
|
|
36
|
+
"kisan": "作成日"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "jinpai",
|
|
40
|
+
"check": "hozon_nensu",
|
|
41
|
+
"doc": "じん肺健康診断の記録・エックス線写真",
|
|
42
|
+
"match": "じん肺",
|
|
43
|
+
"years": 7,
|
|
44
|
+
"transitional": null,
|
|
45
|
+
"basis": "じん肺法17条",
|
|
46
|
+
"fine": "",
|
|
47
|
+
"fine_basis": "",
|
|
48
|
+
"kisan": "作成日"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "ippan_kenshin",
|
|
52
|
+
"check": "hozon_nensu",
|
|
53
|
+
"doc": "健康診断個人票(一般健診)",
|
|
54
|
+
"match": "健康診断個人票|一般健康診断|健康診断結果|健診結果",
|
|
55
|
+
"years": 5,
|
|
56
|
+
"transitional": null,
|
|
57
|
+
"basis": "安衛則51条(安衛法103条)",
|
|
58
|
+
"fine": "¥500,000",
|
|
59
|
+
"fine_basis": "安衛法120条",
|
|
60
|
+
"kisan": "作成日"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "stress_check",
|
|
64
|
+
"check": "hozon_nensu",
|
|
65
|
+
"doc": "ストレスチェック結果の記録",
|
|
66
|
+
"match": "ストレスチェック",
|
|
67
|
+
"years": 5,
|
|
68
|
+
"transitional": null,
|
|
69
|
+
"basis": "安衛則52条の13",
|
|
70
|
+
"fine": "¥500,000",
|
|
71
|
+
"fine_basis": "安衛法120条",
|
|
72
|
+
"kisan": "作成日"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "mensetsu_shido",
|
|
76
|
+
"check": "hozon_nensu",
|
|
77
|
+
"doc": "面接指導結果の記録",
|
|
78
|
+
"match": "面接指導",
|
|
79
|
+
"years": 5,
|
|
80
|
+
"transitional": null,
|
|
81
|
+
"basis": "安衛則52条の6",
|
|
82
|
+
"fine": "¥500,000",
|
|
83
|
+
"fine_basis": "安衛法120条",
|
|
84
|
+
"kisan": "作成日"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "rodo_jikan_jokyo",
|
|
88
|
+
"check": "hozon_nensu",
|
|
89
|
+
"doc": "労働時間の状況の記録(安衛法)",
|
|
90
|
+
"match": "労働時間の状況",
|
|
91
|
+
"years": 3,
|
|
92
|
+
"transitional": null,
|
|
93
|
+
"basis": "安衛則52条の7の3",
|
|
94
|
+
"fine": "",
|
|
95
|
+
"fine_basis": "",
|
|
96
|
+
"kisan": "作成日"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "eisei_iinkai",
|
|
100
|
+
"check": "hozon_nensu",
|
|
101
|
+
"doc": "安全・衛生委員会の議事録",
|
|
102
|
+
"match": "(安全|衛生)委員会",
|
|
103
|
+
"years": 3,
|
|
104
|
+
"transitional": null,
|
|
105
|
+
"basis": "安衛則23条4項",
|
|
106
|
+
"fine": "",
|
|
107
|
+
"fine_basis": "",
|
|
108
|
+
"kisan": "開催日"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "nenkyu_kanribo",
|
|
112
|
+
"check": "hozon_nensu",
|
|
113
|
+
"doc": "年次有給休暇管理簿",
|
|
114
|
+
"match": "年次有給休暇管理簿|年休管理簿|有給休暇管理簿",
|
|
115
|
+
"years": 5,
|
|
116
|
+
"transitional": 3,
|
|
117
|
+
"basis": "労基則24条の7・72条(経過措置3年)",
|
|
118
|
+
"fine": "",
|
|
119
|
+
"fine_basis": "",
|
|
120
|
+
"kisan": "基準日から1年の期間満了日"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "rodosha_meibo",
|
|
124
|
+
"check": "hozon_nensu",
|
|
125
|
+
"doc": "労働者名簿",
|
|
126
|
+
"match": "労働者名簿",
|
|
127
|
+
"years": 5,
|
|
128
|
+
"transitional": 3,
|
|
129
|
+
"basis": "労基法109条・143条(経過措置3年)",
|
|
130
|
+
"fine": "¥300,000",
|
|
131
|
+
"fine_basis": "労基法120条",
|
|
132
|
+
"kisan": "退職・解雇・死亡の日",
|
|
133
|
+
"kisan_bad": "作成|入社|雇入|採用"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "chingin_daicho",
|
|
137
|
+
"check": "hozon_nensu",
|
|
138
|
+
"doc": "賃金台帳",
|
|
139
|
+
"match": "賃金台帳",
|
|
140
|
+
"years": 5,
|
|
141
|
+
"transitional": 3,
|
|
142
|
+
"basis": "労基法109条・143条(経過措置3年)",
|
|
143
|
+
"fine": "¥300,000",
|
|
144
|
+
"fine_basis": "労基法120条",
|
|
145
|
+
"kisan": "最後の記入をした日"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "shukkinbo",
|
|
149
|
+
"check": "hozon_nensu",
|
|
150
|
+
"doc": "出勤簿・タイムカード",
|
|
151
|
+
"match": "出勤簿|タイムカード|タイムシート|勤怠記録",
|
|
152
|
+
"years": 5,
|
|
153
|
+
"transitional": 3,
|
|
154
|
+
"basis": "労基法109条・143条(経過措置3年)",
|
|
155
|
+
"fine": "¥300,000",
|
|
156
|
+
"fine_basis": "労基法120条",
|
|
157
|
+
"kisan": "最後の記入をした日"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"id": "koyo_juyo",
|
|
161
|
+
"check": "hozon_nensu",
|
|
162
|
+
"doc": "雇入れ・解雇・退職に関する書類",
|
|
163
|
+
"match": "雇入れ|雇用契約書|労働条件通知書|解雇|退職届|退職に関する",
|
|
164
|
+
"years": 5,
|
|
165
|
+
"transitional": 3,
|
|
166
|
+
"basis": "労基法109条・143条(経過措置3年)",
|
|
167
|
+
"fine": "¥300,000",
|
|
168
|
+
"fine_basis": "労基法120条",
|
|
169
|
+
"kisan": "労働者の退職・死亡の日"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "koyo_hihokensha",
|
|
173
|
+
"check": "hozon_nensu",
|
|
174
|
+
"doc": "雇用保険の被保険者に関する書類",
|
|
175
|
+
"match": "雇用保険.*被保険者",
|
|
176
|
+
"years": 4,
|
|
177
|
+
"transitional": null,
|
|
178
|
+
"basis": "雇用保険法施行規則143条",
|
|
179
|
+
"fine": "",
|
|
180
|
+
"fine_basis": "",
|
|
181
|
+
"kisan": "完結の日"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"id": "koyo_hoken",
|
|
185
|
+
"check": "hozon_nensu",
|
|
186
|
+
"doc": "雇用保険に関する書類",
|
|
187
|
+
"match": "雇用保険",
|
|
188
|
+
"years": 2,
|
|
189
|
+
"transitional": null,
|
|
190
|
+
"basis": "雇用保険法施行規則143条",
|
|
191
|
+
"fine": "",
|
|
192
|
+
"fine_basis": "",
|
|
193
|
+
"kisan": "完結の日"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"id": "rosai_hoken",
|
|
197
|
+
"check": "hozon_nensu",
|
|
198
|
+
"doc": "労災保険・労働保険料に関する書類",
|
|
199
|
+
"match": "労災保険|労働保険",
|
|
200
|
+
"years": 3,
|
|
201
|
+
"transitional": null,
|
|
202
|
+
"basis": "労災則51条・徴収則70条",
|
|
203
|
+
"fine": "",
|
|
204
|
+
"fine_basis": "",
|
|
205
|
+
"kisan": "完結の日"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"id": "shaho",
|
|
209
|
+
"check": "hozon_nensu",
|
|
210
|
+
"doc": "健康保険・厚生年金保険に関する書類",
|
|
211
|
+
"match": "健康保険|厚生年金",
|
|
212
|
+
"years": 2,
|
|
213
|
+
"transitional": null,
|
|
214
|
+
"basis": "健保則34条・厚年則28条",
|
|
215
|
+
"fine": "",
|
|
216
|
+
"fine_basis": "",
|
|
217
|
+
"kisan": "完結の日"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"id": "kisan_meibo",
|
|
221
|
+
"check": "kisanbi",
|
|
222
|
+
"doc": "労働者名簿の起算日",
|
|
223
|
+
"applies_to": "rodosha_meibo"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"id": "haiki_hayasugi",
|
|
227
|
+
"check": "haiki_yoteibi",
|
|
228
|
+
"doc": "廃棄予定日が法定期間より前",
|
|
229
|
+
"date_re": "(\\d{4})[-/.](\\d{1,2})[-/.](\\d{1,2})",
|
|
230
|
+
"note": "行の1つ目の日付=起算日、2つ目=廃棄予定日"
|
|
231
|
+
}
|
|
232
|
+
]
|
package/strings.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "労務書類 保存期間チェック",
|
|
3
|
+
"subtitle": "法定帳簿の保存期間を文書保存期間表で検査 — 労基法109条・安衛則51条・石綿則41条・雇用保険法施行規則143条・健保則34条(20 rules)",
|
|
4
|
+
"bin": "romu-hozon-kikan-lint-jp",
|
|
5
|
+
"price": 29,
|
|
6
|
+
"free": "開いている保存期間表1枚を検査し、法定年数より短い行・起算日の誤り・早すぎる廃棄予定日を行番号と条文つきで示す(キー不要)。",
|
|
7
|
+
"paid": "ワークスペース内の規程・CSVを一括検査し、条文根拠と罰則つきの監査用レポートをMarkdown/CSVで書き出す。",
|
|
8
|
+
"need_key": "This option needs a licence (労務書類 保存期間チェック).",
|
|
9
|
+
"exts": []
|
|
10
|
+
}
|